Lesson 18 of 30 advanced 5 min read

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

  1. 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

firewall — only 22 and 443 open SSH keys only, no root login automatic security updates systemd-sandboxed service
Each layer removes attacker options; a breach at the edge still meets a locked-down service in the middle.

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 scoped DeviceAllow.

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.