Launch pricing: every plan costs 30% less than the cheapest offshore competitor we track. See the benchmarkEvery plan 30% under the cheapest offshore host

Knowledge base · Security

Linux server hardening: the first hour

A first-hour hardening checklist for Linux servers: updates, a sudo user, SSH keys, firewall, fail2ban, time sync, Lynis audits, backups and incident response.

Updated 7 min read

In this guide

  • Patch the system and turn on automatic security updates before anything else.
  • Log in as a sudo user with an SSH key, then switch off root and password logins.
  • Allow only the ports you need with ufw or nftables, and let fail2ban block brute-force attempts.
  • If a server is compromised, contain it, keep the evidence, rotate every secret and rebuild from a clean template.
Security12 sections
All guides
On this page
  1. Install updates and automatic security updates
  2. Create a sudo user
  3. Log in with an SSH key
  4. Disable password and root logins
  5. Change the SSH port (optional)
  6. Set up a firewall
  7. Block brute force with fail2ban
  8. Keep the clock in sync
  9. Audit with Lynis
  10. Back up before you need to
  11. Notes for Tor relays and VPN servers
  12. If your server is compromised

A new server receives automated login attempts soon after it goes online. Work through this checklist in order, within the first hour. Commands are for Debian and Ubuntu, with notes for AlmaLinux and Rocky Linux where they differ. Run them as root until your sudo user exists, and replace alice with your own username and SERVER_IP with your server's address.

Install updates and automatic security updates

apt update
apt full-upgrade -y
apt install -y unattended-upgrades
dpkg-reconfigure -plow unattended-upgrades

Answer yes in the dialog. It writes /etc/apt/apt.conf.d/20auto-upgrades, and security updates then install daily. To reboot automatically when an update requires it, such as a new kernel, set these lines in /etc/apt/apt.conf.d/50unattended-upgrades:

Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "04:00";

On AlmaLinux and Rocky Linux, install dnf-automatic, set upgrade_type = security and apply_updates = yes in /etc/dnf/automatic.conf, then enable its timer:

dnf upgrade --refresh -y
dnf install -y dnf-automatic
systemctl enable --now dnf-automatic.timer

Reboot if the kernel was updated.

Create a sudo user

Working as root turns every typo into a system-wide change, and root is the first account attackers try. Create a personal account with sudo rights:

apt install -y sudo
adduser alice
usermod -aG sudo alice

On AlmaLinux and Rocky Linux, the admin group is called wheel:

useradd -m -G wheel alice
passwd alice

Log in with an SSH key

On your computer, create a key pair protected by a passphrase, copy the public key to the new account, then log in with it and check that sudo works:

ssh-keygen -t ed25519 -C "alice laptop"
ssh-copy-id alice@SERVER_IP
ssh alice@SERVER_IP
sudo -v

On Windows, use the PowerShell command from the getting started guide instead of ssh-copy-id.

Disable password and root logins

Continue only once the key login and sudo work for your new user. Create /etc/ssh/sshd_config.d/00-hardening.conf with these lines:

PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
MaxAuthTries 3
X11Forwarding no
AllowUsers alice

The 00- prefix matters. For each option, sshd uses the first value it reads, and it reads the files in sshd_config.d in alphabetical order before the rest of sshd_config. Your file therefore wins over 50-cloud-init.conf on cloud images, which can turn passwords back on, and over 01-permitrootlogin.conf on the Red Hat family. AllowUsers refuses every account that is not listed. Check the syntax and the values that will apply, then restart SSH:

sshd -t
sshd -T | grep -Ei 'permitrootlogin|passwordauthentication|kbdinteractive|allowusers'
systemctl restart ssh

On AlmaLinux and Rocky Linux, the service is called sshd. Existing sessions stay open. From a new terminal, confirm that alice can still log in and that root is refused.

Change the SSH port (optional)

A non-standard port removes most bot noise from your logs, but it is not a security boundary: scans still find it, every ssh, scp and rsync command needs the port, and some networks block unusual ports. With key-only logins, port 22 is fine. To move it anyway, add Port 2222 to your hardening file, open the port and restart SSH:

ufw allow 2222/tcp
systemctl restart ssh

Ubuntu 24.04 starts SSH through a systemd socket, so use these two commands there instead of the restart:

systemctl daemon-reload
systemctl restart ssh.socket

On AlmaLinux and Rocky Linux, SELinux and firewalld must allow the new port as well:

dnf install -y policycoreutils-python-utils
semanage port -a -t ssh_port_t -p tcp 2222
firewall-cmd --permanent --add-port=2222/tcp
firewall-cmd --reload
systemctl restart sshd

Test the new port from a new terminal before you remove the rule for port 22:

ssh -p 2222 alice@SERVER_IP

Set up a firewall

ufw on Debian and Ubuntu

apt install -y ufw
ufw default deny incoming
ufw default allow outgoing
ufw limit 22/tcp
ufw allow 443/tcp
ufw enable
ufw status verbose

limit allows SSH but blocks an address that opens six or more connections within 30 seconds. Use your own SSH port if you changed it, and open only what your services need. ufw adds matching IPv6 rules automatically.

nftables

For direct control, write your own ruleset to /etc/nftables.conf. This one drops all incoming traffic except replies, loopback, ICMP (which IPv6 needs to work), SSH and HTTPS:

#!/usr/sbin/nft -f
flush ruleset
table inet filter {
  chain input {
    type filter hook input priority 0; policy drop;
    ct state established,related accept
    ct state invalid drop
    iif "lo" accept
    meta l4proto { icmp, ipv6-icmp } accept
    tcp dport { 22, 443 } accept
  }
}

Check the file, then load it now and at every boot:

nft -c -f /etc/nftables.conf
systemctl enable --now nftables

Use either ufw or your own nftables file, not both. flush ruleset also deletes rules that other software created, so do not use it on Docker hosts. AlmaLinux and Rocky Linux ship with firewalld enabled; open a service like this:

firewall-cmd --permanent --add-service=https
firewall-cmd --reload

Block brute force with fail2ban

apt install -y fail2ban python3-systemd

Create /etc/fail2ban/jail.local. Put your own IP in ignoreip so you cannot ban yourself, and set port to your SSH port:

[DEFAULT]
backend = systemd
bantime = 1h
ignoreip = 127.0.0.1/8 ::1 YOUR_IP

[sshd]
enabled = true
port = 22
systemctl enable fail2ban
systemctl restart fail2ban
fail2ban-client status sshd

The systemd backend reads the journal, so it also works on fresh Debian 12 and 13 systems, which have no /var/log/auth.log. To lift a ban:

fail2ban-client set sshd unbanip IP_ADDRESS

On AlmaLinux and Rocky Linux, fail2ban comes from EPEL:

dnf install -y epel-release
dnf install -y fail2ban

With key-only SSH, fail2ban mainly cuts log noise; it matters more for services that accept passwords.

Keep the clock in sync

timedatectl set-ntp true
timedatectl set-timezone UTC
timedatectl

Look for System clock synchronized: yes. If set-ntp reports that NTP is not supported, install a time client and run it again:

apt install -y systemd-timesyncd

AlmaLinux and Rocky Linux enable chrony by default. Accurate time matters for TLS, one-time passwords, log correlation and Tor; UTC keeps logs from several servers comparable.

Audit with Lynis

apt install -y lynis
lynis audit system

Lynis checks hundreds of settings and prints warnings, suggestions and a hardening index. Details land in /var/log/lynis.log and /var/log/lynis-report.dat. Treat the result as a to-do list, not a score to maximize. On AlmaLinux and Rocky Linux, Lynis is in EPEL.

Back up before you need to

Hardening lowers the risk; backups make an incident recoverable. On a VPS, take a snapshot once this checklist is done, and set up encrypted off-site backups with restic or Borg as described in VPS snapshots and off-site backups. Make the backup repository append-only, so an intruder on the server cannot delete your history.

Notes for Tor relays and VPN servers

Tor relays

  • Our guide to running a Tor relay, bridge or onion service has the full setup for each role.
  • Non-exit relays and bridges only pass encrypted traffic within the Tor network; make that explicit with ExitRelay 0 in /etc/tor/torrc.
  • Exit relays are different: their traffic to websites appears to come from your IP and draws abuse reports. Read the acceptable use policy before you run one. Outbound port 25 is closed by default.
  • Install tor from the Tor Project's repository and let unattended-upgrades update it; the Tor relay guide has the exact origin lines for Debian and Ubuntu.
  • Open only the ORPort in the firewall, and keep any ControlPort or metrics port on localhost.
  • Bandwidth is unmetered under fair use. If you expect sustained high traffic, cap it with RelayBandwidthRate and RelayBandwidthBurst, or AccountingMax, in torrc.
  • Run a relay on its own server: relay addresses are public, and many services block them.

WireGuard VPN servers

The full setup, from keys to phone clients, is in how to host your own VPN with WireGuard. In short: open the WireGuard port, enable IP forwarding, allow forwarding only from the tunnel to your public interface (find its name with ip -br addr), and keep the configuration readable by root only:

ufw allow 51820/udp
echo 'net.ipv4.ip_forward=1' > /etc/sysctl.d/99-wireguard.conf
sysctl --system
ufw route allow in on wg0 out on eth0
chmod 600 /etc/wireguard/wg0.conf
  • Add NAT for the tunnel's address range, and enable IPv6 forwarding too if you route IPv6.
  • Traffic from your VPN users leaves through your server's IP. Spam, scans and attacks they send count as coming from your server under the acceptable use policy, so give access only to people you trust.
  • WireGuard itself keeps no connection logs. If you promise users no logs, also check journald and firewall logging.

If your server is compromised

Typical signs: unknown processes using CPU, unexpected outbound traffic, new accounts or SSH keys, or an abuse notice from us.

  1. Contain it. Stop outgoing attacks first: block outbound traffic in the firewall or shut the server down. We act immediately on attacks from our network under the zero-tolerance rules of the acceptable use policy, even when a server was hijacked.
  2. Preserve evidence. On a VPS, take a snapshot before you change anything, and copy the logs off the server.
  3. Investigate with the commands below: logins, open ports, processes, cron jobs, services, root's SSH keys and accounts with user ID 0.
  4. Rotate every secret that was on the server: passwords, SSH keys (create new ones on a clean machine), access tokens, database passwords and TLS private keys.
  5. Rebuild. Reinstall from a clean template, restore data from a backup made before the break-in, apply this checklist, and close the hole the attacker used, such as an outdated plugin, a weak password or an exposed service.
  6. Tell us. On a dedicated or GPU server, reply by ticket to our abuse notice with what you found and fixed, or ask for help.
last -a
ss -tulpn
ps auxf
ls -la /etc/cron.d /var/spool/cron
systemctl list-units --type=service --state=running
cat /root/.ssh/authorized_keys
awk -F: '$3 == 0' /etc/passwd
Stuck on a step?

Dedicated and GPU customers can open a ticket from the client area with the server’s IP address and what they tried. First reply target: under 12 hours. For every other server, use the Server actions on its page, the guides and the network status page.

Welcome back

Sign in to manage your servers and your balance.

No KYCHuman check by Cloudflare TurnstileNo tracking