Hardening a Linux Server
When you spin up a fresh Ubuntu server and give it a public IP address, it is exposed to the entire internet within minutes. Automated bots constantly scan for open ports and try to log in with common passwords. “Hardening” means reducing the ways an attacker can get in (the “attack surface”) by closing unused doors, removing weak credentials, and keeping software patched. This page is a practical runbook you can follow top-to-bottom on any new Ubuntu 22.04 or 24.04 LTS server before you put real work on it.
Do this first, on a fresh server. It is far easier to harden a server before it has users and services than to retrofit security later. Run through this checklist as step one of every new machine.
Step 1: Patch everything
Most successful attacks exploit known bugs that already have fixes available. Before anything else, install the latest security patches. The apt tool is Ubuntu’s package manager (the program that installs and updates software).
sudo apt update
sudo apt upgrade -y
sudo reboot
Output:
Reading package lists... Done
Building dependency tree... Done
12 packages can be upgraded. Run 'apt list --upgradable' to see them.
...
Setting up linux-image-6.8.0-45-generic ...
A reboot is needed when the kernel (the core of the operating system) or core libraries are updated. After it comes back, you are running the latest code.
Step 2: Create a non-root sudo user
The root user can do anything on a system with no limits, which makes it the prize every attacker wants. You should never log in as root day-to-day. Instead, create a normal user and grant it sudo (a tool that lets a regular user run individual commands as an administrator when needed).
sudo adduser deploy
sudo usermod -aG sudo deploy
The first command creates a user named deploy and prompts you for a password. The second adds them to the sudo group. Test it by switching to the new user and running a privileged command.
su - deploy
sudo whoami
Output:
[sudo] password for deploy:
root
It printed root, which confirms deploy can escalate when needed. From now on, log in as deploy, not root. See Least privilege & sudo for the full reasoning.
Step 3: Switch SSH to key-only login
SSH (Secure Shell, the encrypted protocol you use to log in remotely) is the front door of your server. Passwords can be guessed by bots; cryptographic keys effectively cannot. The plan: copy your public key to the server, then turn off password logins entirely.
On your local machine, generate a key if you do not have one, then copy it up:
ssh-keygen -t ed25519 -C "[email protected]"
ssh-copy-id deploy@your-server-ip
Now log in once with the key to confirm it works, then edit the SSH server config on the server:
sudo nano /etc/ssh/sshd_config
Set these three lines (uncomment and change them if they already exist):
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
Apply the change by restarting the SSH service. systemd is Ubuntu’s service manager (the program that starts, stops, and supervises background services).
sudo systemctl restart ssh
Keep your current session open while you test a new login from a second terminal. If you lock yourself out, the open session is your rescue line. Full details live on SSH security.
Step 4: Turn on a firewall with ufw
A firewall decides which network ports are reachable from outside. Ubuntu ships with ufw (Uncomplicated Firewall), a friendly front-end. The safe default is “deny everything coming in, allow everything going out,” then open only the ports you actually use.
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose
Output:
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
To Action From
-- ------ ----
OpenSSH ALLOW IN Anywhere
80/tcp ALLOW IN Anywhere
443/tcp ALLOW IN Anywhere
Ports 80 and 443 are for HTTP and HTTPS web traffic; only open them if this box serves a website. More on rules and ranges in Firewall configuration.
Step 5: Block brute-force attacks with fail2ban
Even with key-only SSH, bots will hammer your login port. fail2ban watches log files and automatically bans IP addresses that fail repeatedly.
sudo apt install fail2ban -y
sudo systemctl enable --now fail2ban
Create a local override so updates do not wipe your settings. The [sshd] block below bans an IP for one hour after 5 failed attempts:
# /etc/fail2ban/jail.local
[sshd]
enabled = true
maxretry = 5
bantime = 1h
findtime = 10m
sudo systemctl restart fail2ban
sudo fail2ban-client status sshd
See Brute-force protection with fail2ban for jails covering Nginx and other services.
Step 6: Disable unused services
Every running service is a potential entry point. List what is listening on the network and shut down anything you do not need.
sudo ss -tulpn
Output:
Netid State Local Address:Port Process
tcp LISTEN 0.0.0.0:22 sshd
tcp LISTEN 0.0.0.0:25 master (postfix)
If you see something unexpected (here, a mail server on port 25 you never asked for), disable it:
sudo systemctl disable --now postfix
disable --now both stops the service immediately and prevents it starting at boot.
Step 7: Enable automatic security updates
You will not log in every day to run apt upgrade, so let the server patch itself. The unattended-upgrades package installs security fixes automatically.
sudo apt install unattended-upgrades -y
sudo dpkg-reconfigure --priority=low unattended-upgrades
This writes /etc/apt/apt.conf.d/20auto-upgrades. Confirm it contains:
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
By default it only installs security updates, which is exactly what you want. See Automatic security updates for tuning reboot behaviour.
Hardening checklist at a glance
| Step | What it stops | Tool |
|---|---|---|
| Patch | Known exploits | apt upgrade |
| Non-root user | Full-system takeover | adduser + sudo |
| Key-only SSH | Password guessing | sshd_config |
| Firewall | Open-port scans | ufw |
| fail2ban | Brute-force floods | fail2ban |
| Disable services | Extra entry points | systemctl disable |
| Auto updates | Drifting out of date | unattended-upgrades |
Best Practices
- Harden the server before deploying any application, not after.
- Use one non-root user per human or per deployment role; never share the
rootpassword. - Prefer SSH keys over passwords everywhere, and rotate keys when someone leaves the team.
- Open the fewest firewall ports possible, and document why each one is open.
- Keep
unattended-upgradeson so the box never falls behind on security fixes. - Run
sudo ss -tulpnperiodically to catch services that appeared without your knowledge. - Pair hardening with regular backups so you can recover if prevention fails.