The Security Checklist Every Kenyan SME Running Its Own Server Should Finish This Week
Most security advice aimed at small businesses is either a sales pitch or a 200-page framework nobody finishes. Neither helps the Nairobi SME with one Ubuntu VPS running ERPNext, a WordPress site, and a MySQL database that quietly holds every customer phone number the company has ever collected.
Here is my thesis after years of cleaning up other people's servers: you do not have a security budget problem, you have a completeness problem. The businesses that get hurt are almost never the ones missing an expensive tool. They are the ones where seven of ten basic controls are in place and the other three were never finished. Attackers do not need to beat your best control. They need to find the one you skipped.
So this is a checklist you can actually finish. Every item is free or nearly free. Most take under fifteen minutes. Do them in order, tick them off, and write the date next to each one.
The threat model that actually applies to you
Nobody is spending money on a zero-day for your business. What is actually pointed at your server right now is automation: scanners walking the entire IPv4 space looking for open Redis, exposed admin panels, and old plugin versions; credential stuffing bots replaying passwords leaked from other breaches; and phishing kits targeting your finance email. Kenya's National KE-CIRT/CC reported roughly 4.56 billion detected cyber threat events in the October to December 2025 quarter alone, and attributed the surge largely to weak patching and low phishing awareness rather than anything exotic.
Design for the bored automated attacker, not the movie villain. If you are not worth a human's afternoon, boring controls win.
The checklist
1. Make SSH key-only, and verify it actually took
Password SSH is the single most reliably attacked door on a Kenyan VPS. Disable it. The gotcha that catches people on Ubuntu 22.04 and 24.04 is that /etc/ssh/sshd_config.d/*.conf files are included near the top of the config and win over what you edited in the main file. Cloud images frequently ship a 50-cloud-init.conf that re-enables password auth.
# 1. Confirm your key works in a SECOND terminal before you lock anything
ssh -i ~/.ssh/id_ed25519 deploy@your.server.ip
# 2. Find every override that could re-enable passwords
sudo grep -rn "PasswordAuthentication\|PermitRootLogin" /etc/ssh/sshd_config /etc/ssh/sshd_config.d/
# 3. Write your own override that sorts last
sudo tee /etc/ssh/sshd_config.d/99-hardening.conf >/dev/null <<'EOF'
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin no
MaxAuthTries 3
EOF
# 4. Test the parsed, effective config - this is the step people skip
sudo sshd -t
sudo sshd -T | grep -E "passwordauthentication|permitrootlogin"
sudo systemctl restart ssh
If sshd -T says passwordauthentication yes, you are not done, no matter what your edited file says.
2. Close every port you did not deliberately open
Databases, Redis, and Elasticsearch bound to 0.0.0.0 are how a lot of "sophisticated attacks" actually start.
# What is actually listening on a public interface?
sudo ss -tulpn | grep -v "127.0.0.1\|::1"
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp
sudo ufw allow 80,443/tcp
sudo ufw enable
sudo ufw status numbered
Then verify from outside the box, because a host firewall plus a cloud provider firewall can disagree in surprising ways.
nmap -Pn -p- --open your.server.ip
3. Turn on automatic security patching
Unpatched software is on every incident post-mortem, and patching is the one control that scales for free. Ubuntu ships unattended-upgrades; it just is not always configured to matter.
sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgrades
cat /etc/apt/apt.conf.d/20auto-upgrades
# /etc/apt/apt.conf.d/52-my-overrides
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "03:30";
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
Unattended-Upgrade::Mail "ops@yourcompany.co.ke";
Set the reboot window to your real quiet hour, not 02:00 by superstition. Then run sudo unattended-upgrade --dry-run --debug once so you know it works.
4. Get admin panels off the public internet
phpMyAdmin, Adminer, /wp-admin, Portainer, Grafana, and your ERP's /app desk do not need to answer the whole world. Two cheap options: a WireGuard or Tailscale network for staff, or an nginx allowlist on the admin paths.
location /adminer.php {
allow 197.248.0.0/16; # replace with your real office/VPN range
allow 100.64.0.0/10; # Tailscale range
deny all;
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
}
5. MFA on the accounts that can destroy you
Rank your accounts by blast radius: domain registrar, DNS, VPS provider, GitHub, Google Workspace, M-Pesa/bank portals, then your application admin. Turn on MFA top-down. Prefer an authenticator app or hardware key over SMS, because SIM swap is a real and locally practical attack. Record recovery codes in the company password manager, not in someone's personal notes app.
6. Prove your backup restores
An untested backup is a belief, not a control. Ransomware and a fat-fingered DROP TABLE are answered by the same thing: a restore you have actually performed.
# Nightly logical dump, encrypted, offsite, with a retention policy
mysqldump --single-transaction --routines --triggers erp_prod \
| gzip \
| age -r age1qz...yourpublickey > /var/backups/erp_$(date +%F).sql.gz.age
rclone copy /var/backups/ remote:erp-backups/ --max-age 48h
# The part that counts: restore into a scratch database once a month
gunzip -c erp_2026-07-01.sql.gz | mysql erp_restore_test
mysql -e "SELECT COUNT(*) FROM erp_restore_test.tabCustomer;"
Keep at least one copy that your production server's credentials cannot delete. That is the whole point.
7. Least privilege in the database
Your application does not need GRANT ALL ON *.*. When the app is compromised, the database grant is the difference between one table leaking and the whole business leaking.
CREATE USER 'erp_app'@'127.0.0.1' IDENTIFIED BY 'generated-not-guessed';
GRANT SELECT, INSERT, UPDATE, DELETE ON erp_prod.* TO 'erp_app'@'127.0.0.1';
CREATE USER 'reporting'@'127.0.0.1' IDENTIFIED BY 'different-secret';
GRANT SELECT ON erp_prod.* TO 'reporting'@'127.0.0.1';
REVOKE ALL PRIVILEGES ON *.* FROM 'erp_app'@'127.0.0.1';
FLUSH PRIVILEGES;
8. Log, and keep the logs long enough to be useful
The median small business discovers an intrusion weeks after it starts. Default log rotation of seven days means the evidence is gone before you look. Keep at least 90 days of nginx access logs and auth logs, compressed. It costs a few hundred megabytes.
# /etc/logrotate.d/nginx (relevant lines)
daily
rotate 90
compress
delaycompress
missingok
9. Offboarding as a written procedure
Write a single file called OFFBOARDING.md listing every system a staff member could hold access to: server SSH keys, GitHub org, Google Workspace, the password manager, the VPS console, the M-Pesa till, WhatsApp Business, and the shared secrets file. When someone leaves, you walk the list the same day. If you cannot produce that list right now, you do not know who can log into your business.
10. The one-page incident plan
Under Kenya's Data Protection Act, section 43, if personal data is accessed by an unauthorised person and there is a real risk of harm, the data controller must notify the Data Commissioner within 72 hours of becoming aware, and communicate to affected data subjects. Seventy-two hours is not enough time to also invent a process. Your one-pager needs: who declares an incident, who calls the lawyer, where the logs are, the ODPC breach reporting link, and a template notification. Write it before you need it.
What to spend money on, in order
| Control | Time to do | Monthly cost |
|---|---|---|
| SSH keys only, verified | 15 min | KES 0 |
| ufw plus external port scan | 15 min | KES 0 |
| unattended-upgrades | 10 min | KES 0 |
| Admin panels behind VPN/allowlist | 45 min | KES 0 to 1,500 |
| MFA everywhere critical | 60 min | KES 0 |
| Offsite encrypted backups | 90 min | KES 300 to 1,500 |
| Password manager for the team | 60 min | KES 400 per user |
| Cloudflare in front of the origin | 30 min | KES 0 on free tier |
That is a complete baseline for well under KES 5,000 a month for a small team. Compare that to a KES 5 million administrative fine ceiling under section 63 of the Act, or a week of downtime during month-end.
What not to buy
Do not buy an "AI-powered threat platform" before you have finished the list above. Do not buy a penetration test while your admin panel is still on the public internet with a shared password; you already know what the report will say. Do not buy antivirus for a Linux server as your primary control. And be sceptical of any vendor who leads with compliance certificates rather than asking what your data actually is and where it lives.
The rhythm that keeps it true
Security decays. Put three recurring items in your calendar and treat them like payroll.
- Weekly, 10 minutes: read
sudo ufw status,sudo ss -tulpn, and the last 200 lines of auth log. Look for anything new. - Monthly, 30 minutes: restore one backup into a scratch database and count rows. Review who still has access to what.
- Quarterly, 60 minutes: rotate the credentials that matter, review the offboarding list against the actual payroll, and re-read your incident one-pager.
None of this is clever. That is the point. The Kenyan SMEs that get through the next two years without a headline are not the ones with the best tooling. They are the ones that finished the boring list and kept it finished.