Ubuntu 24.04 LTS Hardening Checklist: Verify Every Step
Harden Ubuntu 24.04 LTS on a single VPS with SSH keys, firewall rules, security updates, restricted access, and recovery checks you can verify yourself.

On this page
- Prove you can reconnect before changing access
- Disable SSH passwords and direct root login
- Allow SSH before enabling the firewall
- Enable security updates and choose a reboot policy
- Inspect listeners and confirm AppArmor enforcement
- Separate administrative access from agent access
- Retain useful logs and verify an alert reaches you
- Schedule a restore rehearsal and complete a controlled reboot
- FAQ
You disable password login, close your terminal, and discover that your SSH key never worked. Start hardening Ubuntu 24.04 LTS by proving a separate key-based login and console recovery path. Then restrict SSH, enable a firewall, configure security updates, and review service permissions. Verify each change through effective configuration and fresh connections, and finish with a restore rehearsal before trusting the server with production data.
Prove you can reconnect before changing access
Your existing SSH session still works, but a new terminal returns permission denied after an authentication change. Prevent that lockout by proving key-based access before editing SSH, and keep the original session open until every access check passes. Open your VPS console now and confirm that it offers a usable recovery path.
Use a normal account with working sudo access on Ubuntu 24.04 LTS. The laptop examples call that account deploy and use the documentation address 203.0.113.10; replace those values with your account and server address. If you still depend on root login, complete the account and key preparation in our first-hour Ubuntu server setup guide before continuing.
Run this connection check from your laptop using the key you intend to keep. Disabling connection sharing forces a fresh authentication attempt instead of reusing an existing authenticated connection. A private-key passphrase prompt is acceptable; falling back to the server account password is deliberately disabled.
ssh -o ControlMaster=no -o ControlPath=none -o PreferredAuthentications=publickey -o PasswordAuthentication=no -o KbdInteractiveAuthentication=no [email protected]Install the utilities used below from the server session. Authenticate sudo before beginning if your account requires a password. These commands assume a fresh VPS; on an existing production machine, first record which applications must remain reachable.
sudo apt-get update
sudo env DEBIAN_FRONTEND=noninteractive apt-get install -y openssh-server ufw unattended-upgrades apparmor-utils
sudo /usr/sbin/sshd -tsshd -t exits successfully without diagnostic output. Resolve package or SSH configuration errors before changing access.Disable SSH passwords and direct root login
Once a fresh key-based login works, remove password authentication and direct root access. Ubuntu reads SSH configuration snippets near the beginning of its main configuration, and the first value generally wins. An apparently correct setting can therefore lose to an earlier snippet, as explained in Ubuntu's OpenSSH documentation.
Save a protected copy of the existing configuration before editing it. The command below preserves an existing backup rather than replacing it on a repeat run. Keep its location in your recovery notes so you can restore access through the console.
sudo cp -an /etc/ssh /root/ssh-before-hardening/root/ssh-before-hardening/sshd_config exists before editing the live configuration.Save this configuration as /etc/ssh/sshd_config.d/00-local-hardening.conf, owned by root and writable only by root. Inspect earlier snippets and any Match sections for conflicting authentication rules. This baseline assumes public-key authentication without an interactive additional authentication method.
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin noValidate the configuration before reloading the running service. A reload keeps your recovery session available while new connections use the updated settings. If validation fails, correct the configuration before attempting another reload.
sudo /usr/sbin/sshd -t && sudo systemctl reload ssh.service
sudo /usr/sbin/sshd -T | grep -E '^(pubkeyauthentication|passwordauthentication|kbdinteractiveauthentication|permitrootlogin) 'pubkeyauthentication yes, passwordauthentication no, kbdinteractiveauthentication no, and permitrootlogin no. Repeat the laptop connection check successfully before closing your recovery session.If you use conditional SSH rules, inspect the effective configuration for the actual account and connecting address with sshd -T -C. Treat the global output as incomplete evidence for those accounts. Our SSH hardening guide for servers with agent keys covers separating human access from automation.
Allow SSH before enabling the firewall
A database bound to a public interface can become reachable before you notice it. Start with incoming connections denied and explicitly allow SSH. UFW supplies a manageable host firewall, but installation alone does not activate it; see Ubuntu's firewall documentation.
The following commands allow the standard SSH service and, when running through SSH, the destination port of your current connection. Keep outbound traffic allowed for this baseline so package retrieval and application dependencies continue working. If you run these commands from a console with a custom SSH port, add that port's allow rule before enabling UFW.
sudo ufw allow OpenSSH
if [ -n "${SSH_CONNECTION:-}" ]; then
hardening_ssh_port="${SSH_CONNECTION##* }"
sudo ufw allow "${hardening_ssh_port}/tcp"
fi
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw --force enable
sudo ufw status verboseStatus: active, a deny policy for incoming traffic, and an allow rule for your SSH port. Open another fresh SSH connection successfully; an existing session alone does not prove that new connections are allowed.For a public web server, allow HTTP and HTTPS before directing users to it. Skip this block if the machine has no public web service. A firewall exception permits traffic but does not create a listener.
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw status verboseInspect any provider firewall separately, because a host rule cannot override an upstream block. Review both address families when the VPS has multiple public addresses. On an existing server, audit old allow rules too: changing the default policy does not remove them.
Verify exposure from your laptop, with Nmap installed, against your own server. Repeat against every public address and inspect container port mappings separately if you deploy containers. Record the intended public services before interpreting the scan.
nmap -Pn -sT -p- 203.0.113.10Enable security updates and choose a reboot policy
A hardened configuration still leaves you exposed when vulnerable packages stay installed. Refresh package information and apply pending upgrades during a maintenance window. Package upgrades can restart services, so check application health afterward even when SSH stays connected.
sudo apt-get update
sudo env DEBIAN_FRONTEND=noninteractive NEEDRESTART_MODE=a apt-get -y -o Dpkg::Options::=--force-confdef -o Dpkg::Options::=--force-confold upgrade
sudo dpkg --auditdpkg --audit produces no report of incomplete package installation. Investigate held-back packages separately and confirm that your application still answers its normal health check.Enable daily automatic package refreshes and unattended upgrades while keeping automatic rebooting disabled. Retain the distribution's security origins unless you have reviewed a specific additional repository. Adding a repository does not automatically include it in unattended updates, as Ubuntu's automatic-update documentation explains.
sudo tee /etc/apt/apt.conf.d/99-local-hardening >/dev/null <<'EOF'
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
Unattended-Upgrade::Automatic-Reboot "false";
EOF
sudo systemctl enable --now apt-daily.timer apt-daily-upgrade.timer
apt-config dump | grep -E 'APT::Periodic::(Update-Package-Lists|Unattended-Upgrade)|Unattended-Upgrade::Automatic-Reboot'
sudo unattended-upgrade --dry-run --debugAssign someone responsibility for pending reboots instead of leaving them indefinitely. Check for /var/run/reboot-required after maintenance and arrange a console-backed restart when it exists. Application runtimes installed outside APT need their own update procedure; this package policy does not maintain them.
Inspect listeners and confirm AppArmor enforcement
A service you forgot about is still part of your attack surface. Inspect listening sockets and running services, then identify the owner and purpose of each listener. Pay particular attention to databases and development servers that should accept only local connections.
sudo ss -lntup
systemctl list-units --type=service --state=running --no-pagerDisable an unnecessary service only after checking what depends on it, then repeat the socket inspection. For services you need, change the application's bind address rather than relying entirely on a firewall. Avoid copying a generic service-removal script onto a VPS whose networking and recovery components you have not identified.
AppArmor limits what profiled applications can access, and Ubuntu normally loads it by default. Enforcement is per profile: complain mode records violations without blocking them. Use Ubuntu's AppArmor documentation to interpret the status before changing a profile.
sudo aa-statusaa-status reports that AppArmor is loaded and identifies profiles in enforce mode. Confirm that any application you intend to confine actually has an enforcing profile; loaded AppArmor alone does not establish application coverage.Run that check on the host, not inside an application container or restricted build environment. If a profile blocks legitimate work, inspect the denial and adjust the narrow rule involved. We leave unrelated kernel tuning alone until a workload-specific requirement justifies a change and a recovery plan exists.
Separate administrative access from agent access
An old deployment account with unrestricted sudo can undo every earlier restriction. Review privileged accounts and remove obsolete SSH keys after confirming who still uses them. Keep a human recovery identity separate from deployment automation so revoking automation does not also remove your administrative access.
getent passwd | awk -F: '$3 == 0 { print $1 }'
getent group sudo
sudo -l
sudo visudo -cvisudo -c reports valid syntax.Group membership is only part of the review: inspect direct grants in /etc/sudoers.d/ and check each automation account's effective permissions. A syntactically valid rule can still grant unrestricted control. Also inspect access to privileged service sockets and writable files that root executes.
We recommend Claude Code for assisted operations, beginning with inspection and proposed changes. Run it through a dedicated identity whose operating-system permissions enforce the intended scope. Read-only access is still sensitive when it includes application secrets, so expose only the files the task requires.
- Require human approval for SSH, firewall, account, and sudo policy changes.
- Keep recovery credentials and private SSH keys outside the agent's accessible files.
- Allow privileged actions only through narrowly scoped, root-owned commands whose inputs cannot expand into arbitrary execution.
- Review configuration diffs and run the relevant validator before applying a privileged change.
An approval prompt is a workflow control; account permissions determine what a compromised process can actually do. Avoid granting broad access to interpreters or editors through sudo. Use our production guardrails for AI agents when turning repeated maintenance tasks into approved operations.
Retain useful logs and verify an alert reaches you
A failed login record is useful only if it survives long enough for someone to investigate. Configure persistent journal storage with a bounded disk budget. The size below is an example limit for local logs; adjust it to your available space and investigation needs.
sudo mkdir -p /etc/systemd/journald.conf.d
sudo tee /etc/systemd/journald.conf.d/60-local-retention.conf >/dev/null <<'EOF'
[Journal]
Storage=persistent
SystemMaxUse=200M
EOF
sudo systemctl restart systemd-journald
sudo journalctl --flush
sudo journalctl --disk-usage
sudo journalctl -u ssh.service --since today --no-pagerSet an external availability check for your public application and route failures to a channel you actually read. Add notification for backup failures and overdue maintenance according to your monitoring setup. Trigger the monitoring service's test notification and acknowledge its arrival before treating the alert as operational.
We treat automated login bans as an optional follow-up once key-only SSH and exposure checks work. Choose additional tooling only when you can explain which events it will detect and how you will recover from a mistaken block. Our Fail2ban and CrowdSec comparison for a single server helps with that decision.
Schedule a restore rehearsal and complete a controlled reboot
Your next task is to restore the application somewhere isolated, using the backup you intend to trust. A backup job reporting success does not establish that the database can be opened or the application can start. Use a disposable machine or isolated environment that cannot send production messages or alter live data.
Keep recoverable copies outside the VPS and protect their credentials separately from the production account. Use the database's supported backup mechanism or a documented consistent snapshot procedure. Copying changing database files without coordinating consistency can leave you with an archive that cannot be restored.
- Restore application files and database contents into the isolated environment.
- Retrieve required secrets through your recovery process without copying them into chat or shell history.
- Start the application and verify a representative read and write against restored data.
- Record the recovery steps, any missing dependency, and who owns the next rehearsal.
Then book a maintenance window to verify that the hardened server survives a restart. Keep the provider console available and confirm that expected services are configured to start at boot. Run the reboot only when you are ready for the application interruption and SSH disconnection.
sudo systemctl rebootsudo journalctl -b -1 --no-pager can read the previous boot.Put the next restore rehearsal on your operations calendar before leaving this task. Assign an owner to any failed check and resolve access or recovery failures before depending on unattended maintenance. Repeat the exposure check whenever you add a service or change how it publishes ports.
FAQ
What should an Ubuntu server hardening checklist include?
Start with the controls that prevent loss of access and reduce public exposure, then cover ongoing maintenance and recovery. Each control needs evidence: a new authenticated connection, an effective configuration value, or a successful recovery action. A checklist that merely says to install security packages misses whether those packages are active and whether you can recover when a change fails.
Is Ubuntu 24.04 LTS secure enough for a production VPS?
It can be a suitable production base when you maintain the operating system and configure the workload carefully. The image name cannot tell you whether your database is public or an automation account has unrestricted privileges. Review the application and its deployment process alongside the host. Operating-system hardening cannot repair broken application authorization or protect secrets that the application exposes.
Should I change the SSH port when hardening Ubuntu?
We would spend the effort on reliable key authentication and account separation first. A different port adds another setting to maintain across clients, firewalls, and recovery notes. If you change it, keep the old path available until a fresh connection to the new listener succeeds. Check the actual listening socket as well as the SSH configuration before removing the old firewall rule.
Do I need Fail2ban if SSH passwords are disabled?
Decide based on an explicit operational need, such as repeated unwanted attempts that obscure useful logs. Automated bans introduce another access-control component that you must understand and recover from. They do not revoke a stolen valid key or restrict what an authenticated account can execute. We would complete authentication and privilege checks before adding a banning service to a small VPS.
Can Claude Code safely harden my Ubuntu server?
Claude Code can help inspect configuration and prepare changes, but its access should match the task. Use a dedicated account, review privileged modifications, and keep a separate human recovery path. For access-control changes, require the agent to explain the expected verification result before execution. Confirm the result yourself through a fresh connection rather than accepting a successful command exit as complete proof.
Put this guide in the bigger picture
See how this topic connects to the rest of your product. Our free field guide explains apps, infrastructure and AI, with checklists to help you make business decisions.






