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

IMPORTANT: Do not close this session!

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
Tip: Use a Passphrase

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

Important: Follow the Order!

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
Opening Additional Ports

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
Understanding the Configuration

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
Check Fail2ban Status

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 yes back
  • 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.