Skip to content

Server Hardening

Every article in this module covered one control in depth: least privilege as a principle, sudo and PAM as the authentication and privilege-escalation layer, SELinux/AppArmor as mandatory access control, firewalls as the network boundary, fail2ban and auditd as detection. None of them, on its own, is "server hardening" — hardening is the discipline of applying all of them, in a sensible order, to one concrete server, plus a handful of items that don't have their own dedicated article: removing what isn't needed, automating patching, and verifying the result. This closing article is that checklist.

What You Will Learn

By the end of this article you will be able to:

  • explain why hardening steps need to be applied and verified in order, not all at once;
  • reduce a server's package and service footprint to what it actually needs;
  • configure automatic security updates and know what they don't cover;
  • apply a handful of kernel-level (sysctl) hardening settings and explain what each does;
  • use lynis to get an independent, automated second opinion on a server's hardening state.

Why Order Matters

A hardening pass touches authentication, the firewall, and system services all at once if done carelessly — and any one of those, misapplied, can lock out the very session doing the work. The safe sequence mirrors the "confirm before the next irreversible step" pattern used throughout this course:

  1. confirm remote access works and has a fallback (a console session independent of SSH, if the server is remote);
  2. reduce the package and service footprint;
  3. lock down authentication (SSH, sudo, PAM, password policy);
  4. apply mandatory access control;
  5. apply the firewall, SSH access confirmed first;
  6. add detection (fail2ban, auditd, log review);
  7. automate patching;
  8. verify the whole result independently.

Each numbered step below links to the article that covers it in depth — this article's job is the ordering and the items that don't belong anywhere else.

Step 1: Confirm a Fallback Before Touching Anything

Before any other step, confirm there's a way back onto the server that doesn't depend on the change about to be made — a hosting provider's console access, or a second, already-open SSH session kept alive throughout the entire hardening pass. Every destructive-adjacent step below (ufw enable, a PAM change, an SSH config reload) repeats this same warning in its own article; stated once here, it applies to the whole checklist, not just one step in it.

Step 2: Reduce the Footprint

The single cheapest hardening step, and the one most often skipped, is removing what a server doesn't need at all — Linux Security Fundamentals introduced this as attack surface reduction.

ss -tulpn
Netid  State   Local Address:Port
tcp    LISTEN  0.0.0.0:22
tcp    LISTEN  0.0.0.0:3306

A MySQL server listening on 0.0.0.0:3306 on a host where only local applications should ever reach the database is worth questioning immediately — either it should bind to 127.0.0.1 only, or the firewall rule for it should never have allowed access from outside the application subnet in the first place (see Firewall and UFW).

systemctl list-unit-files --state=enabled
bluetooth.service         enabled
cups.service              enabled
ssh.service               enabled

bluetooth and cups (printing) enabled on a headless server are leftovers from a default install, not requirements — each one is a running daemon with its own attack surface and no legitimate purpose on this machine:

sudo systemctl disable --now bluetooth cups
sudo apt purge cups bluez
sudo apt autoremove --purge

autoremove --purge clears packages that were only installed as dependencies of something already removed, along with their configuration files — a smaller installed footprint is fewer packages that could, in principle, ever need a security patch.

Step 3: Lock Down Authentication

This is where most of the module's earlier articles apply directly, in this order:

  1. SSH — key-only authentication, no root login, restricted AllowUsers — see SSH Hardening, which already covers the full checklist and the safe rollout order.
  2. sudo — scoped rules, no blanket NOPASSWD: ALL, I/O logging for sensitive accounts — see sudo Security.
  3. PAMpam_faillock enabled, nullok removed from pam_unix — see PAM Basics and Root and sudo.
  4. Password policy — complexity, history, and expiration set deliberately rather than left at distribution defaults — see Password Policies.

No new commands here — the point of listing them together is the order: authentication needs to be solid before the firewall and MAC layers go on top of it, since those two protect a system whose authentication is assumed to already be trustworthy.

Step 4: Apply Mandatory Access Control

Confirm SELinux or AppArmor is actually enforcing, not merely installed:

getenforce         # expect: Enforcing
aa-status | head -n 3   # expect: profiles in enforce mode

A server with SELinux installed but left in Permissive, or AppArmor's profiles left in complain, gets the logging benefit of MAC without the blocking benefit — worth checking explicitly, since a Permissive system otherwise looks identical to an Enforcing one in every command that doesn't specifically check the mode. SELinux and AppArmor covers switching modes safely and building profiles for anything not already covered by the default policy.

Step 5: Apply the Firewall

sudo ufw allow 22/tcp    # or the confirmed alternate SSH port
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw enable

As covered in Firewall and UFW, the SSH allow rule must exist and be confirmed before ufw enable runs, not after — repeated here because it's the step most likely to end a hardening session with a locked-out server if done out of order.

Step 6: Add Detection

sudo systemctl enable --now fail2ban auditd

fail2ban and auditd Basics cover configuring each in depth; the hardening checklist's job is simply confirming both are actually running, since a service that's installed but not enabled provides no protection at all — a mistake easy to make when a package installs a daemon but leaves it disabled by default.

Step 7: Automate Patching

Production Updates already covers unattended-upgrades in full — a hardened server that never receives security patches is hardened against yesterday's vulnerabilities only:

sudo dpkg-reconfigure --priority=low unattended-upgrades
grep -A2 'Unattended-Upgrade::Allowed-Origins' /etc/apt/apt.conf.d/50unattended-upgrades
Unattended-Upgrade::Allowed-Origins {
        "${distro_id}:${distro_codename}-security";

Automatic patching is not automatic verification

unattended-upgrades applies security patches; it does not confirm services restart correctly afterward, and it does not cover major version upgrades. Production Updates covers needrestart, the tool that closes exactly this gap — a patched-but-not-restarted service is still running the vulnerable code in memory.

A Few Items With No Article of Their Own

Two small, standalone hardening items don't warrant a full article but belong on any checklist:

A login banner, displayed before authentication, stating that access is monitored and unauthorized use is prohibited — a legal and deterrent measure in many jurisdictions and organizational policies, not a technical control:

sudo nano /etc/issue.net
Authorized access only. All activity is monitored and logged.
sudo grep Banner /etc/ssh/sshd_config
Banner /etc/issue.net

Kernel network hardening via sysctl, a small set of settings worth enabling on any internet-facing server:

sudo nano /etc/sysctl.d/99-hardening.conf
net.ipv4.tcp_syncookies = 1
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.icmp_echo_ignore_broadcasts = 1
  • tcp_syncookies mitigates a SYN-flood by allowing the kernel to handle a connection backlog without keeping full state for each half-open connection;
  • accept_redirects = 0 and accept_source_route = 0 refuse ICMP redirects and source-routed packets, both of which can otherwise be abused to reroute this host's traffic through an attacker-controlled path;
  • icmp_echo_ignore_broadcasts prevents the host from participating in a Smurf-style amplification attack.
sudo sysctl -p /etc/sysctl.d/99-hardening.conf
net.ipv4.tcp_syncookies = 1
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.icmp_echo_ignore_broadcasts = 1

Step 8: Verify Independently with lynis

After applying the checklist by hand, an independent audit tool provides a second opinion that doesn't share the blind spots of whoever just did the hardening:

sudo apt install lynis
sudo lynis audit system
[+] System Tools
------------------------------------
  - Scanning available tools...
  - Checking present vendor and tools...

[+] Kernel
------------------------------------
  - Checking default run level                             [ RUNLEVEL 5 ]
  - Checking CPU support (NX/PAE)                           [ FOUND ]
...
Hardening index : 68 [############        ]

Warning: Access to audit log is not restricted
Suggestion: Consider hardening SSH configuration [CIS-CAT: 5.2.11]

lynis doesn't fix anything itself — it inspects the system's actual configuration against a large set of known hardening benchmarks and reports a score alongside specific, actionable suggestions. The score itself matters less than the specific list of suggestions underneath it; treating "get to 100" as the goal, rather than reviewing each suggestion on its own merits for this specific server, produces the same box-checking failure mode covered in Linux Security Fundamentals — a high score is not proof of security, only evidence that the tool's specific checks currently pass.

Practical Scenario: Hardening a Fresh Ubuntu Server LTS Before Production

Problem. A newly provisioned Ubuntu Server LTS instance needs to go from default install to production-ready.

Following the checklist in order:

# Step 1: fallback already confirmed — provider console access verified before starting

# Step 2: footprint
sudo systemctl list-unit-files --state=enabled | grep -v -E 'ssh|systemd|cron|networkd|resolved'
sudo apt autoremove --purge

# Step 3: authentication (see linked articles for full detail)
# SSH keys confirmed, PasswordAuthentication no, sudoers scoped, pam_faillock enabled

# Step 4: MAC
aa-status | head -n 3

# Step 5: firewall — SSH confirmed first
sudo ufw allow 22/tcp
sudo ufw enable

# Step 6: detection
sudo systemctl enable --now fail2ban auditd

# Step 7: patching
sudo dpkg-reconfigure --priority=low unattended-upgrades

# Step 8: verify
sudo lynis audit system

Result. lynis's hardening index rises from a fresh-install baseline (typically in the 50s) into the high 70s or 80s after the checklist is applied, and its remaining suggestions become the input for the next iteration rather than a one-time pass.

Conclusion. No single step here was new — every command traces back to an article already covered in this module or an earlier one. The value of this final article is the order and the completeness of the list, not any individual technique.

Common Mistakes

  • Applying every hardening step at once, without testing between them. As emphasized throughout this module, a mistake in any one step (a bad PAM line, an enabled firewall with no SSH rule) is far easier to isolate and fix when steps are applied and verified one at a time.
  • Hardening authentication and the firewall but skipping the footprint reduction step. A server with fewer running services simply has less that needs to be hardened in the first place — skipping this step means every later step has more surface to cover.
  • Treating a lynis score as the finish line. As covered above, the score is a rough signal; the specific list of suggestions, evaluated against what this particular server actually needs, is the part that matters.
  • Enabling unattended-upgrades and assuming patching is now fully automated. Reboots for kernel updates and service restarts after a library update, covered in Production Updates, still require an active process, not just the package's default configuration.
  • Running the checklist once at provisioning time and never again. Permission Audit and this module's overall message both apply here — a server hardened once and never revisited drifts back toward its unhardened default over time.

Exercises

  1. On a lab VM, run through steps 2 (footprint reduction) and 8 (lynis) only, note the hardening index, then apply steps 3 through 7 and re-run lynis. Compare the two scores and identify which specific suggestions disappeared.
  2. Explain why the order in this checklist puts footprint reduction before firewall configuration, rather than the other way around.
  3. Pick three lynis suggestions from a real scan and, for each, decide whether it's worth applying to a specific server you have in mind, explaining your reasoning either way.
  4. Write the single sentence you would want the person taking over this server from you to read first, summarizing the state of its hardening and what still needs attention.

Summary

Server hardening is not a distinct technique — it's the disciplined, ordered application of every control this module covered: reduce the footprint, lock down authentication, apply mandatory access control, apply the firewall with access confirmed first, add detection, automate patching, and verify the result independently with a tool like lynis. The order matters because each step can otherwise interfere with — or lock you out of — the next, and the process itself needs to repeat on a schedule, not run once at provisioning and be forgotten.

References