Security · Self-Hosting · Principles

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.

⏱ 25 min📊 Intermediate📅 April 28, 2026

⚠ The Landscape Changed

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.

1

Defense in Depth

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:

  1. Network edge — Router firewall drops everything except 80/443. No exposed management ports.
  2. Host firewallufw only allows services you intend to expose. Default-deny inbound.
  3. Service isolation — Each application runs as its own system user. Containers (Docker/Podman) add another boundary.
  4. Access control — SSH keys only (no passwords). Apache .htaccess or Require ip for sensitive paths.
  5. Intrusion detection — fail2ban monitors logs and automatically bans aggressive IPs.
  6. Application hardening — CSP headers, SQL parameterization, input validation in your code.
  7. Monitoring — You can't respond to what you don't see. Log watch, disk alerts, unusual traffic patterns.

Related Reading

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.

Why this matters now

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.

2

Least Privilege

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:

  • Never run services as root. Apache, your Python API, your Node app — each gets its own system user.
  • Database permissions are surgical. Your web app's MySQL user needs SELECT/INSERT/UPDATE on its schema — not GRANT, not DROP, definitely not SUPER.
  • File permissions are intentional. chmod 644 for files that should be read, 755 for directories that need traversal. 600 for config files with secrets. Never 777.
  • SSH is key-only, not password. If you must allow SSH from the internet (you probably shouldn't), use ed25519 keys and disable password authentication entirely.
  • Containers are not permission boundaries unless you configure them to be. Run containers as non-root users. Use read-only root filesystems where possible.
Create a dedicated service user
# Create a system user with no shell and no home directory
sudo useradd -r -s /usr/sbin/nologin -d /nonexistent myapp
# Own only what it needs
sudo chown -R myapp:myapp /opt/myapp
sudo chmod 750 /opt/myapp
# Run the service as that user (systemd unit)
User=myapp
NoNewPrivileges=yes

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.

3

Assume Breach

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?

What to monitor

  • Access logs — You should know what normal traffic looks like. When you see 4,000 requests from a new IP in an hour, that should stand out.
  • Auth logs — Repeated SSH failures, sudo attempts, new user creation. These are red flags.
  • Process table — Are there processes running as users that shouldn't have active processes? A www-data process spawning a shell is never good.
  • Outbound connections — Your web server shouldn't be initiating connections to random IPs on port 443. That's exfiltration or C2 callbacks.
  • Disk usage changes — A sudden drop in free space can mean logs are being flooded, files are being encrypted (ransomware), or data is being staged for exfiltration.

★ The 3 AM Test

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.

Quick anomaly checks — cron these daily
# Failed SSH attempts in last 24h
grep "Failed password" /var/log/auth.log | wc -l
# Newly created users (check /etc/passwd mtime or diff)
find /etc/passwd -mtime -1
# Top talkers in Apache access log (last 10k lines)
tail -10000 /var/log/apache2/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -10
# Running processes owned by www-data or nobody
ps -U www-data -U nobody -o pid,cmd --no-headers
4

Zero Trust Networking

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:

  • Don't expose management interfaces. phpMyAdmin, phpLiteAdmin, Adminer, Portainer — these should never be world-accessible. If you need remote access, use a VPN (Tailscale, WireGuard) or SSH tunneling.
  • Services talk to each other over localhost only. If your web app and your database are on the same machine, MySQL should bind to 127.0.0.1, not 0.0.0.0.
  • Use a mesh VPN for personal access. Tailscale (free for personal use) or plain WireGuard gives you encrypted access to your infrastructure without exposing ports to the world. This is the single biggest security win you can make in an afternoon.
  • API endpoints need authentication. Even internal APIs. An attacker who lands on your box shouldn't be able to call arbitrary internal endpoints without a token or key.

★ The Biggest Self-Hosting Mistake

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.

5

Supply Chain Awareness

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:

  • Minimize dependencies. Every dependency is a trust decision. Before adding a library, ask: can I write this in 30 lines? If yes, write it.
  • Pin versions. 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.
  • Use a dependency scanner. pip-audit for Python, npm audit for Node, composer audit for PHP. These check your dependency tree against known CVEs.
  • apt unattended-upgrades — with caution. Security patches should be automatic. Everything else, test first. On Ubuntu/Debian, configure /etc/apt/apt.conf.d/50unattended-upgrades to only auto-install from the security pocket.
  • Read the diff. When a dependency updates, what actually changed? For critical dependencies, skim the changelog or diff. Yes, it's tedious. It's also how you catch a compromised release before it runs on your server.
Dependency auditing
# Python — check for known vulnerabilities
pip install pip-audit && pip-audit
Found 2 known vulnerabilities in 42 packages
requests 2.31.0 — CVE-2024-... (HIGH) → upgrade to >=2.32.0

# Node — same concept
npm audit --production

# PHP — Composer audit
composer audit

# System — check what needs updating
apt list --upgradable 2>/dev/null | grep -i security

Current Concerns for 2026

1. AI-Powered Reconnaissance

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.

2. Ransomware Targeting Small Organizations

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.

3. IoT Botnets Are Bigger and Smarter

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.

4. Credential Stuffing at Scale

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.

Your Security Baseline

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

How to prioritize

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?"

  1. Start with 1-5. These eliminate automated, indiscriminate attacks — the kind that make up 95%+ of what hits a self-hosted server. One afternoon of work.
  2. Add 6-9 as your services mature. These protect against targeted attacks and limit damage when something inevitably gets through.
  3. Pursue 10-14 when you're storing data that would be genuinely damaging to lose — donor records for a nonprofit, customer data, anything regulated.

★ The Most Important Security Habit

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.

← Back to Guides

Did this guide help?

Your answers shape what we write next.

Join the conversation.

Questions, experiences, or ideas — we're listening.