A fresh Ubuntu 24.04 server should be hardened before it hosts an application or stores production data. Server hardening reduces unnecessary access, limits exposed services, keeps security patches current, and gives you a recovery path when a configuration change goes wrong.
In this tutorial, you will create a sudo user, configure SSH key authentication, apply SSH settings through a drop-in file, enable UFW, configure automatic security updates, review listening services and user accounts, add optional Fail2Ban protection, and prepare backups and console recovery.
Step 1 — Update the Server and Check for a Required Reboot
Refresh package metadata and install available updates:
sudo apt update
sudo apt upgrade -y
Check whether Ubuntu recommends a reboot:
if [ -f /var/run/reboot-required ]; then
cat /var/run/reboot-required
else
echo "No reboot required"
fi
Reboot during a safe maintenance window when required:
Reconnect after the server comes back online.
Step 2 — Create a Non-Root Sudo User
Create a regular administrative user. Replace deploy with your preferred username:
sudo adduser deploy
sudo usermod -aG sudo deploy
Verify group membership:
The output should include the sudo group. Daily administration through a regular account reduces the chance that an accidental command runs with unrestricted privileges.
Before disabling root or password-based SSH access, confirm that this account can use sudo:
The expected result is:
Step 3 — Install and Verify an SSH Public Key
Generate an Ed25519 key on your local computer when you do not already have one:
ssh-keygen -t ed25519 -C "deploy-access"
Copy the public key to the new user:
ssh-copy-id deploy@your_server_ip
Open a second terminal and verify the account can log in using the key:
ssh deploy@your_server_ip
Keep the original session open. Do not disable password authentication or root login until a separate key-based session works.
For detailed key generation, agents, permissions, and troubleshooting, follow How to Set Up SSH Keys on Ubuntu 24.04.
Step 4 — Harden SSH Through a Drop-In File
Ubuntu's OpenSSH configuration supports files under /etc/ssh/sshd_config.d/. A dedicated drop-in keeps local hardening changes separate from the package-managed main configuration.
Create the file:
sudo nano /etc/ssh/sshd_config.d/99-raff-hardening.conf
Add:
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
MaxAuthTries 3
LoginGraceTime 30
X11Forwarding no
Validate the complete SSH configuration before applying it:
No output means the syntax test passed. Check the effective values:
sudo sshd -T | grep -E '^(permitrootlogin|passwordauthentication|kbdinteractiveauthentication|pubkeyauthentication|maxauthtries|logingracetime|x11forwarding) '
Reload SSH without terminating established sessions:
sudo systemctl reload ssh
Open another new terminal and test key-based access again:
ssh deploy@your_server_ip
Test that password-only authentication is rejected:
ssh -o PreferredAuthentications=password \
-o PubkeyAuthentication=no \
deploy@your_server_ip
Do not add an AllowUsers directive until every required administrative and automation account has been identified. An incomplete allowlist is a common cause of lockouts.
Step 5 — Enable a Deny-by-Default UFW Policy
Determine the active SSH port:
sudo sshd -T | awk '$1 == "port" {print $2}'
For the standard OpenSSH profile, allow SSH before enabling the firewall:
sudo ufw allow OpenSSH
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw enable
Verify the rules:
Test another SSH connection before closing the current session. Open application ports only when the service is installed and intentionally public.
For IPv6, source restrictions, logging, and Docker behavior, use the complete UFW firewall tutorial.
Install the unattended-upgrades package:
sudo apt install -y unattended-upgrades
Create or review the periodic update configuration:
sudo nano /etc/apt/apt.conf.d/20auto-upgrades
Use:
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
Check the allowed origins in:
sudo nano /etc/apt/apt.conf.d/50unattended-upgrades
Test the configuration without installing packages:
sudo unattended-upgrade --dry-run --debug
Review recent activity:
sudo journalctl -u unattended-upgrades --since '7 days ago' --no-pager
Do not enable automatic reboots blindly on a production system. Decide whether reboots should happen automatically, through a maintenance window, or through your deployment process.
Step 7 — Review Listening Ports and Enabled Services
List listening TCP and UDP sockets:
List services enabled at boot:
systemctl list-unit-files --type=service --state=enabled
For each listening service, confirm:
- It is required.
- It is bound to the intended interface.
- Its firewall rule is necessary.
- Its authentication and update strategy are understood.
Disable and stop an unused service only after confirming it is not required:
sudo systemctl disable --now service-name
Do not expose database, cache, or internal admin ports globally simply because an application needs them locally.
Step 8 — Review Accounts and Sudo Access
List accounts with interactive shells:
awk -F: '$7 !~ /(nologin|false)$/ {print $1, $6, $7}' /etc/passwd
Review members of the sudo group:
Inspect authorized SSH keys for an administrative user:
sudo nl -ba /home/deploy/.ssh/authorized_keys
Remove stale keys and accounts when staff, contractors, or automation no longer require access. Avoid sharing one private SSH key between several people because it makes revocation and attribution harder.
Step 9 — Add Fail2Ban Only When It Fits the Threat Model
SSH keys, disabled password authentication, and a restrictive firewall are the primary controls. Fail2Ban is optional and can reduce repeated authentication noise or protect other log-based services.
Install it:
sudo apt install -y fail2ban
Create an SSH jail override:
sudo nano /etc/fail2ban/jail.d/sshd.local
Add:
[sshd]
enabled = true
backend = systemd
findtime = 10m
maxretry = 5
bantime = 1h
Enable the service and validate the jail:
sudo systemctl enable --now fail2ban
sudo fail2ban-client status sshd
Fail2Ban is not a replacement for key-only authentication, an upstream firewall, or DDoS mitigation. Review its logs and ban behavior before using aggressive settings.
Step 10 — Verify Time Synchronization and Logs
Correct system time is important for package validation, TLS, authentication logs, and incident review.
Check time synchronization:
Review recent SSH events:
sudo journalctl -u ssh --since '24 hours ago' --no-pager
Review high-priority system messages:
sudo journalctl -p warning --since '24 hours ago' --no-pager
A hardened server still needs monitoring. Establish a routine for reviewing failed services, disk usage, authentication events, and unexpected listening ports.
Step 11 — Prepare Backups and Recovery Access
Security changes can cause operational failures even when they reduce attack surface. Before hosting production data:
- Enable scheduled backups.
- Keep at least one recovery copy outside the VM.
- Test restoring the application and database.
- Confirm you can access the Raff browser console when SSH is unavailable.
- Document the active SSH port, firewall rules, administrative accounts, and recovery steps.
Explore Raff Data Protection for VM backup and recovery options. A backup is not proven until you have restored it successfully.
Step 12 — Run the Final Hardening Checklist
Check SSH syntax and effective settings:
sudo sshd -t
sudo sshd -T | grep -E '^(permitrootlogin|passwordauthentication|kbdinteractiveauthentication|pubkeyauthentication) '
Check the firewall and listening ports:
sudo ufw status numbered
sudo ss -tulpn
Check failed services and pending updates:
systemctl --failed
sudo apt update
apt list --upgradable 2>/dev/null
Check whether a reboot is required:
test -f /var/run/reboot-required && cat /var/run/reboot-required || echo "No reboot required"
Repeat these checks after major application deployments because new packages and containers can change the server's exposed services.
Conclusion
You now have an Ubuntu 24.04 server with a non-root sudo account, verified SSH keys, key-only remote access, a deny-by-default UFW policy, automated security updates, service and account auditing, and a defined recovery path.
Raff Linux VMs start at $8.49 per month with 2 vCPU, 2 GB RAM, 40 GB NVMe storage, and 3 Gbps unmetered bandwidth. Hardening remains an ongoing operational process: keep packages current, remove stale access, review listening services, and test recovery regularly.