ubuntu
Ubuntu Server 24.04 fresh-install hardening checklist
The exact steps we run on every new Ubuntu 24.04 host before any workload arrives — SSH, UFW, fail2ban, AppArmor, auditd, and the small details that actually matter.
16 mai 2026 · 10 min · par Sudhanshu K.
Ubuntu Server 24.04 fresh-install hardening checklist
A fresh Ubuntu 24.04 cloud image is not a hardened server. It's a perfectly reasonable starting point that ships with sane defaults for a generic user, which is not the same thing as ready-for-production-with-traffic.
This is the exact checklist we apply to every new Ubuntu 24.04 host before any workload is allowed to land on it. We've run it on several thousand servers across AWS, GCP, Azure and DigitalOcean. Every item has a justification rooted in something we've actually seen attack a customer's box. Nothing here is theatre.
If you're standing up a new server right now, work through it top to bottom. It takes about 25 minutes by hand or seconds via the Ansible role we run as part of provisioning.
0. Before anything else: patch and snapshot
sudo apt update
sudo DEBIAN_FRONTEND=noninteractive apt -y upgrade
sudo apt -y autoremove
sudo rebootYes, even on a "fresh" image, there are usually 30-80 packages with available updates by the time the AMI/image is released. Patch first, snapshot the result, and that snapshot becomes your golden baseline.
1. SSH — the first and most important hardening
The single highest-traffic attack surface on any internet-facing Ubuntu box is sshd. Default config is way too permissive.
sudo install -o root -g root -m 0755 -d /etc/ssh/sshd_config.d
sudo tee /etc/ssh/sshd_config.d/00-edgeservers.conf > /dev/null <<'EOF'
# Authentication
PermitRootLogin no
PasswordAuthentication no
PermitEmptyPasswords no
PubkeyAuthentication yes
ChallengeResponseAuthentication no
KbdInteractiveAuthentication no
UsePAM yes
# Session controls
X11Forwarding no
AllowTcpForwarding no
AllowAgentForwarding no
PermitUserEnvironment no
MaxAuthTries 3
MaxSessions 4
LoginGraceTime 30
ClientAliveInterval 300
ClientAliveCountMax 2
# Crypto — modern defaults, drops legacy algorithms
HostKeyAlgorithms ssh-ed25519,rsa-sha2-512,ecdsa-sha2-nistp256
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
# Access control
AllowGroups ssh-users
Banner /etc/issue.net
EOF
sudo sshd -t && sudo systemctl reload sshA few notes:
AllowGroups ssh-usersis the safety net. Add the explicit accounts that need SSH to this group; nobody else can log in even if their key is inauthorized_keys.- We drop RSA-2048 host key support but keep RSA-2048 user keys via
rsa-sha2-512— older clients still work, weak algorithms don't. MaxAuthTries 3plus fail2ban (below) gives you a quick ban for brute-forcers.
Also: regenerate host keys after first boot. Most cloud images bake host keys into the AMI, which is a small but real concern in shared-image scenarios:
sudo rm /etc/ssh/ssh_host_*
sudo dpkg-reconfigure openssh-server2. A non-root admin account with a hardware-backed key
sudo adduser --disabled-password --gecos "" deploy
sudo usermod -aG sudo,ssh-users deploy
sudo install -d -m 0700 -o deploy -g deploy /home/deploy/.ssh
sudo install -m 0600 -o deploy -g deploy /dev/stdin /home/deploy/.ssh/authorized_keys <<'EOF'
ssh-ed25519 AAAA...your-yubikey-resident-key... deploy@yubikey-prod-01
EOFIf you've still got root keys configured, remove them now. Disable root SSH explicitly (PermitRootLogin no above) and use sudo from your admin account.
For our managed fleet, every engineer's key is a resident key on a YubiKey 5 series. The phishing resistance of WebAuthn-backed SSH keys vs file-on-disk keys is the difference between "credentials in a public S3 bucket are a real incident" and "credentials on a public S3 bucket are mildly embarrassing." For 2026 this is the bar.
3. UFW — host firewall on every host
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw default deny routed
sudo ufw allow from 10.0.0.0/8 to any port 22 proto tcp comment 'SSH from VPC only'
sudo ufw allow 443/tcp comment 'HTTPS'
sudo ufw logging medium
sudo ufw --force enable
sudo ufw status verboseThe argument against host firewalls is "we have security groups upstream." The answer is that security groups get misconfigured weekly — we see it in every pen-test engagement we run — and UFW is your backstop. The cost of running UFW is essentially zero; the value the day someone fat-fingers a security group is enormous.
Restrict SSH to the management VPC CIDR, not 0.0.0.0/0, even if you think SSH is fine because it's key-only. There is no good reason for a production server to accept SSH from "any internet" in 2026.
4. fail2ban — for the bots that get past UFW
UFW handles the IPs you've blocked; fail2ban handles the ones you haven't yet. Even with key-only SSH, fail2ban reduces log noise dramatically and stops a small class of automated scanners from finding side-doors.
sudo apt install -y fail2ban
sudo tee /etc/fail2ban/jail.d/edgeservers.local > /dev/null <<'EOF'
[DEFAULT]
bantime = 24h
findtime = 10m
maxretry = 3
backend = systemd
banaction = ufw
ignoreip = 127.0.0.1/8 10.0.0.0/8
[sshd]
enabled = true
mode = aggressive
[sshd-ddos]
enabled = true
EOF
sudo systemctl enable --now fail2ban
sudo fail2ban-client statusbanaction = ufw makes fail2ban hand bans to UFW rather than the legacy iptables backend, which keeps everything observable through ufw status. mode = aggressive adds the patterns that catch the broader range of probing behaviour, not just failed auth attempts.
5. AppArmor — enforce, don't complain
Ubuntu 24.04 ships AppArmor enabled with a useful set of profiles. The default is fine, but verify state and ensure nothing's been quietly switched to complain:
sudo apt install -y apparmor-utils
sudo aa-status
sudo find /etc/apparmor.d/ -maxdepth 1 -type f -exec aa-enforce {} \;
sudo systemctl restart apparmoraa-status should show every loaded profile in enforce mode. Any profile in complain is logging policy violations to kern.log but not stopping anything — that's a developer mode, not a production mode.
For services you install later (nginx, postgres, your application), check whether they ship a profile and put it in enforce too:
sudo aa-enforce /etc/apparmor.d/usr.sbin.nginx
sudo aa-enforce /etc/apparmor.d/usr.bin.postgres6. auditd — visibility you'll regret not having
You don't have to read auditd logs every day. You do have to be able to read them after an incident, and that means having them collected before the incident:
sudo apt install -y auditd audispd-plugins
sudo tee /etc/audit/rules.d/edgeservers.rules > /dev/null <<'EOF'
# Identity changes
-w /etc/passwd -p wa -k identity
-w /etc/shadow -p wa -k identity
-w /etc/group -p wa -k identity
-w /etc/sudoers -p wa -k scope
-w /etc/sudoers.d/ -p wa -k scope
# Privileged exec
-a always,exit -F arch=b64 -S execve -F euid=0 -F auid>=1000 -F auid!=4294967295 -k root-cmd
# Network configuration
-w /etc/hosts -p wa -k network
-w /etc/network/ -p wa -k network
-w /etc/netplan/ -p wa -k network
# Time
-a always,exit -F arch=b64 -S adjtimex,settimeofday,clock_settime -k time
# Kernel module tampering
-w /sbin/insmod -p x -k modules
-w /sbin/rmmod -p x -k modules
-w /sbin/modprobe -p x -k modules
-a always,exit -F arch=b64 -S init_module,delete_module -k modules
# SSH config
-w /etc/ssh/sshd_config -p wa -k sshd
-w /etc/ssh/sshd_config.d/ -p wa -k sshd
# Make rules immutable until reboot
-e 2
EOF
sudo augenrules --load
sudo systemctl enable --now auditdShip the audit log to a central SIEM via syslog or the Splunk/Loki/Datadog agent. Auditd on the host alone is forensics; auditd into a SIEM is detection.
7. Disable unused services
# Disable services that aren't relevant to most servers
for svc in cups avahi-daemon ModemManager whoopsie bluetooth; do
sudo systemctl mask "$svc" 2>/dev/null || true
done
# Check what's listening
sudo ss -tlnpss -tlnp tells you exactly what's bound to network sockets. Anything you don't recognise — investigate now, not after an incident. The most common surprises on a fresh image are systemd-resolved on 127.0.0.53 (fine), and occasionally a stray snapd socket (also fine, but worth knowing about).
8. Kernel sysctls — sensible network defaults
sudo tee /etc/sysctl.d/99-edgeservers.conf > /dev/null <<'EOF'
# IP spoofing protection
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
# Ignore ICMP redirects
net.ipv4.conf.all.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
# SYN flood protection
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_syn_backlog = 2048
# Log martian packets
net.ipv4.conf.all.log_martians = 1
# Disable source routing
net.ipv4.conf.all.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0
# Restrict kernel pointer leaks
kernel.kptr_restrict = 2
kernel.dmesg_restrict = 1
# ASLR
kernel.randomize_va_space = 2
# Prevent core dumps for SUID
fs.suid_dumpable = 0
# ptrace scope
kernel.yama.ptrace_scope = 1
EOF
sudo sysctl --systemThe kernel.kptr_restrict = 2 and kernel.dmesg_restrict = 1 settings keep unprivileged users from seeing kernel pointers and the dmesg ring buffer — both used by local-privilege-escalation exploits to find ASLR offsets.
9. Unattended-upgrades — daily security patches
sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgradesThe detailed config — what to allowlist, what to blocklist, how to handle reboots — is covered in the first article in this series. At minimum, ensure the -security pocket is enabled and the daily timer is on.
10. Ubuntu Pro and Livepatch
sudo pro attach <your-token>
sudo pro enable livepatch esm-infra esm-apps usgIf you have the licence, attach it now. Livepatch covers kernel CVEs without reboot, ESM extends the security pocket to ~25,000 universe packages, and usg is the CIS audit tool. Detailed treatment of all three is in the earlier articles in this series.
11. The verification pass
# SSH posture
sudo ssh-audit -L
# CIS audit
sudo usg audit cis_level1_server
# Quick AppArmor sanity check
sudo aa-status | head -20
# Open ports
sudo ss -tlnp
# Pending reboots
ls /var/run/reboot-required 2>/dev/null && echo "REBOOT NEEDED"ssh-audit is the single best tool for proving your sshd config does what you think it does. It'll tell you which algorithms are still enabled, which clients you've locked out, and what to remove next.
For a fresh 24.04 host that's been through this checklist, usg audit cis_level1_server should come out at 95%+ pass rate, with the remaining 5% being controls you deliberately skipped (compiler presence, password policy, etc.) — and you should have those exceptions documented somewhere version-controlled.
What's still missing
This checklist is a baseline. It is not the whole story. Things that come next, depending on workload:
- EDR / file integrity monitoring — AIDE, Wazuh, or a commercial agent. For high-value hosts, non-optional.
- Centralized authentication — SSSD against your IdP for fleets above ~10 hosts.
- TLS everywhere — including internal network paths, not just the public edge.
- Backup and restore drills — actually-tested, not just configured.
- Specific application hardening — nginx, Postgres, MySQL, Redis each have their own playbooks.
But everything above gets you to a state where the next attacker scan that hits your IP gives up after about three seconds, which is the goal.
If you'd rather not run this checklist by hand on every new server — we run it automatically on every Ubuntu host we provision, monitor it continuously, and respond when something changes. That's the managed Ubuntu tier in one paragraph. Get in touch if you want a hand.
Sudhanshu K. leads cybersecurity engagements at EdgeServers (RemotIQ Pty Ltd, ABN 91 682 628 128). She has hardened more Ubuntu servers than she has hot dinners and considers ssh-audit mandatory reading.