Skip to content
SecForge
RESOURCES · CHECKLIST

Linux server hardening checklist: from zero to production.

A fresh Linux install is not a secure one. The real steps — access, network, patching, services, logging — before that box touches production traffic.

July 8, 202610 min read

A default Linux install optimizes for compatibility, not security. The installer's job is to get you to a working, bootable system that talks to as much hardware and as many networks as possible with the least friction — it is not trying to guess what you will run on that box or who might come looking for it once it has a public IP address. Hardening is the deliberate, repeatable process of closing that gap: pulling permissions in, shrinking the attack surface, and making sure the box tells someone when something goes wrong — before it goes anywhere near production or the internet.

None of the steps below are exotic. Most take a few minutes each. What matters is doing all of them, every time, on every box — which is exactly what a checklist is for.

User and access hardening

Access control is the first layer, because almost every real-world compromise starts with a credential, not an exploit. Start here.

  • Disable direct root SSH login. Root should never be a valid remote login target — every session should authenticate as a named user and elevate with sudo when needed, so actions stay attributable. In /etc/ssh/sshd_config:
PermitRootLogin no
  • Enforce key-based authentication over passwords. Passwords are guessable, phishable, and reusable across sites; a properly generated SSH key pair is none of those things. Once your key is installed and confirmed working, turn passwords off entirely:
PasswordAuthentication no
PubkeyAuthentication yes
  • Apply least-privilege sudo rules, not blanket sudo access. Adding every admin to a wheel or sudo group with unrestricted ALL=(ALL) ALL access means one compromised account is a compromised root. Scope sudoers entries in /etc/sudoers.d/ to the specific commands a user or service actually needs, and prefer several narrow rules over one wide one.
  • Change the default SSH port — but be honest about what it does. Moving off port 22 is not a real security control; it does nothing against a targeted attacker who simply scans all 65535 ports in seconds. Its only real benefit is cutting down the constant background noise from automated bots crawling the internet for port 22, which makes your logs easier to read. Treat it as housekeeping, not defense.

Network hardening

If access hardening controls who can knock on the door, network hardening decides how many doors exist in the first place. The goal is default-deny: nothing is reachable unless you explicitly allowed it.

  • Set a default-deny firewall baseline. Both ufw (Debian/Ubuntu-family) and nftables (the modern successor to iptables, available broadly) can express the same policy: deny everything incoming by default, then allow only the specific ports the box actually needs to serve. A realistic ufw baseline for a web server that is also administered over SSH:
ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp
ufw allow 443/tcp
ufw enable
  • Install and enable fail2ban. A default-deny firewall stops unsolicited traffic, but it still has to let SSH and your web ports through — and those become the target of repeated brute-force login attempts. fail2ban watches auth logs and temporarily bans source IPs after a threshold of failed attempts, turning an unlimited guessing game into a throttled one:
apt install fail2ban
systemctl enable --now fail2ban

Patch and package hygiene

Most exploited vulnerabilities are already public and already patched — the box just hasn't received the fix yet. Patch hygiene closes that window, and package hygiene shrinks the number of things that could ever need patching.

  • Enable automatic security updates. On Debian/Ubuntu-family systems, the unattended-upgrades package applies security patches on a schedule without requiring a human to remember to run apt upgrade. Equivalent mechanisms exist for other distribution families (dnf-automatic on Fedora/RHEL, for example) — the concept matters more than the exact tool: patching should happen on a schedule, not on someone's memory.
  • Remove unused packages and services. Every installed package is a potential future vulnerability, even one you never touch. A default server image often ships with compilers, mail utilities, or sample services nobody asked for. Audit what's installed and remove what the box doesn't actually need — "installed but unused" is still attack surface, it just doesn't feel like it until it's the thing that gets exploited.

Service hardening

Every running service is a listening process with its own code, its own bugs, and its own privileges. Fewer running services, and less-privileged ones, mean less that can go wrong.

  • Disable and mask anything not actually needed. A stopped service can still be started by another process or a reboot; masking prevents that entirely.
systemctl disable --now cups
systemctl mask cups
  • Run services as a dedicated non-root user. If a web server or application process is compromised, the attacker inherits whatever privileges that process was running with. A service running as its own unprivileged user limits the blast radius; a service running as root hands over the whole machine.
  • Use systemd sandboxing directives for custom services. This is an underused but genuinely powerful lever: directives like NoNewPrivileges=true (blocks privilege escalation via setuid binaries) and ProtectSystem=strict (mounts most of the filesystem read-only for the service) constrain what a compromised process can do, even before you think about the application's own code. Add them to a service's unit file:
[Service]
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true

Logging and monitoring

Hardening reduces the odds of a bad day; logging is what tells you whether one is actually happening. A server with no logging can be hardened perfectly and still fail silently.

  • Enable auditd. The Linux Audit daemon records security-relevant events — file access, privilege escalation, authentication attempts — at a level of detail that default syslogging doesn't capture. It's the difference between knowing a breach happened and knowing exactly what the attacker touched.
  • Ship logs off the box, not just onto local disk. Logs that live only on the server they describe are the first thing an attacker with root access deletes. Forwarding logs to a centralized location — ideally one the compromised box itself cannot write over — means the record survives even if the server doesn't. A home-lab-scale example of exactly this kind of centralized log and detection setup is covered in a SIEM you can actually read.

File system and permissions

The last layer is the file system itself — the quiet defaults that determine who can read what, long after the network and service layers have done their job.

  • Set a sane default umask. A umask of 027 or 077 (rather than the looser 022 some distributions ship with) ensures new files aren't group- or world-readable by default, which matters most on multi-user boxes.
  • Tighten permissions on genuinely sensitive files. /etc/shadow should never be world-readable, and private keys should be locked down to their owner only:
chmod 640 /etc/shadow
chmod 600 ~/.ssh/id_ed25519
  • Disable unused kernel filesystem modules. A lower-priority step, but a real one for security-conscious deployments: filesystems like cramfs or freevxfs that the box will never mount are still loadable kernel modules, and an unused module is still code that could contain a bug. Blacklisting them removes a rarely-checked but real corner of the attack surface.

Taken together, these steps are a baseline, not a finish line. A server hardened once and never revisited drifts — a new service gets installed, a firewall rule gets loosened for a one-off test and never reverted, a package update reintroduces a default you'd already turned off. Revisit this checklist whenever the server's role changes, and pair it with the logging and monitoring from above: hardening decides what shouldn't happen, monitoring is what actually notices when it drifts anyway.