Practical Cybersecurity for Self-Hosters
Principles-first security for people who run their own servers. No fearmongering, no enterprise bloat — just what actually matters in 2026.
Principles-first security for people who run their own servers. No fearmongering, no enterprise bloat — just what actually matters in 2026.
When I wrote Keep Fingers Out of Your Pi in 2016, the threats to a self-hosted server were mostly opportunistic — bots scanning for default passwords, automated WordPress exploits, the occasional DDoS script. In 2026, AI-powered scanners probe your infrastructure within minutes of going online. Supply chain compromises inject backdoors through dependencies you've never audited. Ransomware crews now target small organizations — including nonprofits — because they know those targets lack dedicated security staff.
The good news: the principles that keep you safe haven't changed. They've just become non-optional.
No single control will save you. A firewall won't stop a compromised dependency. fail2ban won't catch a misconfigured API leaking data. The principle is simple: every layer should assume the layer above it has failed.
A practical layered stack for a self-hosted server looks like:
ufw only allows services you intend to expose. Default-deny inbound..htaccess or Require ip for sensitive paths.Keep Fingers Out of Your Pi covers layers 1-4 for Raspberry Pi. Hardening a Self-Hosted Server covers the full checklist for Ubuntu 24.
AI-powered reconnaissance tools — available as open-source projects anyone can run — now probe your server's entire surface within minutes. They don't just try admin/admin and move on. They fingerprint your software versions, cross-reference CVE databases, and chain exploits automatically. A single exposed service running an outdated version is no longer a theoretical risk — it will be found and tested.
The attackers' automation has improved. Your defense needs layers that catch what the previous layer missed.
Every process, user, and service should have exactly the permissions it needs to function — and nothing more. If your web app gets compromised, the attacker inherits whatever privileges that process had. Make them as useless as possible.
Concrete steps:
chmod 644 for files that should be read, 755 for directories that need traversal. 600 for config files with secrets. Never 777.The NoNewPrivileges=yes systemd directive is underused and powerful — it prevents the process from ever gaining new privileges through setuid binaries or capability changes, even if compromised.
Design as though compromise is a question of when, not if. This isn't pessimism — it's engineering. What happens when (not if) someone gets in? Can you detect it? Can you limit the blast radius? Can you recover?
www-data process spawning a shell is never good.If your server were compromised at 3 AM, how would you know? If the answer is "I'd find out when something breaks," you need monitoring. Start with fail2ban (it's already in our other guides) and add a cron job that checks for anomalies in auth.log. Even a daily email summary of unusual activity is better than nothing.
Zero trust isn't an enterprise buzzword — it's a useful principle at any scale: never assume a connection is legitimate just because of where it originates. Your LAN isn't inherently safe. Your localhost isn't inherently safe if a process is compromised.
Applied to self-hosting:
127.0.0.1, not 0.0.0.0.I see it constantly: someone exposes phpMyAdmin on example.com/phpmyadmin, secures it with a password, and thinks they're safe. In my access logs, every single server I've ever run gets probed at /phpmyadmin, /wp-admin, /.env, and /.git — often within hours of going online. If you need database admin access remotely, use an SSH tunnel: ssh -L 8080:localhost:80 yourserver, then access it at http://localhost:8080/phpmyadmin. Zero exposure, full access.
You audit your own code. Do you audit every dependency? Every npm package, every pip library, every apt package you install? Supply chain attacks — where an attacker compromises a widely-used library to distribute backdoors — have become one of the most effective attack vectors. In 2024-2025, several high-profile npm and PyPI packages were found to contain credential stealers disguised as legitimate utilities.
What you can do:
package-lock.json, requirements.txt with exact versions (flask==3.1.0, not flask>=3.0). Floating versions mean you're pulling untested code on every deploy.pip-audit for Python, npm audit for Node, composer audit for PHP. These check your dependency tree against known CVEs./etc/apt/apt.conf.d/50unattended-upgrades to only auto-install from the security pocket.Open-source tools now combine vulnerability scanning with LLM-driven exploitation chaining. They don't just report "Apache 2.4.57 has CVE-2024-XXXXX" — they automatically look up the PoC, adapt it to your specific configuration, and attempt exploitation. The time from exposure to attack attempt has shrunk from days to minutes. Every port you expose needs to be running something you'd trust under active attack.
Ransomware operators have shifted strategy. Instead of targeting one Fortune 500 company for a $10M payout, they target 1,000 small organizations for $10K each. Small nonprofits, municipal offices, and self-hosted operations are all in-scope. The typical entry vector: an exposed RDP or SSH port with password authentication, a vulnerable web application, or a phishing link that installs a backdoor. If your data isn't backed up off-server (and tested for restoration), you're gambling.
The Mirai-style botnets of 2016 were dumb — they tried default credentials and moved on. Modern IoT botnets use ML to classify device types and adapt their exploitation strategy per device. Raspberry Pis are specifically targeted because they're powerful enough to be useful in a botnet (unlike a lightbulb). If you're running a Pi with default credentials or an unpatched OS, it's not a question of if it'll be recruited — it's a question of whether you'd notice.
When a major service is breached (and they are, regularly), username/password pairs are sold in bulk. Attackers then try those credentials against every reachable service — your self-hosted WordPress, your phpBB forum, your custom login form. If you or your users reuse passwords, those credentials will be tried against your server. This is why fail2ban matters even if you think your passwords are strong. It stops the automation before it finds a hit.
This is a tiered checklist. Start with Tier 1 — these are things you can do in an afternoon and they eliminate the most common attack vectors. Tier 2 adds meaningful depth. Tier 3 is for when you're hosting data you can't afford to lose.
| # | Control | Tier | Effort | What it stops |
|---|---|---|---|---|
| 1 | Change default passwords. Pi user, MySQL root, any admin panel. Every single one. | Beginner | 10 min | Automated botnet recruitment |
| 2 | Install and enable ufw. Allow 80/tcp, 443/tcp, and SSH only from your LAN. Default-deny all else. | Beginner | 15 min | Exposed services, lateral movement |
| 3 | Install fail2ban. Enable at minimum the sshd and apache-auth jails. | Beginner | 15 min | Brute force, credential stuffing |
| 4 | Set up unattended-upgrades for security patches only. They install automatically; everything else you review. | Beginner | 10 min | Known-vulnerability exploitation |
| 5 | Disable password SSH auth. Keys only. If SSH is on a nonstandard port, that's not security, but it reduces log noise. | Beginner | 10 min | SSH brute force (the #1 log entry on any exposed server) |
| 6 | Run services as non-root users with NoNewPrivileges=yes in systemd units. |
Intermediate | 30 min | Privilege escalation after compromise |
| 7 | Database: least-privilege users. Your web app's DB user can't DROP or ALTER tables. | Intermediate | 20 min | Data destruction via SQL injection |
| 8 | Set up CSP and security headers in Apache. HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy. | Intermediate | 30 min | XSS, clickjacking, MIME sniffing |
| 9 | Off-server backups, tested. If ransomware encrypts your server, can you restore? Have you tested that restore works? | Intermediate | 2 hr | Ransomware, hardware failure, human error |
| 10 | Dependency auditing in CI (or at minimum, before each deploy). pip-audit, npm audit, composer audit. |
Intermediate | 30 min | Supply chain compromise via known CVEs |
| 11 | Monitoring with alerting. Daily cron checks for unusual auth activity, new users, unexpected outbound connections. | Advanced | 4 hr | Undetected compromise (< 3 AM test) |
| 12 | Mesh VPN for personal access. Tailscale or WireGuard. No exposed management ports. | Advanced | 2 hr | Targeted attacks on management interfaces |
| 13 | Container hardening. Non-root users inside containers, read-only rootfs, seccomp/AppArmor profiles. | Advanced | 8 hr | Container escape, lateral movement |
| 14 | Network segmentation. If you run multiple services on multiple Pi's, place public-facing services on a separate VLAN from internal ones. | Advanced | 1 day | Lateral movement after initial compromise |
Security is a resource allocation problem. You have finite time; threats are infinite. The right question isn't "am I secure?" — it's "what's the most impactful thing I can fix next?"
It's not a specific tool or configuration. It's this: every time you expose something new to the internet, ask what happens if it gets compromised. If the answer makes you uncomfortable, don't expose it yet. Tunnel in instead. Add monitoring first. Limit what that service can touch. The best security control is the one you apply before you need it.
Your answers shape what we write next.
Questions, experiences, or ideas — we're listening.