For LiNotes, LiChat, LiDio and any server of your own

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.

1 · Catas­trophic
2 · Critical
3 · Marginal
4 · Negli­gible
A · Frequent
High
High
Serious
Medium
B · Probable
High
High
Serious
Medium
C · Occa­sional
High
Serious
Medium
Low
D · Remote
Serious
Medium
Medium
Low
E · Impro­bable
Medium
Medium
Medium
Low

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.

Checksum (SHA-256) of haerten.sh 1.1:
feb071563810198c9d7f30d8e7360a96742ad0791d03d7e5b405b150e17da724
The command downloads the script, checks the checksum and opens it for reading. If it doesn’t say “OK”, stop. Then start it yourself with sudo bash haerten.sh.

Terminal
$ 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.

Terminal
$ 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.

Terminal (your computer)
$ 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.

Terminal
$ 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.

Terminal
$ 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

Terminal
$ 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

Terminal
$ 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.

watchdog.conf · fernwaechter.sh

🚨

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.

Part A (ESP32-C6) · Part B (Wi-Fi)

Guard 3: what happens in an emergency

🚪
T + 0 sDoor, vibration or tampering
⏳
up to 30 sPre-alarm: disarm with the key
📣
AlarmSiren, message to Part B
🌐
+ a few sDNS: homepage points to GitHub Pages
⚡
+ 20 sZigbee: server power off
Door contact (reed)GPIO 4 · normally closed VibrationGPIO 5 · SW-420 Tamper (case)GPIO 6 Key switchGPIO 7 · armed/disarmed Siren 12 VGPIO 10 → MOSFET ESP32-C6Part A · alarm panelZigbee coordinator own UPS / batterylocked case Zigbee: on/off Zigbee smart plugcertified, 230 Vor relay on 5/12 V DC ServerLUKS diskPower off =data safe UART “ALARM” ESP32 with Wi-FiPart B · redirectToken for 1 record onlyTLS with root cert DNS: www → GitHub Pagesshort TTL (60–300 s)

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.

Terminal
$ 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.

StandardWhat it coversCheck with
DISA STIGUS Department of Defense hardening rules, CAT I (critical) to CAT IIISTIGs and STIG Viewer · OpenSCAP with STIG profile · NIST National Checklist Program
CIS BenchmarksConsensus hardening for Linux, Docker, nginxLynis (free, local) · CIS-CAT
NIST SP 800-53Catalogue of security controls (AC, SC, CP …)Catalogue · OpenSCAP · NVD for known vulnerabilities
TLS: BSI TR-02102-2 · NIST SP 800-52r2Connection encryption, certificate, protocolstestssl.sh (local) · SSL Labs (tick “Do not show the results”) · internet.nl
OWASP Secure Headers · ASVSSecurity headers and web applicationsMozilla HTTP Observatory · securityheaders.com · OWASP ZAP
BSI IT-GrundschutzGerman standard for information securityCompendium and checklists · internet.nl (web and email)
CVE / known vulnerabilitiesDo my packages have known vulnerabilities?Debian Security Tracker · Ubuntu Security Notices · CVE.org
Attack surface from outsideWhat does the internet see of my server?Shodan · Censys · nmap from a different network
DNSDNSSEC and recordsDNSViz
MIL-STD-882ESafety as a system: find, assess and reduce hazardsNot a scanner. Use the risk matrix above, with all findings from the other tools