Hardening Ubuntu 24.04 in 2026: the controls that actually matter

I've been running and re-running Ubuntu hardening playbooks since 18.04. Most of what's published online is still a copy of a copy of a 16.04 CIS checklist, with password minlen 14 and a dozen sysctl knobs.

Ubuntu 24.04 Hardening Playbook 2026

I've been running and re-running Ubuntu hardening playbooks since 18.04. Most of what's published online is still a copy of a copy of a 16.04 CIS checklist, with password minlen 14 and a dozen sysctl knobs that haven't moved the needle since systemd-resolved became the default.

This is the short version of the 24.04 playbook I actually use on production. The full version with the Ansible roles, Lynis baseline, and benchmark numbers is on my site (linked at the bottom).

1. Stop trusting unattended-upgrades exit codes

On 24.04, needrestart 3.6 plus phased updates will silently leave kernel meta-packages in a kept back state. The unit reports success. Your boxes drift.

Minimal fix in /etc/apt/apt.conf.d/50unattended-upgrades:

Unattended-Upgrade::Allow-Phased-Updates "true";
Unattended-Upgrade::Allowed-Origins {
    "${distro_id}:${distro_codename}";
    "${distro_id}:${distro_codename}-security";
    "${distro_id}ESMApps:${distro_codename}-apps-security";
    "${distro_id}ESM:${distro_codename}-infra-security";
};

Then add an assertion you can actually fail CI on:

#!/usr/bin/env bash
set -euo pipefail
running=$(uname -r)
latest=$(apt-cache policy linux-image-generic | awk '/Candidate:/ {print $2}')
if ! dpkg -l "linux-image-${running}" >/dev/null 2>&1; then
  echo "Running kernel ${running} no longer installed — reboot pending"; exit 1
fi
echo "OK: kernel ${running}, candidate ${latest}"

2. AppArmor: enforce, don't complain

24.04 ships several profiles in complain mode out of the box. aa-status will happily report them as loaded. Anything in complain is documentation, not enforcement.

sudo aa-enforce /etc/apparmor.d/usr.bin.man
sudo aa-enforce /etc/apparmor.d/usr.sbin.cups-browsed
sudo aa-enforce /etc/apparmor.d/snap.*

Watch out for snap refreshes resetting profiles. I run an apparmor_parser -r sweep nightly via systemd timer.

3. nftables that don't fight Docker

Docker 25 on 24.04 uses iptables-nft by default, so you can finally have one ruleset. The trick is putting your filter rules in a separate table so Docker's chains don't get flushed when you reload.

table inet filter {
    chain input {
        type filter hook input priority 0; policy drop;
        ct state established,related accept
        iif lo accept
        tcp dport { 22, 80, 443 } accept
        ip protocol icmp accept
        log prefix "nft-drop: " limit rate 5/minute
    }
}

Leave Docker's nat and filter tables alone. nft list ruleset after docker compose up should still show both your inet filter and Docker's ip filter DOCKER-USER.

4. auditd rules that won't drown you

The CIS auditd ruleset generates roughly 8x more events than you need on a busy host. I strip it down to four categories: identity, time, privileged commands, and module loading.

-w /etc/passwd -p wa -k identity
-w /etc/shadow -p wa -k identity
-w /etc/sudoers -p wa -k identity
-w /etc/sudoers.d/ -p wa -k identity
-a always,exit -F arch=b64 -S init_module -S finit_module -S delete_module -k modules
-a always,exit -F arch=b64 -S settimeofday -S clock_settime -k time

Measured event volume on a typical app host: ~120 events/hour vs ~1,400 with the stock CIS ruleset.

5. Kernel lockdown is the cheap win

echo 'GRUB_CMDLINE_LINUX="lockdown=integrity"' | sudo tee /etc/default/grub.d/99-lockdown.cfg
sudo update-grub

This blocks unsigned module loading and kexec without breaking your average userland. If you're on ZFS or running out-of-tree drivers, test in staging first — it will refuse to load them unless they're signed against the platform keyring.

What I left out

  • pam_pwquality complexity knobs (use a password manager, move on)
  • Disabling IPv6 (just configure it)
  • tcp_wrappers (gone in 24.04, finally)

Full version

The full playbook with the Ansible roles, the Lynis baseline diff (94 -> 78 hardening index on a default install), and the actual benchmark output from oscap is on linuxsecureops.com:

https://linuxsecureops.com/article/ubuntu-24-04-hardening-playbook-2026

If you've found something the CIS benchmark gets wrong on 24.04 specifically, I'd genuinely like to hear it — the playbook is versioned in git and I update it when the noble-updates pocket changes behaviour.

Article changelog (1)
  • — Expanded with TL;DR, table of contents, or additional sections
Felix Lindqvist
About the Author Felix Lindqvist

Penetration tester and OSCP holder. Reverse engineers misconfigured servers for a living and writes about what he finds.