---
title: "Linux Server Hardening Checklist: The First Hour"
description: "A first-hour hardening checklist for Linux servers: updates, a sudo user, SSH keys, firewall, fail2ban, time sync, audits and backups."
url: https://offshoreserv.com/docs/security/hardening
lang: en
updated: 2026-09-26
source: HTML page at the url above (canonical); this is its Markdown version
---

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 26 September 2026 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

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.

> Keep your current SSH session open until a new login works, and know your way back in: the console in the client area for a VPS, the IPMI console for a dedicated server.

## 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](https://offshoreserv.com/docs/getting-started#add-ssh-key) 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](https://offshoreserv.com/docs/vps/snapshots). 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](https://offshoreserv.com/docs/vps/tor-relay) 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](https://offshoreserv.com/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](https://community.torproject.org/relay/setup/guard/debian-ubuntu/updates/) 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](https://offshoreserv.com/docs/vps/wireguard-vpn). 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](https://offshoreserv.com/docs/vps/operating-systems#reinstall), 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
```

> Do not clean up a compromised system and keep running it. Backdoors survive a cleanup; rebuilding is the only reliable fix.

**Stuck on a step?**

Dedicated and GPU customers can open a ticket from the [client area](https://offshoreserv.com/account/support) 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](https://offshoreserv.com/docs) and the [network status](https://offshoreserv.com/status) page.

---

OffshoreServ is an offshore hosting provider: VPS, dedicated servers, Windows RDP and GPU servers in seven jurisdictions (Iceland, Switzerland, Moldova, Romania, the Netherlands, Bulgaria and Malaysia), paid only in cryptocurrency (Bitcoin, Ethereum, Monero, Tether (USDT) and Solana), with no identity checks (no KYC).

Prices and plans: https://offshoreserv.com/pricing · Answers: https://offshoreserv.com/faq · Every page: https://offshoreserv.com/llms.txt
