$linuxjunkies
>

Linux Server Security Checklist

A step-by-step Linux server hardening checklist: secure SSH, firewall rules, automatic updates, service auditing, fail2ban, and intrusion detection for any internet-facing server.

IntermediateUbuntuDebianFedoraArch12 min readUpdated June 7, 2026

Before you start

  • Root or sudo access on a freshly installed server
  • A local SSH key pair already generated (ssh-keygen -t ed25519)
  • Basic familiarity with a terminal text editor such as nano or vim

Every server exposed to the public internet is probed within minutes of going online. This checklist covers the essential hardening steps you should complete before a server handles real traffic: locking down SSH, configuring a firewall, enforcing automatic updates, auditing running services, and setting up basic intrusion detection. Work through these in order on a fresh install; retrofitting a live server follows the same steps but requires more care around service restarts.

1. Create a Non-Root Admin User

Logging in directly as root over SSH is unnecessary and dangerous. Create a dedicated admin account, add it to the appropriate sudo group, and you will disable root SSH login in a later step.

Debian/Ubuntu

adduser deploy
usermod -aG sudo deploy

Fedora / RHEL / Rocky

useradd -m deploy
usermod -aG wheel deploy

Arch

useradd -m -G wheel deploy
passwd deploy

Verify you can sudo as the new user before closing your root session.

2. Harden SSH

SSH is the primary attack surface on most servers. Three changes make an enormous difference: key-only authentication, disabling root login, and changing the default port (optional, but cuts automated noise).

Install your public key

# Run this from your LOCAL machine, replacing the IP and user
ssh-copy-id -i ~/.ssh/id_ed25519.pub [email protected]

Edit /etc/ssh/sshd_config

Set these directives explicitly. They may already exist — search before appending.

Port 2222                    # optional: non-default port
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
AuthorizationKeysFile .ssh/authorized_keys
X11Forwarding no
AllowTcpForwarding no
MaxAuthTries 3
LoginGraceTime 30
ClientAliveInterval 300
ClientAliveCountMax 2

Restart and verify

sshd -t                         # syntax check before restarting
systemctl restart sshd

Do not close your current session until you have confirmed a new session connects successfully with the key.

3. Configure the Firewall

Allow only the ports your services actually need. Below are the common defaults: SSH (adjust if you changed the port), HTTP, and HTTPS.

UFW (Debian/Ubuntu)

ufw default deny incoming
ufw default allow outgoing
ufw allow 2222/tcp    # your SSH port
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable
ufw status verbose

firewalld (Fedora / RHEL / Rocky)

systemctl enable --now firewalld
firewall-cmd --permanent --remove-service=ssh          # removes port 22
firewall-cmd --permanent --add-port=2222/tcp
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --reload
firewall-cmd --list-all

nftables (Arch / any distro)

cat /etc/nftables.conf   # inspect existing rules before editing
systemctl enable --now nftables

Edit /etc/nftables.conf directly to add your allowed ports, then systemctl reload nftables.

4. Enable Automatic Security Updates

Unpatched vulnerabilities are the most common root cause of server compromise. Automate security-only updates; review major version upgrades manually.

Debian/Ubuntu — unattended-upgrades

apt install -y unattended-upgrades
dpkg-reconfigure -plow unattended-upgrades

Verify the service is active:

systemctl is-enabled unattended-upgrades

Fedora / RHEL / Rocky — dnf-automatic

dnf install -y dnf-automatic
# Edit /etc/dnf/automatic.conf: set upgrade_type = security
systemctl enable --now dnf-automatic-install.timer

Arch

Arch ships rolling releases with no concept of security-only updates. Use a systemd timer to run pacman -Syu regularly, and review the Arch Security Advisories at security.archlinux.org.

5. Disable Unused Services

Every listening daemon is an attack surface. List what is running, stop anything you don't recognise or need, and disable it so it won't restart on boot.

systemctl list-units --type=service --state=running
# Example: disable the Avahi mDNS daemon (rarely needed on servers)
systemctl stop avahi-daemon
systemctl disable avahi-daemon
systemctl mask avahi-daemon    # prevents re-enabling by package updates

Also audit listening network sockets:

ss -tlnp

Every entry in the output should be a service you deliberately chose to expose. If you see 0.0.0.0 on a port you didn't intend to open, investigate immediately.

6. Install fail2ban for Brute-Force Protection

Even with key-only SSH, fail2ban blocks repeated failed login attempts and can protect other services like Nginx, Postfix, or WordPress.

Debian/Ubuntu

apt install -y fail2ban

Fedora / RHEL / Rocky

dnf install -y fail2ban

Configure a local jail override

Never edit /etc/fail2ban/jail.conf directly; it gets overwritten on upgrade. Create a local override instead:

cat > /etc/fail2ban/jail.local <<'EOF'
[DEFAULT]
bantime  = 1h
findtime = 10m
maxretry = 5

[sshd]
enabled = true
port    = 2222
EOF

systemctl enable --now fail2ban
fail2ban-client status sshd

7. Kernel and Sysctl Hardening

A handful of kernel parameters meaningfully reduce the attack surface against network-based exploits. Add them to a drop-in file so they survive package updates to /etc/sysctl.d/.

cat > /etc/sysctl.d/99-hardening.conf <<'EOF'
# Disable IP forwarding (unless this is a router/container host)
net.ipv4.ip_forward = 0
net.ipv6.conf.all.forwarding = 0

# Protect against SYN flood
net.ipv4.tcp_syncookies = 1

# Ignore ICMP redirects
net.ipv4.conf.all.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0

# Ignore broadcast pings
net.ipv4.icmp_echo_ignore_broadcasts = 1

# Randomise virtual address space
kernel.randomize_va_space = 2
EOF

sysctl --system

If this server runs containers or acts as a router, keep ip_forward enabled; disabling it will break routing.

8. Audit File Permissions and SUID Binaries

SUID/SGID binaries run with elevated privileges regardless of which user executes them. Know what you have.

find / -xdev -perm /4000 -type f 2>/dev/null   # SUID binaries
find / -xdev -perm /2000 -type f 2>/dev/null   # SGID binaries

Compare the output against a known-good baseline. If you see anything unexpected — especially in /tmp, /var, or home directories — investigate. You can remove the SUID bit from binaries you don't need:

chmod u-s /usr/bin/some-binary

9. Install an Intrusion Detection Baseline

AIDE (Advanced Intrusion Detection Environment) records cryptographic hashes of important system files. Run it after hardening and store the database somewhere off-server; compare it periodically to detect unexpected changes.

Debian/Ubuntu

apt install -y aide
aide --config=/etc/aide/aide.conf --init
mv /var/lib/aide/aide.db.new /var/lib/aide/aide.db

Fedora / RHEL / Rocky

dnf install -y aide
aide --init
mv /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz

Schedule regular checks with a systemd timer or cron, and have the output emailed to you or shipped to a log aggregator.

Verification

After completing the checklist, run a quick external scan against your own server. nmap from a separate machine gives you an attacker's-eye view:

# From a different machine
nmap -sV -p- --open 203.0.113.10

The only open ports should be the ones you explicitly allowed. A clean result here is not a guarantee of security, but an obvious misconfiguration will show up immediately.

Troubleshooting

  • Locked out of SSH after changing the port or disabling passwords: You will need console access (cloud provider serial console or physical KVM). Always verify new SSH settings in a second terminal before logging out.
  • UFW/firewalld blocking a service unexpectedly: Run journalctl -xe and look for UFW BLOCK or REJECT kernel log entries to identify the offending port or protocol.
  • fail2ban not banning IPs: Check fail2ban-client status sshd and inspect /var/log/fail2ban.log. A common issue is a mismatched log path or backend; set backend = systemd in jail.local if your distro uses journald without traditional log files.
  • sysctl settings not persisting after reboot: Confirm the file is in /etc/sysctl.d/ and ends in .conf. Run sysctl --system again and verify with sysctl net.ipv4.tcp_syncookies.
tested on:Ubuntu 24.04Debian 12Rocky 9Fedora 40

Frequently asked questions

Do I need to do all of these steps every time I provision a server?
Yes, or better: bake them into a provisioning script or configuration management tool (Ansible, Terraform, cloud-init) so every server starts hardened without manual effort.
Is changing the SSH port from 22 actually worth it?
It does not improve real security against a targeted attacker, but it eliminates virtually all automated credential-stuffing noise in your logs, which makes genuine intrusion attempts easier to spot.
Should I disable IPv6 if I'm not using it?
Disabling it entirely can break some system functionality and is not necessary. Instead, make sure your firewall rules cover both IPv4 and IPv6 — ufw and firewalld do this by default.
How does fail2ban work when I've already disabled password authentication on SSH?
It still provides value by banning IPs that probe for valid usernames, attempt expired-key handshakes, or attack other services like Nginx, Postfix, or a web application on the same host.
What should I do after AIDE reports a changed file?
Determine whether the change was expected (a package update, a config edit you made) or unexplained. Unexplained changes to binaries, libraries, or cron jobs should be treated as a potential compromise and investigated before re-updating the baseline.

Related guides