InfraRunBook
    Back to articles

    The Complete Server Hardening Checklist for 2026

    Cyber Security for Servers
    Published: Aug 23, 2026
    Updated: Aug 23, 2026

    A practical, step-by-step server hardening checklist for 2026 covering SSH, firewalls, kernel parameters, auditing, and patch management, with a full configuration example and verification steps.

    The Complete Server Hardening Checklist for 2026

    Every year I tell myself I'm going to write down the hardening steps I actually apply to production servers, instead of relying on memory and a scattered pile of old notes. This is that list for 2026. It's not theoretical. Every setting here is something I've deployed on real boxes, and every mistake in the "common mistakes" section is something I've either done myself or watched a colleague do at 2 AM during an incident.

    Server hardening isn't a one-time task you check off and forget. It's a baseline you establish, then defend against drift. New CVEs land, packages get reinstalled with default configs, and someone on the team always opens a port "just for testing" and forgets to close it. This checklist gives you a repeatable process, not just a list of settings to paste in once.

    Prerequisites

    Before you touch anything, make sure you have out-of-band access to the server. This is the single most important prerequisite and the one people skip most often. If you're hardening SSH or firewall rules remotely and you lock yourself out, you need a way back in that doesn't depend on the network stack you just broke. That means IPMI, a cloud provider's serial/VNC console, or physical access.

    You'll also want:

    • Root or sudo access on the target host, and a non-root administrative account already created (in my examples I use infrarunbook-admin).
    • A recent full backup or snapshot, taken before you start. Hardening changes can break legacy applications that depend on loose permissions or weak crypto.
    • A change window communicated to whoever depends on this server, especially if it's a shared box with cron jobs or scheduled batch processes.
    • A working configuration management setup (Ansible, Salt, or even a well-tested shell script) if you're hardening more than one host. Doing this by hand on ten servers is how inconsistency creeps in.

    I'm assuming a Debian/Ubuntu-based server in most examples below (host sw-infrarunbook-01, internal address 10.20.30.15), but the concepts translate directly to RHEL-family distros with different package names.

    Step-by-step setup

    1. Update everything first

    Don't harden a server that's three months behind on patches. Get it current before you start layering on configuration changes, otherwise you won't know if a problem you hit later is caused by your hardening or by an unpatched bug.

    apt update && apt full-upgrade -y
    apt install unattended-upgrades apt-listchanges -y
    dpkg-reconfigure --priority=low unattended-upgrades

    I enable unattended upgrades for security patches only, not full-upgrade automation. In my experience, automating full upgrades unattended on production servers eventually breaks something at 3 AM with nobody watching. Security patches are a safer default to automate.

    2. Lock down SSH

    SSH is the front door on almost every server you'll ever touch, so it gets disproportionate attention here. Create your admin user first, copy your public key over, and confirm you can log in with it before you disable password authentication.

    adduser infrarunbook-admin
    usermod -aG sudo infrarunbook-admin
    mkdir -p /home/infrarunbook-admin/.ssh
    cp /root/.ssh/authorized_keys /home/infrarunbook-admin/.ssh/
    chown -R infrarunbook-admin:infrarunbook-admin /home/infrarunbook-admin/.ssh
    chmod 700 /home/infrarunbook-admin/.ssh
    chmod 600 /home/infrarunbook-admin/.ssh/authorized_keys

    Test that login before editing sshd_config. Open a second terminal session and confirm key-based login works while your existing root session is still open. If you skip this step and something's wrong with the key, you'll find out the hard way when you're already locked out.

    3. Configure the firewall

    Default deny incoming, default allow outgoing, and only open what you actually need. I use ufw on Debian-family hosts because it's readable at a glance, even though it's just a wrapper around iptables/nftables.

    ufw default deny incoming
    ufw default allow outgoing
    ufw allow 2222/tcp comment 'ssh'
    ufw allow from 10.20.30.0/24 to any port 5432 proto tcp comment 'postgres internal only'
    ufw limit 2222/tcp
    ufw enable

    Note the ufw limit line. That rate-limits new connections on the SSH port, which blunts basic brute-force attempts without needing a separate tool. I still run fail2ban on top of it, because limit and fail2ban catch slightly different attack patterns and the overlap is cheap.

    4. Harden the kernel with sysctl

    Kernel-level network hardening catches a whole class of spoofing and redirection attacks before they ever reach an application. This is the step people skip most often because it feels abstract, but it's cheap to apply and it works quietly in the background forever.

    # /etc/sysctl.d/99-hardening.conf
    net.ipv4.conf.all.accept_redirects = 0
    net.ipv4.conf.all.send_redirects = 0
    net.ipv4.conf.all.accept_source_route = 0
    net.ipv4.conf.all.rp_filter = 1
    net.ipv4.icmp_echo_ignore_broadcasts = 1
    net.ipv4.tcp_syncookies = 1
    net.ipv6.conf.all.accept_redirects = 0
    net.ipv6.conf.all.accept_ra = 0
    kernel.randomize_va_space = 2
    kernel.dmesg_restrict = 1
    fs.suid_dumpable = 0
    sysctl -p /etc/sysctl.d/99-hardening.conf

    5. Install and configure auditd

    Auditing isn't prevention, it's the thing that tells you what happened after prevention fails. I've closed out more than one incident review by pulling exact timestamps from auditd logs that nothing else on the box had captured.

    apt install auditd audispd-plugins -y
    systemctl enable auditd --now

    At minimum, watch changes to authentication files, sudoers, and the audit configuration itself:

    # /etc/audit/rules.d/hardening.rules
    -w /etc/passwd -p wa -k identity
    -w /etc/shadow -p wa -k identity
    -w /etc/sudoers -p wa -k sudoers_change
    -w /etc/ssh/sshd_config -p wa -k sshd_config
    -w /var/log/auth.log -p wa -k auth_log
    -e 2

    That final -e 2 line locks the audit configuration so it can't be changed without a reboot, not even by root. That's intentional. It means if an attacker gets root, they can't quietly disable auditing without leaving a very obvious trace.

    6. Restrict cron, at, and set proper file permissions

    Cron and at are frequently used for persistence once someone has a foothold, so I explicitly allowlist who can use them rather than leaving it open by default.

    rm -f /etc/cron.deny /etc/at.deny
    echo "infrarunbook-admin" > /etc/cron.allow
    echo "infrarunbook-admin" > /etc/at.allow
    chmod 600 /etc/cron.allow /etc/at.allow
    chmod 700 /etc/cron.d /etc/cron.daily /etc/cron.hourly /etc/cron.weekly /etc/cron.monthly

    7. Disable unused services and remove unnecessary packages

    Every running service is attack surface, whether or not it's actually being used. I go through systemctl list-unit-files --state=enabled on every new server and disable anything I can't justify.

    systemctl list-unit-files --state=enabled
    systemctl disable --now avahi-daemon cups rpcbind
    apt purge avahi-daemon cups-common -y
    apt autoremove -y

    Full configuration example

    Here's a consolidated sshd_config that reflects the combined result of the SSH steps above, tuned for a typical internal application server. Adjust the port and AllowUsers line for your environment.

    # /etc/ssh/sshd_config
    Port 2222
    Protocol 2
    AddressFamily inet
    ListenAddress 10.20.30.15
    
    PermitRootLogin no
    PasswordAuthentication no
    PermitEmptyPasswords no
    ChallengeResponseAuthentication no
    KbdInteractiveAuthentication no
    UsePAM yes
    
    AllowUsers infrarunbook-admin
    MaxAuthTries 3
    LoginGraceTime 30
    ClientAliveInterval 300
    ClientAliveCountMax 2
    
    X11Forwarding no
    AllowAgentForwarding no
    AllowTcpForwarding no
    PermitTunnel no
    
    KexAlgorithms curve25519-sha256,diffie-hellman-group16-sha512
    Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com
    MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
    
    Banner /etc/issue.net
    LogLevel VERBOSE

    After editing, always validate the syntax before restarting the service:

    sshd -t
    systemctl restart sshd

    Keep that existing SSH session open while you restart. If the new config has a problem, you want a live session to fall back on rather than discovering the failure by being locked out entirely.

    Verification steps

    Once everything is applied, don't just trust that it worked. Verify each layer independently.

    # Confirm SSH is only listening where expected
    ss -tlnp | grep sshd
    
    # Confirm root login and password auth are actually rejected
    ssh -o PreferredAuthentications=password -p 2222 infrarunbook-admin@10.20.30.15
    
    # Confirm firewall rules match intent
    ufw status verbose
    
    # Confirm sysctl values took effect
    sysctl net.ipv4.conf.all.accept_redirects net.ipv4.tcp_syncookies kernel.randomize_va_space
    
    # Confirm auditd is running and rules are loaded
    auditctl -l
    systemctl status auditd --no-pager
    
    # Confirm the audit config is actually immutable
    auditctl -s | grep enabled
    
    # Confirm no unexpected services are listening
    ss -tlnp
    
    # Confirm cron access is restricted
    cat /etc/cron.allow

    I also run a quick external port scan from a separate machine on the same network segment, just to see the server the way an attacker on that segment would see it. It's surprising how often that turns up a listener you forgot about, usually something a package installed as a side effect and enabled by default.

    nmap -sT -p- 10.20.30.15

    If you're managing more than a handful of hosts, run a CIS benchmark scanner (OpenSCAP works well on RHEL-family systems, Lynis is a lighter-weight option that works everywhere) as a periodic check rather than a one-time audit. Configuration drift is real, and a server that was hardened six months ago is not guaranteed to still be hardened today.

    Common mistakes

    The mistake I see most often is disabling password authentication before confirming key-based login actually works. It takes thirty seconds to test in a second terminal window, and skipping that step has cost people entire evenings of console access requests to get back into a box.

    Another one: hardening sysctl settings on a server that's actually a router, NAT gateway, or load balancer, without realizing some of those settings assume the host isn't forwarding traffic. Setting net.ipv4.ip_forward = 0 blindly across your fleet will quietly break anything that depends on forwarding. Know your host's role before applying a blanket template.

    I've also seen teams treat the firewall as the only layer of defense and skip host-based controls entirely, on the theory that "the cloud security group already blocks that." Security groups and firewalls get misconfigured, get temporarily opened for debugging and never closed, or get bypassed entirely if the workload moves. Defense in depth means the host itself shouldn't assume the network in front of it is trustworthy.

    Locking the audit configuration with -e 2 and then forgetting you did it is a subtler trap. Six months later someone tries to add a new audit rule, it silently fails to apply, and they spend an hour confused before remembering the config requires a reboot to change. Document that choice somewhere your future self, or your teammate, will actually see it.

    Finally, watch out for hardening that never gets revisited. I've walked into environments where the hardening checklist was applied once, years ago, on a golden image, and every server since has been cloned from that image without anyone checking whether the settings still match current guidance or whether drift had crept in through manual changes. Treat this checklist as a baseline to re-verify on a schedule, not a box to check once and forget.

    Frequently Asked Questions

    Should I harden SSH before or after setting up the firewall?

    Harden SSH first, and always confirm key-based login works in a second session before disabling password authentication. Once SSH access is confirmed solid, layer the firewall on top so you don't risk combining two changes that could lock you out at once.

    Is changing the SSH port from 22 actually a meaningful security measure?

    It mainly reduces log noise from automated scanners rather than stopping a targeted attacker, since a port scan finds a non-standard port in seconds. It's worth doing because it cuts down noise, but don't treat it as a real security control on its own — key-based auth and rate limiting matter far more.

    How often should I re-run this hardening checklist on existing servers?

    I re-verify core settings quarterly and after any major OS upgrade, since package updates sometimes reset configuration files to distribution defaults. Running a lightweight scanner like Lynis on a schedule catches drift between manual reviews.

    Do I need auditd if I already have centralized logging?

    Yes. Centralized logging typically captures application and syslog output, but auditd captures kernel-level file access and system call events that nothing else on the box records. Ship the auditd logs to your central log system too, so you're not relying on local disk if the host is compromised.

    What's the safest order of operations if I'm hardening a server I can only access remotely?

    Update packages first, then SSH hardening with a verified fallback session, then firewall rules with a rate limit rather than a hard block on the management port, then sysctl and auditd last since those are lower risk of causing a lockout. Always keep an out-of-band console option available as a safety net regardless of order.

    Related Articles