A freshly installed Ubuntu server is usable but not hardened. This guide is the exact checklist I run through on every new Ubuntu 24.04 LTS VPS before putting it into production: SSH key-only on a custom port, UFW with minimal rules, fail2ban against brute-force, automatic updates, AppArmor enabled, auditd for traceability, final verification. 30 copy-paste commands in the exact order, each followed by a “this works” verification. Plan 30 to 45 minutes total. The server emerges capable of weathering automated attack traffic without breaking a sweat.
The 10-step checklist
- First root access and admin user creation
- Initial system updates
- SSH hardening (keys, custom port, no root login)
- UFW firewall with minimal rules
- fail2ban against SSH brute-force
- Automatic updates (unattended-upgrades)
- AppArmor in enforce mode
- Audit logs with auditd
- Time synchronisation and NTP
- Final verification and snapshot
- FAQ
First root access and admin user creation
Log in as root once, create a non-privileged user for daily admin work, add them to the sudo group.
# On your local machine ssh root@YOUR_IP # On the server (as root) adduser admin # create the admin user usermod -aG sudo admin # add to sudo group mkdir -p /home/admin/.ssh chmod 700 /home/admin/.ssh cp /root/.ssh/authorized_keys /home/admin/.ssh/ chown -R admin:admin /home/admin/.ssh
Initial system updates
A freshly provisioned server typically contains 20 to 60 outdated packages. Patching them before any other operation avoids installing dependencies on vulnerable bases.
apt update apt upgrade -y apt dist-upgrade -y apt autoremove -y apt autoclean
If a “Pending kernel upgrade” message appeared, reboot the server (reboot) before moving to the next step.
SSH hardening (keys, custom port, no root login)
Three coordinated changes to /etc/ssh/sshd_config: deny direct root login, disable password authentication, and move the default port (22) to a less-scanned high port.
# Backup the config before editing cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak # Edit the config nano /etc/ssh/sshd_config # Lines to set or add: Port 2222 # custom port (pick any 1024-65535) PermitRootLogin no # no more direct root login PasswordAuthentication no # SSH keys only PubkeyAuthentication yes MaxAuthTries 3 # 3 attempts max per connection ClientAliveInterval 300 # auto-disconnect after 5 min ClientAliveCountMax 2 LoginGraceTime 30 AllowUsers admin # restrict to the admin user only
Validate the syntax before restarting the service, otherwise you lose access:
sshd -t # No output means OK; if there is one, fix it before continuing # Restart SSH systemctl restart ssh
# From your local machine, in a new terminal ssh admin@YOUR_IP -p 2222
UFW firewall with minimal rules
UFW (Uncomplicated Firewall) is Ubuntu’s wrapper around nftables/iptables. Principle: everything is denied by default, you explicitly open the ports you need.
apt install -y ufw ufw default deny incoming ufw default allow outgoing # Open the custom SSH port (mandatory before enabling) ufw allow 2222/tcp comment 'SSH custom port' # Open application ports as needed ufw allow 80/tcp comment 'HTTP' ufw allow 443/tcp comment 'HTTPS' # Enable the firewall ufw enable # answer y
For servers with a remote database (Postgres, MySQL, Redis), do not open ports 5432, 3306, 6379 to the internet: use an SSH tunnel or a WireGuard VPN from the application consuming the database.
fail2ban against SSH brute-force
Even with SSH key-only, bots will try connection attempts at scale (every failure consumes CPU and pollutes the logs). fail2ban automatically blocks IPs that fail N times in M minutes.
apt install -y fail2ban # Create the local config (jail.local takes precedence over jail.conf) cat > /etc/fail2ban/jail.local <<'EOF' [DEFAULT] bantime = 3600 findtime = 600 maxretry = 3 ignoreip = 127.0.0.1/8 ::1 [sshd] enabled = true port = 2222 backend = systemd EOF systemctl enable --now fail2ban
To unban an IP that was blocked by mistake: fail2ban-client set sshd unbanip 1.2.3.4.
Automatic updates (unattended-upgrades)
Ubuntu security patches ship on average 2 to 3 times per week. Applying them automatically (while avoiding major version upgrades that can break things) is the best effort/impact security measure.
apt install -y unattended-upgrades apt-listchanges # Enable Ubuntu's default config dpkg-reconfigure -plow unattended-upgrades # Answer Yes to the question # Verify the config file nano /etc/apt/apt.conf.d/50unattended-upgrades
In 50unattended-upgrades, verify that security sources are enabled:
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";
};
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "03:00";
AppArmor in enforce mode
AppArmor is Ubuntu’s Mandatory Access Control: it restricts each service to a profile of allowed files and syscalls. On Ubuntu 24.04 it is installed by default, but some profiles run in “complain” mode (alert only). We switch them to “enforce”.
apt install -y apparmor apparmor-utils apparmor-profiles apparmor-profiles-extra # Current status aa-status # Switch all profiles to enforce aa-enforce /etc/apparmor.d/*
If a service breaks after enabling enforce mode, check the logs (journalctl -u apparmor) and either adjust the offending profile or temporarily switch it back to complain: aa-complain /etc/apparmor.d/usr.sbin.service-name.
Audit logs with auditd
auditd records sensitive system events (reads of critical files, sudo commands, config modifications). Essential for answering questions like “who modified /etc/passwd yesterday at 2 pm?”.
apt install -y auditd audispd-plugins # Minimal rules: watch critical files cat > /etc/audit/rules.d/hardening.rules <<'EOF' # User account modifications -w /etc/passwd -p wa -k user_modification -w /etc/shadow -p wa -k user_modification -w /etc/group -p wa -k user_modification -w /etc/sudoers -p wa -k sudoers_modification -w /etc/sudoers.d/ -p wa -k sudoers_modification # SSH modifications -w /etc/ssh/sshd_config -p wa -k ssh_config # Abnormal network egress -a always,exit -F arch=b64 -S socket -F a0=10 -k network_socket # Lock the rules (no modification without reboot) -e 2 EOF systemctl enable --now auditd systemctl restart auditd
To inspect events: ausearch -k user_modification or aureport -au.
Time synchronisation and NTP
A server whose clock drifts by more than a few seconds creates two problems: its TLS certificates may appear invalid (rejection of outbound HTTPS connections), and log correlation with other machines becomes impossible. Ubuntu 24.04 uses systemd-timesyncd by default.
timedatectl set-timezone Europe/Paris # or your time zone timedatectl set-ntp true # Verify timedatectl status
Final verification and snapshot
Before considering the server production-ready, batch of final checks in one block:
# Consolidated checks (";" separator so everything runs even if one command fails)
echo "=== SSH ===" ; grep -E "^(Port|PermitRoot|PasswordAuth)" /etc/ssh/sshd_config
echo "=== UFW ===" ; ufw status numbered | head -10
echo "=== fail2ban ===" ; fail2ban-client status sshd
echo "=== unattended-upgrades ===" ; systemctl is-active unattended-upgrades
echo "=== AppArmor ===" ; aa-status | head -3
echo "=== auditd ===" ; systemctl is-active auditd
echo "=== Updates ===" ; apt list --upgradable 2>/dev/null | wc -l
echo "=== Uptime ===" ; uptime
If every output is consistent with expectations, take a server snapshot at your provider (OVH “Backup snapshot”, Hetzner “Snapshot”, AWS “AMI”, Scaleway “Snapshot”). This gives you a clean rollback point in case of later mishaps.
For production-critical servers, then add uptime monitoring (SecurityWatch or UptimeRobot) and application supervision (Netdata, Grafana Cloud Free, or Prometheus + node_exporter).
Monitor security across multiple servers?
SecurityWatch tracks uptime, TLS certificate, HTTP headers and configuration drift across multiple targets from your browser, with Slack or Discord webhook alerts. Free, browser-based, localStorage watchlist.
Routine maintenance after hardening
The initial hardening only lasts if maintenance follows. The minimum cadence on an Ubuntu 24.04 LTS server in 2026: weekly check of fail2ban-client status sshd and journalctl -p err -b; monthly check of aa-status, ufw status and apt list --upgradable; quarterly check of /etc/passwd (presence of unexpected users), of /etc/cron.d/ (cron jobs added without your consent) and of last -n 50 (recent logins). A deviation on any of these signals “investigate within 24 hours”.
FAQ
Why change the SSH port if I am already using keys?
Changing the port does not replace key authentication, it complements it. Bots that scan the entire IPv4 space concentrate 90 percent of their attempts on port 22; moving the service to a high port drastically reduces log noise (from 10,000 attempts/day to 5-10) and the CPU sshd consumes rejecting them. It is security through obscurity in the strict sense, but combined with keys it is free and effective.
UFW or nftables directly, which to choose?
UFW for 95 percent of cases: readable config, simple syntax, sufficient for a standard application server. Use nftables directly when you need complex rules (port knocking, GeoIP, fine rate limiting) that UFW cannot express. You can also write raw rules in /etc/ufw/before.rules to extend UFW without replacing it.
Can automatic updates break my server?
By default, unattended-upgrades only touches the -security sources (critical patches, never major versions). The breakage risk is very low on an LTS. For production-critical servers, only disable the automatic reboot (Automatic-Reboot "false") and schedule the reboot manually during your maintenance window.
AppArmor or SELinux?
On Ubuntu, AppArmor by default. SELinux is feasible but requires disabling AppArmor first and rewriting profiles, which does not bring net gain for most cases. SELinux fits on RHEL/CentOS Stream/Rocky Linux where it is the native integrated option.
Should I encrypt the disks of a VPS?
With LUKS on a VPS where you do not control the hypervisor, the benefit is limited: your key must be stored somewhere (often in RAM decrypted at boot), which does not prevent a datacenter operator with storage access from recovering the data. Disk encryption is more relevant on bare-metal hardware or on secondary data volumes via dm-crypt + external key.
How do I audit the server 6 months after hardening?
Three free tools cover recurring audits. Lynis (apt install lynis; lynis audit system) gives a hardening score with a list of gaps. OpenSCAP with the “Standard System Security Profile for Ubuntu” profile for regulated environments. chkrootkit and rkhunter for rootkit detection. Run Lynis every quarter, the others when there is suspicion.













