Skip to content

Linux Server Hardening Checklist for a New VPS or Server

8 min read

Applies to: Ubuntu 26.04 LTS (OpenSSH 10.2p1, ufw 0.36.2, unattended-upgrades 2.12) and AlmaLinux 10.2 (firewalld, dnf-automatic); Debian, Ubuntu 24.04 and Rocky Linux use the same tools

Hardening a Linux server means closing the easy ways in. On a new VPS or dedicated server, in this order: install updates and turn on automatic security updates, create a sudo user, log in with SSH keys and switch off password and root logins, allow only the ports you use, block repeated failed logins, remove services you do not need, keep SELinux or AppArmor on, and keep logs, time and backups in order. The commands below were tested on Ubuntu 26.04 and AlmaLinux 10.

Before you start: keep your current SSH session open while you change SSH or firewall settings, and test a new login in a second window before you close the first. If you do lock yourself out, you need the server’s console (on Ucartz unmanaged dedicated servers, the free IPMI KVM).

1. Update the system and turn on automatic security updates

# Ubuntu and Debian
sudo apt update && sudo apt full-upgrade
# AlmaLinux, Rocky Linux, RHEL
sudo dnf upgrade

Reboot if a new kernel was installed. On Ubuntu, the file /run/reboot-required exists when a reboot is needed.

Ubuntu and Debian: according to Ubuntu, unattended-upgrades is installed by default on Ubuntu Server and installs security updates daily (our dry run on Ubuntu 26.04 used the resolute and resolute-security origins). Check it:

$ cat /etc/apt/apt.conf.d/20auto-upgrades
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";

If the file is missing, run sudo apt install unattended-upgrades; on Ubuntu 26.04 that created it with both values at "1". Automatic reboots are off by default (Automatic-Reboot in 50unattended-upgrades), so plan your own.

AlmaLinux and Rocky Linux: use dnf-automatic. In our AlmaLinux 10 test (dnf-automatic 4.20), /etc/dnf/automatic.conf was set to download updates but not install them (apply_updates = no, upgrade_type = default). To install security updates automatically:

sudo dnf install dnf-automatic
sudo sed -i 's/^upgrade_type = default/upgrade_type = security/' /etc/dnf/automatic.conf
sudo systemctl enable --now dnf-automatic-install.timer

According to the DNF documentation, this timer installs updates whatever apply_updates says, and upgrade_type = security limits it to updates with a security advisory (dnf updateinfo list --security listed AlmaLinux ALSA advisories in our test). Reboots stay with you (reboot = never).

More on update methods: update all packages on Linux safely and set up automatic updates.

2. Create a normal user with sudo

Work as a normal user and use sudo for admin tasks. The admin group is sudo on Ubuntu and Debian and wheel on AlmaLinux and Rocky Linux.

# Ubuntu and Debian
sudo adduser deploy
sudo usermod -aG sudo deploy
# AlmaLinux and Rocky Linux
sudo useradd -m deploy && sudo passwd deploy
sudo usermod -aG wheel deploy

Check it (lab output, Ubuntu 26.04):

$ id deploy
uid=1001(deploy) gid=1001(deploy) groups=1001(deploy),27(sudo),100(users)
$ sudo -l -U deploy
User deploy may run the following commands on vps:
    (ALL : ALL) ALL

Log in as this user in a new window and run sudo -v before you change anything for root.

3. Use SSH keys, then switch off passwords and root login

Create a key on your own computer (not on the server) and copy the public half to the new user:

$ ssh-keygen -t ed25519 -C "deploy@laptop"
$ ssh-copy-id -i ~/.ssh/id_ed25519.pub deploy@203.0.113.10

The server keeps the public key in ~/.ssh/authorized_keys; keep the folder at 700 and the file at 600 (in our lab: 700 /home/deploy/.ssh, 600 /home/deploy/.ssh/authorized_keys). Step-by-step help, including Windows: how to set up SSH keys for authentication and connecting to your VPS with SSH.

When the key login works, put your SSH settings in a file of their own:

sudo nano /etc/ssh/sshd_config.d/00-hardening.conf
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no

Why 00: sshd reads sshd_config.d/*.conf in name order and uses the first value it finds for each setting. In our test, a 50-cloud-init.conf with PasswordAuthentication yes beat our file while it was called 60-hardening.conf; once ours sorted first, passwords were off. Run ls /etc/ssh/sshd_config.d/ and look for files that switch these settings back on:

  • 50-cloud-init.conf: cloud-init writes PasswordAuthentication into this file on many cloud images.
  • 01-permitrootlogin.conf on AlmaLinux and Rocky Linux: the installer writes PermitRootLogin yes here when you allow root password login over SSH. The file itself says to remove it to opt out. Our AlmaLinux 10 test system had the same setting in 25-permitrootlogin.conf.

Check the syntax and the values sshd will really use, then apply:

$ sudo sshd -t
$ sudo sshd -T | grep -E '^(permitrootlogin|passwordauthentication|kbdinteractiveauthentication) '
permitrootlogin no
passwordauthentication no
kbdinteractiveauthentication no
$ sudo systemctl restart ssh      # AlmaLinux and Rocky Linux: sshd

sshd -t prints nothing when the file is valid. A typo stops it with a clear message, for example Bad configuration option: PermitRootLogn. Out of the box, Ubuntu 26.04 used permitrootlogin prohibit-password and passwordauthentication yes. On AlmaLinux 10, sshd -T showed permitrootlogin yes and passwordauthentication yes before the change and no for both after it.

A different SSH port is optional: it cuts log noise but does not replace keys. On Ubuntu 24.04 and later it needs extra steps for ssh.socket: see how to change the SSH port safely. More options: sshd hardening.

4. Turn on a firewall

Ubuntu and Debian (UFW): allow SSH first, then enable. Lab output, Ubuntu 26.04:

$ sudo ufw allow OpenSSH
Rules updated
Rules updated (v6)
$ sudo ufw enable
$ sudo ufw status verbose
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), deny (routed)
New profiles: skip

To                         Action      From
--                         ------      ----
22/tcp (OpenSSH)           ALLOW IN    Anywhere
22/tcp (OpenSSH (v6))      ALLOW IN    Anywhere (v6)

If you moved SSH to another port, allow that port number instead of OpenSSH. Then open only what you serve, for example sudo ufw allow 80,443/tcp for a website. Details: how to set up UFW on Ubuntu.

AlmaLinux and Rocky Linux (firewalld): firewalld is the standard firewall. In our AlmaLinux 10 test the default zone, public, allowed cockpit, dhcpv6-client and ssh. Cockpit is a web admin console; if you do not use it, close it, and open only the services you run:

sudo firewall-cmd --permanent --remove-service=cockpit
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
sudo firewall-cmd --list-services
dhcpv6-client https ssh

If firewalld is not running, start it with sudo systemctl enable --now firewalld. More: open a port in firewalld.

5. Block repeated failed logins with Fail2Ban

Fail2Ban bans IP addresses after repeated failed logins. Install it with sudo apt install fail2ban, or on AlmaLinux and Rocky Linux from EPEL (sudo dnf install epel-release, then sudo dnf install fail2ban). In our tests the Debian and Ubuntu packages turned on the SSH jail straight away; on AlmaLinux no jail was enabled, so add [sshd] with enabled = true to /etc/fail2ban/jail.local and run sudo systemctl enable --now fail2ban. Check with sudo fail2ban-client status sshd. It complements step 3; it does not replace it. Full setup, jails and testing: how to install and configure Fail2Ban. cPanel servers have their own brute-force protection, cPHulk: see how to secure a cPanel and WHM server.

6. Remove what you do not use

sudo ss -tulpn
systemctl list-units --type=service --state=running

ss lists every listening port and the program behind it. For each, ask whether you need it and whether it must be reachable from the internet. Disable what you do not need (sudo systemctl disable --now SERVICE) or remove the package. Bind databases and caches to 127.0.0.1 unless another server must reach them, and then allow only that server’s IP.

7. Keep SELinux or AppArmor switched on

Both are mandatory access control systems: on top of file permissions, they limit what each confined program may do, so a compromised service is held to its policy. Your distribution decides which one you have.

  • SELinux (RHEL, AlmaLinux, Rocky Linux, Fedora): labels every process and file and allows only what the policy permits for those labels. According to Red Hat, RHEL 10 runs it in enforcing mode with the targeted policy by default, and disabling it is strongly discouraged; our AlmaLinux 10 system had SELINUX=enforcing in /etc/selinux/config. Check with getenforce.
  • AppArmor (Ubuntu, Debian): uses a profile per program that lists the files and permissions it may use. It is on by default according to Ubuntu, and on Debian since Debian 10 according to the Debian wiki. Check with sudo aa-status.

SELinux vs AppArmor in short: SELinux decides by labels that it gives to every file and process; AppArmor decides by readable per-program profiles written with file paths, and programs without a profile are not confined. Both protect you only while they are on. When an application is blocked, fix the label or profile (on SELinux, setroubleshoot explains each denial; permissive mode lets you test) instead of disabling the system.

8. Logs, time, audits and backups

  • Logs: journalctl -u ssh (-u sshd on AlmaLinux) and last show SSH and recent logins.
  • Time: correct time matters for logs, TLS and updates: synchronise time with NTP.
  • Audit: run Lynis now and then for further suggestions, and a rootkit scanner such as rkhunter.
  • Backups: keep copies off the server and test a restore.

Ucartz VPS and dedicated servers include Free Basic Managed Support for small tasks (best effort). If you would rather have engineers harden, patch and monitor your servers for you, see Linux server management.

Official documentation: Ubuntu: AppArmor, Red Hat: getting started with SELinux.

Was this answer helpful?

Still stuck? Open a ticket