Before this:Container security
Production hardening
Key takeaways
Hardening is least privilege applied to a whole host. Close every port you don’t
serve with a firewall. Log in with SSH keys, not passwords. Turn on automatic
security updates. And confine each service to a sandbox — GopherTrunk’s systemd
unit does this with DynamicUser, ProtectSystem=strict, NoNewPrivileges, and
tightly scoped device access. Every switch removes power an attacker could otherwise
inherit.
Container security locked down one container; this lesson locks down the host it runs on and any bare-metal service beside it. The idea is the same — least privilege — scaled up to the machine. It leans on firewalls, SSH, and hardening systems from the other modules.
Close everything you don’t serve
Every open port is a door. A firewall shuts every door except the few you actually
use. On Ubuntu/Debian, ufw makes this a few lines:
sudo ufw default deny incoming # deny everything inbound by default
sudo ufw default allow outgoing
sudo ufw allow 22/tcp # SSH
sudo ufw allow 443/tcp # HTTPS via the reverse proxy
sudo ufw enable
Notice GopherTrunk’s API port 8080 is not opened: it’s bound to 127.0.0.1 and
reached only through the reverse proxy on
- If a service doesn’t need to be reachable from outside, don’t open its port — the proxy and localhost handle the rest.
Lock down SSH
SSH is the front door to the host, so harden it. Use key authentication and turn password login off entirely — a key can’t be guessed the way a password can:
# /etc/ssh/sshd_config
PasswordAuthentication no
PermitRootLogin no
PubkeyAuthentication yes
No password auth means brute-force attempts against sshd are pointless, and
PermitRootLogin no forces admins through an unprivileged account that then uses sudo
— an audit trail and one less privileged door. See
SSH & remote access for the key setup.
Patch automatically
Most compromised servers were running software with a known fix that nobody applied. Automatic security updates close that gap:
sudo apt install unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades # enable security updates
Security patches now land without you logging in. Pair it with a reboot window for kernel updates and you shut the largest, cheapest attack vector — see package management.
Sandbox each service
The systemd lesson introduced GopherTrunk’s unit; hardening is where its sandbox directives earn their place. The real unit confines the scanner tightly:
DynamicUser=true # run as an ephemeral, unprivileged user
ProtectSystem=strict # the whole filesystem is read-only, except...
ReadWritePaths=/var/lib/gophertrunk
ProtectHome=true # /home is invisible to the service
NoNewPrivileges=true # can never gain new privileges via setuid
ProtectKernelModules=true # can't load kernel modules
ProtectKernelTunables=true # can't rewrite /proc/sys
RestrictRealtime=true
RestrictNamespaces=true
DeviceAllow=char-usb_device rwm # only the USB device it actually needs
Read that as a list of powers removed: the scanner can’t write outside its data directory, can’t see home directories, can’t escalate privileges, can’t touch the kernel, and can reach only USB device nodes. Even if the process is fully compromised, the attacker inherits this tiny box — the exact mirror of the container’s dropped capabilities, applied to a bare-metal service.
Hardening is layers, not a switch
Quick check: what does ProtectSystem=strict in GopherTrunk's unit do?
Recap
- Hardening is least privilege applied to the whole host, in layers.
- A firewall (
ufw) closes every port but the few you serve — GopherTrunk’s 8080 stays on localhost. - Use SSH keys, disable password and root login.
- Turn on automatic security updates to close known holes unattended.
- Sandbox each service — GopherTrunk’s systemd unit uses
DynamicUser,ProtectSystem=strict,NoNewPrivileges, and scopedDeviceAllow.
Next up: automating build, test, and release with CI/CD pipelines.
Frequently asked questions
What is the principle of least privilege?
Give every user, process, and service exactly the access it needs to do its job and nothing more. A web app doesn’t need root; a scanner doesn’t need to write outside its data directory; an admin doesn’t need password SSH. When something is compromised, least privilege limits how far the damage spreads — the attacker inherits only the narrow access that component had.
Should I turn on automatic security updates?
For most servers, yes. Unattended security updates close known holes without waiting for you to log in, and known-but-unpatched vulnerabilities are how most servers get compromised. On Debian and Ubuntu, unattended-upgrades applies security patches automatically. Pair it with a reboot policy for kernel updates and you close the largest, easiest attack window.