Secure your server
Updates, automatic security updates, a sudo user, key-only login and fail2ban.
6 min read
On this page
Applies to Debian 12/13, Ubuntu 22.04/24.04, AlmaLinux 9 and Rocky Linux 9. You need a working SSH login as root, see Connect via SSH.
Every server on the internet sees its first login attempts from bots within minutes. After the steps below, they go nowhere.
1. Install updates
Debian and Ubuntu:
apt update && apt full-upgrade -yAlmaLinux and Rocky Linux:
dnf upgrade -yIf a new kernel came in, reboot (reboot) and reconnect.
2. Automatic security updates
Debian and Ubuntu: unattended-upgrades
Ubuntu usually has the package already; on Debian, install it:
apt install -y unattended-upgrades
dpkg-reconfigure -plow unattended-upgradesAnswer the question with Yes. Security updates are now installed daily on their own. To check:
systemctl status unattended-upgrades
unattended-upgrade --dry-run --debugThe log is in /var/log/unattended-upgrades/.
AlmaLinux and Rocky Linux: dnf-automatic
dnf install -y dnf-automaticOpen /etc/dnf/automatic.conf and set in the [commands] section:
[commands]
upgrade_type = security
apply_updates = yesThen enable the timer:
systemctl enable --now dnf-automatic.timer
systemctl list-timers dnf-automatic.timer3. Create a user with sudo
Don't work as root day to day. Create your own user, here alex.
Debian and Ubuntu:
apt install -y sudo
adduser alex
usermod -aG sudo alexAlmaLinux and Rocky Linux:
useradd -m -G wheel alex
passwd alexYou'll need that password for sudo. Give the user the same SSH key as root:
mkdir -p /home/alex/.ssh
cp /root/.ssh/authorized_keys /home/alex/.ssh/
chown -R alex:alex /home/alex/.ssh
chmod 700 /home/alex/.ssh
chmod 600 /home/alex/.ssh/authorized_keysTest in a second window:
ssh alex@203.0.113.10
sudo whoamisudo whoami must ask for your password and then print root.
4. Harden SSH
Instead of editing /etc/ssh/sshd_config, create your own file in /etc/ssh/sshd_config.d/. Package updates leave it alone. All the systems listed here read this folder.
The name must start with 00-: for each setting, SSH uses the first value it finds, and cloud images often ship their own files such as 50-cloud-init.conf that allow password logins.
nano /etc/ssh/sshd_config.d/00-hardening.conf# Keys only, no passwords
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
# root only with a key (or "no" if you only use your sudo user from now on)
PermitRootLogin prohibit-password
MaxAuthTries 3
X11Forwarding noCheck the file and reload SSH:
sshd -tNo output means no errors. Then:
# Debian and Ubuntu
systemctl reload ssh
# AlmaLinux and Rocky Linux
systemctl reload sshdCheck that the values apply:
sshd -T | grep -Ei 'passwordauthentication|permitrootlogin|kbdinteractive'Expected: passwordauthentication no, permitrootlogin without-password (the internal name of prohibit-password) and kbdinteractiveauthentication no.
Now test in the second window:
ssh alex@203.0.113.10
ssh -o PubkeyAuthentication=no alex@203.0.113.10The first must work, the second must fail with Permission denied (publickey).
5. fail2ban
fail2ban blocks addresses for a while after too many failed logins. With key-only login it isn't a must, but it keeps the logs clean.
Debian and Ubuntu:
apt install -y fail2ban python3-systemdAlmaLinux and Rocky Linux (fail2ban comes from EPEL):
dnf install -y epel-release
dnf install -y fail2ban fail2ban-firewalldCreate your own file instead of editing jail.conf:
nano /etc/fail2ban/jail.d/sshd.local[sshd]
enabled = true
backend = systemd
maxretry = 5
findtime = 10m
bantime = 1hbackend = systemd matters: Debian 12 and 13 no longer write /var/log/auth.log, so fail2ban reads from the journal instead.
systemctl enable --now fail2ban
fail2ban-client status sshdThe output shows Currently banned and Total banned. To unban an address:
fail2ban-client set sshd unbanip 198.51.100.7Check that everything is in place
- Logging in as your user with a key works,
sudo whoamiprintsroot. - Logging in with a password fails.
systemctl status unattended-upgradesorsystemctl list-timers dnf-automatic.timershows it as active.fail2ban-client status sshdresponds.
Only then close the root session.
Common errors
| Problem | Fix |
|---|---|
Password login still works despite PasswordAuthentication no | Another file in /etc/ssh/sshd_config.d/ comes first. sshd -T shows the effective value; your file must sort first alphabetically (00-…). |
sudo: command not found | Debian without sudo: apt install sudo as root. |
alex is not in the sudoers file | Group missing: usermod -aG sudo alex (Debian/Ubuntu) or usermod -aG wheel alex (AlmaLinux/Rocky), then log in again. |
| fail2ban doesn't start | journalctl -u fail2ban -n 50. Usually backend = systemd or python3-systemd is missing. |
| Locked out | Open the Console in the dashboard, log in as root with the password from Credentials, fix 00-hardening.conf and run systemctl reload ssh or sshd. Reset root password in the System tab sets a new password. |
What next
- Set up a firewall so only the ports you need are open.
- Add swap if RAM gets tight.
- Help: ticket.