Estimated reading time: A coffee break - approx. 15 minutes
2025-02-15
Hardening Ubuntu Server
A freshly installed Ubuntu Server is like an apartment with the door wide open – functional, but not secure. In this guide, we'll show you step by step how to professionally secure your server: from SSH key authentication through firewall configuration to protection against brute-force attacks with Fail2ban.
Variable Explanation
In this guide, we use placeholders in square brackets that you need to replace with your own values. This keeps the guide flexible and allows you to apply it to any server.
Variables you need to adjust:
[server-name] = FQDN or IP address of your server
[server-user-name] = Your username on the server
[service-name] = Identifier for the system/service
Variables that stay as they are:
[client-name] = Your local Linux client
[client-user-name] = Your local username
1 Create a User on the Server
This is your safety line in case something goes wrong during configuration. Alternatively, you can use a console session from your cloud provider (AWS, Hetzner, Proxmox, etc.).
Log in to the Server
[client-user-name]@[client-name]:~$ ssh root@[server-name]
Create the User
root@[server-name]:~# adduser [server-user-name]
info: Adding user `[server-user-name]' ...
info: Selecting UID/GID from range 1000 to 59999 ...
info: Adding new group `[server-user-name]' (1001) ...
info: Adding new user `[server-user-name]' (1001) with group `[server-user-name] (1001)' ...
info: Creating home directory `/home/[server-user-name]' ...
info: Copying files from `/etc/skel' ...
New password:
Retype new password:
Add User to Sudoers
To enable the new user to perform administrative tasks, we add them to the sudo group:
root@[server-name]:~# usermod -aG sudo [server-user-name]
2 Create SSH Key Pair on the Client
SSH keys are significantly more secure than passwords. An attacker would need access to your private key, not just guess a password. We use ed25519 – a modern, secure algorithm.
Create Directory for SSH Keys
[client-user-name]@[client-name]:~$ mkdir -p /[encryptedfs]/[service-name]/config/.ssh
Generate SSH Key
[client-user-name]@[client-name]:~$ ssh-keygen -t ed25519 -C "[service-name]"
Generating public/private ed25519 key pair.
Enter file in which to save the key (/home/[client-user-name]/.ssh/id_ed25519): /[encryptedfs]/[service-name]/config/.ssh/id_[service-name]
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /[encryptedfs]/[service-name]/config/.ssh/id_[service-name]
Your public key has been saved in /[encryptedfs]/[service-name]/config/.ssh/id_[service-name].pub
A passphrase provides additional protection for your private key. Even if someone gains access to the file, they cannot use it without the passphrase.
Copy Public Key to the Server
[client-user-name]@[client-name]:~$ ssh-copy-id -i /[encryptedfs]/[service-name]/config/.ssh/id_[service-name].pub [server-user-name]@[server-name]
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/[encryptedfs]/[service-name]/config/.ssh/id_[service-name].pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
[server-user-name]@[server-name]'s password:
Number of key(s) added: 1
Now try logging into the machine, with: "ssh '[server-user-name]@[server-name]'"
and check to make sure that only the key(s) you wanted were added.
Test Connection with SSH Key
Test the connection with the newly created key:
[client-user-name]@[client-name]:~$ ssh -i /[encryptedfs]/[service-name]/config/.ssh/id_[service-name] [server-user-name]@[server-name]
If you can log in successfully, key authentication is working!
3 Harden SSH Configuration
Now comes the most important step: we disable password logins and only allow SSH key authentication. We also completely block root login.
Edit Main Configuration File
[server-user-name]@[server-name]:~$ sudo vim /etc/ssh/sshd_config
Add or modify the following lines:
# Enable key authentication
PubkeyAuthentication yes
# Disable password authentication
PasswordAuthentication no
# Block empty passwords
PermitEmptyPasswords no
# Block root login
PermitRootLogin no
# Allow only the user listed
AllowUsers [server-user-name]
# Disable X11 forwarding (enable only if you need to display GUI applications from the server on your local machine)
X11Forwarding no
# Max authentication tries before disconnect
MaxAuthTries 3
# SSH keepalive interval in seconds
ClientAliveInterval 300
# Max keepalive messages without response
ClientAliveCountMax 2
Check Additional Configuration Files
Some cloud providers create their own configuration files that can override your settings.
Therefore, check the /etc/ssh/sshd_config.d/ directory:
[server-user-name]@[server-name]:~$ ls -al /etc/ssh/sshd_config.d/
total 12
drwxr-xr-x 2 root root 4096 Feb 15 06:19 .
drwxr-xr-x 4 root root 4096 Feb 15 06:23 ..
-rw-r--r-- 1 root root 26 Feb 15 06:19 dd-foo-bar.conf
If a file contains PasswordAuthentication yes, change it:
[server-user-name]@[server-name]:~$ sudo vim /etc/ssh/sshd_config.d/dd-foo-bar.conf
# Disable password authentication
PasswordAuthentication no
Restart SSH Service
For the changes to take effect, restart the SSH service:
[server-user-name]@[server-name]:~$ sudo systemctl restart ssh
Verification - Very Important!
Test in a new SSH session whether:
- You can log in with the SSH key
- You cannot log in with a password
- You cannot log in as root
Only close your original session after all tests have succeeded!
4 Enable UFW Firewall
A firewall is essential for server security. UFW (Uncomplicated Firewall) makes configuration simple and understandable. We only open the ports that are actually needed.
Check Firewall Status
[server-user-name]@[server-name]:~$ sudo ufw status verbose
Status: inactive
Add Firewall Rules
Execute the commands exactly in this order. If you block everything first and then allow SSH, you'll lock yourself out!
[server-user-name]@[server-name]:~$ sudo ufw allow 22/tcp comment 'SSH'
[server-user-name]@[server-name]:~$ sudo ufw allow 80/tcp comment 'HTTP'
[server-user-name]@[server-name]:~$ sudo ufw allow 443/tcp comment 'HTTPS'
[server-user-name]@[server-name]:~$ sudo ufw default deny incoming
[server-user-name]@[server-name]:~$ sudo ufw default allow outgoing
Enable Firewall
[server-user-name]@[server-name]:~$ sudo ufw enable
Check Rules
[server-user-name]@[server-name]:~$ sudo ufw status numbered
Status: active
To Action From
-- ------ ----
[ 1] 22/tcp ALLOW IN Anywhere # SSH
[ 2] 80/tcp ALLOW IN Anywhere # HTTP
[ 3] 443/tcp ALLOW IN Anywhere # HTTPS
[ 4] 22/tcp (v6) ALLOW IN Anywhere (v6) # SSH
[ 5] 80/tcp (v6) ALLOW IN Anywhere (v6) # HTTP
[ 6] 443/tcp (v6) ALLOW IN Anywhere (v6) # HTTPS
If you run additional services (e.g., MySQL on port 3306 or PostgreSQL on 5432),
add them with sudo ufw allow 3306/tcp comment 'MySQL'.
5 Set Up Fail2ban for SSH
Fail2ban protects against brute-force attacks by automatically blocking IP addresses after multiple failed login attempts for a specified period. An indispensable tool for any publicly accessible server.
Install Fail2ban
[server-user-name]@[server-name]:~$ sudo apt update
[server-user-name]@[server-name]:~$ sudo apt install fail2ban
Create Local Configuration
We create a local configuration file that will survive updates:
[server-user-name]@[server-name]:~$ sudo vim /etc/fail2ban/jail.local
[DEFAULT]
# Ban duration in seconds (3600 = 1 hour)
bantime = 3600
# Time window to count failed attempts (600 = 10 minutes)
findtime = 600
# Maximum failed attempts before ban
maxretry = 3
# Ignore localhost
ignoreip = 127.0.0.1/8 ::1
[sshd]
enabled = true
port = 22
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime: How long an IP remains blocked (3600 seconds = 1 hour)
findtime: Time window for failed attempts (600 seconds = 10 minutes)
maxretry: Maximum number of failed attempts (3 = banned after the third attempt)
Restart Fail2ban
[server-user-name]@[server-name]:~$ sudo systemctl restart fail2ban
Check Logs
Verify that Fail2ban started correctly:
[server-user-name]@[server-name]:~$ sudo tail /var/log/fail2ban.log
2026-02-15 18:44:45,543 fail2ban.filter [92546]: INFO maxRetry: 3
2026-02-15 18:44:45,543 fail2ban.filter [92546]: INFO findtime: 600
2026-02-15 18:44:45,543 fail2ban.actions [92546]: INFO banTime: 3600
2026-02-15 18:44:45,543 fail2ban.filter [92546]: INFO encoding: UTF-8
2026-02-15 18:44:45,544 fail2ban.jail [92546]: INFO Jail 'sshd' started
With sudo fail2ban-client status sshd you can see how many IPs are currently banned
and how many failed login attempts have been registered.
Success - Your Server Is Now Much More Secure!
You have successfully:
- Created a dedicated user with sudo privileges
- Set up SSH key authentication and disabled password login
- Completely blocked root login
- Configured a firewall that only opens necessary ports
- Installed Fail2ban to automatically defend against brute-force attacks
Troubleshooting
I've locked myself out - what now?
This is exactly why you should have kept the original SSH session open! If you've locked yourself out anyway:
- Use the console access from your cloud provider
- Temporarily set
PasswordAuthentication yesback - Restart the SSH service:
sudo systemctl restart ssh - Log in and check the configuration again
Fail2ban has banned my own IP!
# Unban IP:
sudo fail2ban-client set sshd unbanip YOUR-IP-ADDRESS
# Or add your own IP to the whitelist in /etc/fail2ban/jail.local:
ignoreip = 127.0.0.1/8 ::1 YOUR-IP-ADDRESS
Note: This guide provides solid basic security for Ubuntu Server. For production environments with high security requirements, we recommend a professional security assessment and individualized hardening measures.