Server hardening checklist: reduce what attackers can reach

Updated November 2026

Patching closes known holes; hardening reduces how many holes an attacker can reach in the first place. The work is simple: run fewer network services, keep admin interfaces off the internet, give every account and program only the access it needs, and make sure you can see and undo an attack.

Find what is listening

Every program that accepts network connections is something an attacker can probe; together they make up the server’s attack surface. Start by listing them, as root so you see the owning process:

sudo ss -tulpn

The options mean: -t TCP, -u UDP, -l listening sockets only, -p show the process, -n numbers instead of service names. Read the "Local Address:Port" column:

For each line, ask: does this need to exist, and does it need to be reachable from the network? If the answer is no, stop it or bind it to loopback.

# list running services
systemctl list-units --type=service --state=running

# stop a service now and keep it off after reboot (STOPS THE SERVICE)
sudo systemctl disable --now cups.service

# or remove the package if nothing needs it
sudo apt remove cups        # Debian, Ubuntu
sudo dnf remove cups        # RHEL family, Fedora

Databases and caches used by a local application should listen on loopback only. Defaults differ: PostgreSQL's listen_addresses defaults to localhost, and the stock redis.conf has bind 127.0.0.1 -::1 with protected mode on, but upstream MySQL's bind_address defaults to *, which means all IPv4 and IPv6 interfaces. Check what your package actually set rather than assuming. See ports that should never be public.

Keep admin panels private

Hosting panels, web consoles, database tools such as phpMyAdmin, and monitoring dashboards are high-value targets: one login or one bug gives control of the server. Don't leave them open to the whole internet. In order of preference:

  1. Reach them only over a VPN or a private network.
  2. Bind them to 127.0.0.1 and use an SSH tunnel: ssh -L 8443:127.0.0.1:8443 admin@server, then open https://localhost:8443 on your own machine.
  3. If they must face the internet, allow only your office or VPN addresses in the firewall and turn on MFA.

Remove panels and tools you no longer use. An old phpMyAdmin directory that nobody patches is a common way in.

Least privilege

Separate users per site

If ten websites run as the same Linux user, a hole in one plugin on one site lets the attacker change all ten. Run each site under its own system user (for PHP, a separate PHP-FPM pool per site, each with its own user), with its own home directory and database account. A compromise then stays inside one site. See Updating PHP and nginx, Apache, OpenSSL.

File permissions

# world-writable files and directories under the web root
sudo find /var/www -xdev \( -type f -o -type d \) -perm -0002 -ls

# set-uid and set-gid programs on the system (compare against a known-good list)
sudo find / -xdev \( -perm -4000 -o -perm -2000 \) -type f -ls

Never "fix" a permission problem with chmod -R 777. It lets every local user and every compromised service change those files.

Time, logs and backups

Accurate time

Logs from different servers can only be compared if the clocks agree, and TLS and MFA codes fail when the clock drifts. timedatectl shows System clock synchronized: yes when time sync works; sudo timedatectl set-ntp true enables the first available sync service.

Logging

systemd-journald stores logs on disk under /var/log/journal when its Storage= setting is persistent, or auto and that directory exists; otherwise logs live in memory and vanish at reboot. Check that the directory exists. An attacker with root can delete local logs, so also send logs to another machine. See Logging and monitoring.

Offline backups

CISA's ransomware guide says to keep offline, encrypted backups of critical data and to regularly test that they can be restored. "Offline" matters: if the server can delete or overwrite its own backups, so can an attacker or ransomware running as root on it. Use a backup system that the server pushes to but cannot erase (pull-based backups, or storage with retention locks). See Ransomware-proof backups.

MFA for panels and accounts

Passwords leak. CISA recommends phishing-resistant MFA, such as FIDO2 security keys, for all services. Turn on MFA for hosting panels, your cloud or hosting provider account, the domain registrar and DNS, code hosting, and the VPN. The provider account matters as much as the server: whoever controls it can open a console, reset root or delete the machine. For SSH, see MFA options for SSH.

Automatic security updates

Most attacks use known bugs that already have fixes. Let the server install security updates by itself and keep manual patching for the rest. The short version:

# Debian, Ubuntu
sudo apt install unattended-upgrades
sudo dpkg-reconfigure unattended-upgrades

# RHEL family: set upgrade_type = security in /etc/dnf/automatic.conf, then
sudo dnf install dnf-automatic
sudo systemctl enable --now dnf-automatic-install.timer

Installed updates still need service restarts and, for kernels, a reboot. Details are in Automatic updates and Restarts and reboots.

The checklist

Sources