Your server.
Your fortress.
Run your own server and your data stays your call.
Here’s how to lock it down against intruders, one layer at a time.
Defence in layers
No single safeguard stops everything. That’s why security experts build their systems in layers: hardware, network, operating system, services, data. If one layer fails, the next one holds. We apply the same idea to the server at home.
This guide follows publicly available standards: MIL-STD-882E (risk assessment), the DISA STIGs from the US Department of Defense (hardening rules, categories CAT I to III), NIST SP 800-53, the CIS Benchmarks and the IT-Grundschutz Compendium from the BSI (German Federal Office for Information Security). It is not a substitute for an audit, and it is not a certification.
1 · Assess the risk
What comes first
MIL-STD-882E rates every hazard with two questions. How bad would it be? And how likely is it? Whatever is red gets fixed first.
High: an open door
Password login from the internet, root allowed to log in, no updates. Catastrophic and tried all the time. Close this first.
Serious: too much on show
A database or admin page open to the internet, version numbers in every response. Anyone who looks will find it.
Medium: theft
Rare, but critical when it happens: the machine disappears, disk and all. Encryption and Guard 3 protect you here.
2 · The one-button solution
One script for everything
For Debian, Ubuntu and Raspberry Pi OS. First it shows what’s missing. Then it asks before going ahead, and it backs up every file before changing it. It won’t lock you out: passwords stay allowed until an SSH key is in place.
$ sudo bash haerten.shCurrent state (Debian GNU/Linux 12) ✘ automatic security updates off ✘ no active firewall (ufw) ✘ SSH: password login allowed ✔ SSH key in place for userContinue? [y/N] y1 · Automatic security updates ✔2 · Firewall: SSH (limited), 80, 443 ✔3 · SSH: keys only, no root ✔4 · fail2ban ✔5 · Kernel ✔6 · nginx: version hidden ✔! Open a second terminal and log in again.
What it does
- Updates: security updates install themselves. STIG CAT II
- Firewall: all incoming traffic blocked, except SSH (limited), 80 and 443. NIST SC-7
- SSH: no root, keys only, fewer attempts. STIG CAT I
- fail2ban: guess wrong too often and you’re out for an hour. Repeat offenders, for a week. NIST AC-7
- Kernel: no redirects, no address leaks, protected links. CIS 3.x
- nginx: no version number, plus a snippet for security headers. OWASP
Just look, without changing anything: sudo bash haerten.sh --pruefen. Skip a step: --ohne firewall. More ports: --port 8448. The script itself prints its messages in German, so the run above is a translation. It accepts “y” as well as “j” (ja).
Read first, then run. Running a script from the internet with sudo is a matter of trust. That’s why even the button above doesn’t run anything: it downloads the script, checks the checksum and shows it to you. You start it yourself. It’s short and commented. You can also read the source on this page.
3 · Step by step
Prefer the terminal yourself?
The same steps, by hand. That way you know what every line does.
Automatic security updates STIG CAT II
Most break-ins exploit holes that were patched long ago.
$ sudo apt update && sudo apt install -y unattended-upgrades $ sudo dpkg-reconfigure -plow unattended-upgrades
SSH keys instead of passwords STIG CAT I
Create a key on your own computer and copy it to the server. Then test in a second window that you can log in without a password.
$ ssh-keygen -t ed25519 $ ssh-copy-id benutzer@dein-server
Seal up SSH STIG CAT I
Only once key login works: lock out root, switch off passwords, limit attempts.
$ printf 'PermitRootLogin no\nPasswordAuthentication no\n…' \ | sudo tee /etc/ssh/sshd_config.d/10-haerten.conf $ sudo sshd -t && sudo systemctl reload ssh
Firewall: only what you need NIST SC-7
Close everything, then open only what you need. Check your SSH port first: sudo sshd -T | grep ^port. If it isn’t 22, change the number below, or you’ll lock yourself out. Watch out with Docker: published container ports bypass ufw. So bind containers to 127.0.0.1 and let only nginx face the outside.
$ sudo apt install -y ufw $ sudo ufw default deny incoming $ sudo ufw limit 22/tcp && sudo ufw allow 80,443/tcp $ sudo ufw enable
Lock out failed attempts NIST AC-7
$ sudo apt install -y fail2ban $ printf '[sshd]\nenabled = true\nbackend = systemd\n…' \ | sudo tee /etc/fail2ban/jail.d/haerten.local $ sudo systemctl restart fail2ban
Encrypt the disk NIST SC-28
Best done during installation: choose “Encrypted LVM” (LUKS). Then a thief without the key can’t read your data. For a server without a screen, you can unlock it over SSH at boot (package dropbear-initramfs).
Web server: headers, no version number OWASP ASVS
$ echo 'server_tokens off;' | sudo tee /etc/nginx/conf.d/server-tokens.conf $ sudo nginx -t && sudo systemctl reload nginx
And add these to every HTTPS block: Strict-Transport-Security, Content-Security-Policy, X-Content-Type-Options nosniff, Referrer-Policy no-referrer, Permissions-Policy. Careful: a strict Content-Security-Policy blocks inline scripts and outside sources, so test it first. Only add includeSubDomains to HSTS once every subdomain has HTTPS. And an add_header inside a location block drops all inherited headers there.
Backups, stored separately NIST CP-9
3-2-1: three copies, two media, one off-site, encrypted (with restic or borg, for example). And practise a restore once a quarter. Only a test restore shows whether the backup is complete.
4 · The three guards
Who keeps watch while you sleep?
Securing your server is only the start. Guards notice when something happens anyway, and they act. From software to hardware to an alarm system on the server cabinet.
Guard 1 · Software
A small script on the server, run every two minutes by a systemd timer:
- Services and containers are running, or get restarted
- The website responds
- The certificate is valid for at least 14 more days
- Disk below 90 %, temperature below 80 °C
- Alerts go to your own ntfy server or by email, at most once an hour
waechter.sh · .service · .timer · Settings · alarm-ntfy.sh
Guard 2 · Hardware and remote guard
What if the server itself freezes? The hardware watchdog in the chip restarts it when the system stops responding for 15 seconds.
And if it goes down completely, only a remote guard on a second device will notice. It checks reachability, DNS and open ports from the outside and reports anything unexpected.
Guard 3 · Break-in protection
An alarm panel with ESP32-C6 and Zigbee: door contact, vibration sensor and tamper sensor on the server cabinet.
When the alarm goes off, it cuts power to the server, so the encrypted disk is useless. It points your homepage to a copy on GitHub, so your site stays online, and it raises the alarm on your phone.
Guard 3: what happens in an emergency
Why two ESP32s?
The ESP32-C6 speaks Zigbee and keeps an eye on the sensors. A second ESP32 with Wi-Fi switches the DNS record when the alarm goes off. That way Wi-Fi never interferes with the radio link to the plug. And if one part fails, the other keeps working.
Build it safely
- No DIY on 230 V: use a certified Zigbee smart plug, or a relay on the low-voltage circuit only (5/12 V).
- Separate power supply for the alarm panel (UPS or battery), so an intruder can’t simply unplug it.
- Token with minimal rights: it may change that one DNS record and nothing else. Turn on flash encryption on the ESP32.
- Encrypt the disk (LUKS): only then does “power off” make the data worthless to a thief.
- Plan for false alarms: to the server, “power off” is a power cut. A journaling file system (ext4), databases with a transaction log and a daily backup take care of that.
- Prepare a fallback: a copy of your homepage on GitHub Pages in a public repository. Keep the DNS TTL short.
To be honest: the programs for Guard 3 compile without errors (arduino-esp32 3.3.12), but we haven’t tested them on real hardware in long-term use. Test the circuit before you rely on it. Interference on the Zigbee radio can stop the power cut. So remember: disk encryption is the real protection. The guard is the second layer.
Alarm on your phone
Loud, persistent and even through “Do not disturb”, much like an emergency alert. ntfy does this at its highest priority. Guard 3 sends the alarm straight from the ESP32, because by then the server has no power. Guards 1 and 2 use the script.
$ curl -fsSLo alarm-ntfy \ https://lisoftware.de/sicherheit/waechter/alarm-ntfy.sh $ echo '3aa63d7fe351ea39795a9d21a7bad644a5e94ab8ae9c9843ad076e99cedfae3d alarm-ntfy' | sha256sum -c $ sudo install -m 755 alarm-ntfy /usr/local/bin/ $ sudoedit /etc/default/alarm-ntfy # NTFY_URL, NTFY_TOKEN $ sudo alarm-ntfy --test $ sudo alarm-ntfy "Server cabinet door open"
Set up your phone
- ntfy app: install it (F-Droid, Google Play or App Store), add your ntfy server and subscribe to the topic. On Android, turn on “Instant delivery” for the subscription.
- Your own server with a token: on the public ntfy.sh, anyone who knows the topic name can read along and send fake alarms. So use your own ntfy server with an access token and a long, random topic.
- Android: System settings → Notifications → ntfy → the channel for the highest priority: allow it to override “Do not disturb”. In the app, turn on the setting that keeps alerting for the highest priority.
- iPhone: add ntfy as an allowed app under Focus → “Do Not Disturb”. To be honest: with your own server, instant notifications only arrive if it sends a wake-up call through ntfy.sh and Apple via
upstream-base-url. That call carries no text, but it does carry a hash of the topic. Whether you use this service is up to you. - Test alarm with
sudo alarm-ntfy --test, then once a month.
No app can trigger a real official alert (cell broadcast). An ntfy alarm at the highest priority comes closest.
5 · Get it checked
Test your own server
The best-known tools and sites for testing your server against the standards. Online checkers see your address. If you’d rather they didn’t, use the tools that run on your own computer.
| Standard | What it covers | Check with |
|---|---|---|
| DISA STIG | US Department of Defense hardening rules, CAT I (critical) to CAT III | STIGs and STIG Viewer · OpenSCAP with STIG profile · NIST National Checklist Program |
| CIS Benchmarks | Consensus hardening for Linux, Docker, nginx | Lynis (free, local) · CIS-CAT |
| NIST SP 800-53 | Catalogue of security controls (AC, SC, CP …) | Catalogue · OpenSCAP · NVD for known vulnerabilities |
| TLS: BSI TR-02102-2 · NIST SP 800-52r2 | Connection encryption, certificate, protocols | testssl.sh (local) · SSL Labs (tick “Do not show the results”) · internet.nl |
| OWASP Secure Headers · ASVS | Security headers and web applications | Mozilla HTTP Observatory · securityheaders.com · OWASP ZAP |
| BSI IT-Grundschutz | German standard for information security | Compendium and checklists · internet.nl (web and email) |
| CVE / known vulnerabilities | Do my packages have known vulnerabilities? | Debian Security Tracker · Ubuntu Security Notices · CVE.org |
| Attack surface from outside | What does the internet see of my server? | Shodan · Censys · nmap from a different network |
| DNS | DNSSEC and records | DNSViz |
| MIL-STD-882E | Safety as a system: find, assess and reduce hazards | Not a scanner. Use the risk matrix above, with all findings from the other tools |