Linux server hardening checklist: 20+ actionable steps covering SSH lockdown, firewall rules, kernel tuning, audit logging, and CIS benchmark practices for Ubuntu and Debian.
A Linux server hardening checklist is the difference between a server that survives in production and one that gets compromised within days. Every fresh Ubuntu or Debian installation ships with sensible defaults for general use — not for facing the internet. Default SSH allows password logins, the kernel forwards packets it shouldn't, and audit logging is turned off. This guide gives you 20+ concrete hardening steps you can work through in order, with real config files and commands. I use this checklist on every server I deploy, and I update it as new CIS benchmark recommendations come out.
Why a Hardening Checklist Matters for Production Servers
Ad-hoc security doesn't scale. When you set up three servers by memory, each one ends up slightly different — one has Fail2Ban, another doesn't; one has kernel hardening, the other forgot. A written checklist forces consistency. If you ever need to rebuild a server, you follow the same steps and get the same result.
The CIS (Center for Internet Security) publishes detailed benchmarks for Ubuntu and Debian. They run 200+ pages. This article distills the most impactful steps into a practical checklist that takes about an hour to work through. I skip the ones that only matter in very specific compliance environments (FIPS, for example) and focus on what actually prevents breaches on a typical web or application server.
If you have already followed my VPS security guide, some of these steps will overlap. This checklist goes deeper — covering kernel parameters, SUID auditing, audit logging, and file system hardening that the VPS guide only touches briefly.
System Updates and Package Hygiene
Before hardening anything else, bring the system to a clean baseline. Outdated packages are the easiest entry point for attackers — known CVEs with public exploits get scanned for constantly.
sudo apt update && sudo apt upgrade -y
sudo apt autoremove -y
sudo apt install -y unattended-upgrades apt-listchanges
Enable automatic security updates so you don't have to remember to run this manually. On Ubuntu, unattended-upgrades handles this:
sudo dpkg-reconfigure -plow unattended-upgrades
Select "Yes" when prompted. The configuration lives at /etc/apt/apt.conf.d/50unattended-upgrades. By default it pulls from the -security origin only, which is exactly what you want — security fixes without unexpected feature changes. I keep Automatic-Reboot set to "false" and schedule kernel reboots manually during maintenance windows. For a deeper look at this, check the Linux reference.
Remove Unnecessary Packages
Every installed package is potential attack surface. If you don't need a desktop environment, mail server, or FTP daemon, remove them:
sudo apt purge -y telnet rsh-client rsh-redone-client
sudo apt purge -y xinetd nis
dpkg --list | grep -i xserver
On a headless server, there should be no X server packages. If dpkg lists any, purge them. The principle is simple: what isn't installed can't be exploited.
User Accounts and Sudo Hardening
Running everything as root is the fastest way to turn a small mistake into a full compromise. Create a dedicated admin user with sudo access and lock down root.
adduser deployer
usermod -aG sudo deployer
Before disabling root login, open a second terminal and confirm that deployer can SSH in and run sudo commands. I have locked myself out of servers by being impatient with this step — don't skip the verification.
Password Policies
Even with key-based SSH (which you should use), local password quality matters for sudo and console access. Install libpam-pwquality and configure minimum requirements:
sudo apt install -y libpam-pwquality
Edit /etc/security/pwquality.conf:
# /etc/security/pwquality.conf
minlen = 12
minclass = 3
maxrepeat = 3
dcredit = -1
ucredit = -1
lcredit = -1
This requires at least 12 characters with at least 3 character classes (uppercase, lowercase, digits, special). maxrepeat = 3 prevents strings like aaaa. These settings align with the CIS benchmark Level 1 recommendations for Ubuntu 22.04+.
SSH Access Hardening for Linux Servers
SSH is the front door. If this is weak, nothing else matters. Edit /etc/ssh/sshd_config and set these values:
# /etc/ssh/sshd_config
PermitRootLogin no
PubkeyAuthentication yes
PasswordAuthentication no
PermitEmptyPasswords no
MaxAuthTries 3
MaxSessions 3
ClientAliveInterval 300
ClientAliveCountMax 2
X11Forwarding no
AllowTcpForwarding no
AllowAgentForwarding no
Banner /etc/issue.net
LoginGraceTime 30
Key decisions explained: PermitRootLogin no forces all access through a named user with sudo — this creates an audit trail. AllowTcpForwarding no prevents attackers from using a compromised SSH session as a tunnel. LoginGraceTime 30 gives 30 seconds to authenticate before the connection drops, reducing the window for brute-force scripts. Apply with:
sudo sshd -t && sudo systemctl restart sshd
The sshd -t test catches syntax errors before the restart. If the test fails, fix the config — restarting with a broken config can lock you out. Always keep your current session open until you verify a new connection works in a separate terminal.
Optional: Limit SSH to Specific Users
If you know exactly who should have SSH access, add an AllowUsers directive:
AllowUsers deployer monitoring
This rejects connections for any other user before authentication even begins. On servers with multiple user accounts (database users, application service accounts), this is an effective layer.
Firewall Configuration with UFW
Default-deny incoming and explicitly allow only what you need. I use UFW on every Ubuntu/Debian server because the syntax maps directly to intent:
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp comment 'SSH'
sudo ufw allow 80/tcp comment 'HTTP'
sudo ufw allow 443/tcp comment 'HTTPS'
sudo ufw --force enable
sudo ufw status verbose
The comment flag documents why each rule exists — invaluable when you revisit a server months later. If you change SSH to a non-standard port, update the rule before removing port 22. For a detailed walkthrough, see my VPS server security guide.
Rate Limiting SSH
UFW has a built-in rate limiter that blocks IPs with more than 6 connection attempts within 30 seconds:
sudo ufw limit 22/tcp comment 'SSH rate limit'
This replaces the basic allow rule for SSH and adds brute-force protection without installing Fail2Ban. For stronger protection, Fail2Ban is still worth adding — it offers customizable ban times and jail configurations.
File System Permissions and SUID Auditing
How do I find suspicious SUID binaries on Linux?
SUID (Set User ID) binaries run with the owner's privileges regardless of who executes them. A vulnerable SUID binary owned by root is a direct path to privilege escalation. You can find all SUID binaries on your system with a single find command — then compare the results against known legitimate binaries. Anything unexpected needs investigation. Audit them regularly:
sudo find / -perm -4000 -type f -exec ls -la {} \; 2>/dev/null
Common legitimate SUID binaries include /usr/bin/sudo, /usr/bin/passwd, and /usr/bin/chfn. Anything unexpected — especially in /tmp, /home, or /opt — should be investigated. Remove the SUID bit from binaries that don't need it:
sudo chmod u-s /path/to/suspicious-binary
Mount Options for /tmp and /var/tmp
Temporary directories are a common staging ground for exploits. Mount them with restrictive options by adding to /etc/fstab:
# /etc/fstab — restrict /tmp
tmpfs /tmp tmpfs defaults,noexec,nosuid,nodev,size=512M 0 0
noexec prevents execution of binaries in /tmp. nosuid ignores SUID bits. nodev blocks device files. These three options together eliminate the most common attack patterns that use /tmp as a staging area. Apply changes with sudo mount -o remount /tmp or reboot.
Kernel and Network Stack Hardening with sysctl
What does sysctl hardening actually protect against?
The Linux kernel exposes tunable parameters through /proc/sys/. Without hardening, the defaults allow IP spoofing, SYN flood attacks, ICMP redirect abuse, and source-routed packets — all common attack vectors against internet-facing servers. A hardened sysctl config closes these gaps at the kernel level, before traffic ever reaches your applications. Create a hardening file at /etc/sysctl.d/99-hardening.conf:
# /etc/sysctl.d/99-hardening.conf
# Disable IP forwarding (not a router)
net.ipv4.ip_forward = 0
net.ipv6.conf.all.forwarding = 0
# SYN flood protection
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_syn_backlog = 2048
net.ipv4.tcp_synack_retries = 2
# Ignore ICMP redirects (prevent MITM via routing manipulation)
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
# Don't send ICMP redirects
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0
# Reverse path filtering (anti-spoofing)
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
# Ignore broadcast pings (prevent smurf attacks)
net.ipv4.icmp_echo_ignore_broadcasts = 1
# Log suspicious packets
net.ipv4.conf.all.log_martians = 1
net.ipv4.conf.default.log_martians = 1
# Disable source routing
net.ipv4.conf.all.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0
# Harden ASLR
kernel.randomize_va_space = 2
# Restrict kernel pointers and dmesg
kernel.kptr_restrict = 2
kernel.dmesg_restrict = 1
Apply immediately without rebooting:
sudo sysctl --system
These settings cover the CIS benchmark Level 1 network parameters. tcp_syncookies is the single most important one — without it, a SYN flood can exhaust the connection table and take down your web server. kptr_restrict = 2 hides kernel addresses from non-root users, making kernel exploits significantly harder. dmesg_restrict = 1 prevents unprivileged users from reading kernel ring buffer messages, which can leak sensitive hardware and driver information.
Audit Logging and Intrusion Detection
If someone compromises your server, logs are the only way to understand what happened. Install the Linux Audit Framework:
sudo apt install -y auditd audispd-plugins
sudo systemctl enable --now auditd
Add rules to monitor critical files. Create /etc/audit/rules.d/hardening.rules:
# Monitor user/group changes
-w /etc/passwd -p wa -k identity
-w /etc/group -p wa -k identity
-w /etc/shadow -p wa -k identity
-w /etc/gshadow -p wa -k identity
# Monitor SSH config changes
-w /etc/ssh/sshd_config -p wa -k sshd_config
# Monitor sudo configuration
-w /etc/sudoers -p wa -k sudoers
-w /etc/sudoers.d/ -p wa -k sudoers
# Monitor cron jobs (persistence mechanism)
-w /etc/crontab -p wa -k cron
-w /var/spool/cron/ -p wa -k cron
# Monitor kernel module loading
-w /sbin/insmod -p x -k modules
-w /sbin/modprobe -p x -k modules
Load the rules:
sudo augenrules --load
sudo auditctl -l
Now any write or attribute change to /etc/passwd, /etc/shadow, SSH config, or cron jobs triggers an audit log entry. Search with sudo ausearch -k identity to find all identity-related events. In my experience, monitoring /etc/crontab and /var/spool/cron/ is critical — cron is one of the most common persistence mechanisms after a breach.
Disabling Unnecessary Services and Open Ports
Every listening service is a potential entry point. List what is currently listening:
sudo ss -tlnp
You should see your SSH daemon, and any web server or database you run — nothing else. Common services to disable on a hardened server:
sudo systemctl disable --now avahi-daemon
sudo systemctl disable --now cups
sudo systemctl disable --now rpcbind
avahi-daemon (mDNS/DNS-SD) and cups (printing) have no place on a headless server. rpcbind is needed for NFS — if you don't use NFS, remove it. After disabling, run ss -tlnp again and verify the list matches your expectations.
Additional Hardening Measures
Should I disable core dumps on a production server?
Yes. Core dumps can contain passwords, encryption keys, and other secrets from crashed processes. On a production server there is rarely a need to debug crashes at the core dump level — and the security risk of leaking sensitive memory contents outweighs the debugging benefit. Disable them:
# /etc/security/limits.conf
* hard core 0
Also add to /etc/sysctl.d/99-hardening.conf:
fs.suid_dumpable = 0
Login Banner
A login banner at /etc/issue.net serves a legal purpose — it establishes that unauthorized access is prohibited. This matters if you ever need to prosecute:
Authorized access only. All activity is monitored and logged.
Reference it in your SSH config with Banner /etc/issue.net (already included in our sshd_config above).
Set Proper umask
The default umask of 022 allows group and other users to read new files. For a production server, tighten this to 027 in /etc/login.defs:
# /etc/login.defs
UMASK 027
New files created by users will be readable only by the owner and group, not by others.
The Full Linux Server Hardening Checklist
Here is the consolidated checklist. Work through it top to bottom on every new server:
- Run
apt update && apt upgrade -y— patch everything first - Enable
unattended-upgradesfor automatic security patches - Remove unnecessary packages (telnet, rsh, X server, cups)
- Create a non-root admin user with sudo access
- Configure password quality with
libpam-pwquality(min 12 chars, 3 classes) - Set up SSH key authentication and copy keys to the server
- Harden
/etc/ssh/sshd_config— disable root login, passwords, forwarding - Test SSH config with
sshd -tbefore restarting - Install and enable UFW — default deny incoming, allow SSH/HTTP/HTTPS
- Enable UFW rate limiting on SSH (
ufw limit 22/tcp) - Audit SUID binaries with
find / -perm -4000 - Mount
/tmpwithnoexec,nosuid,nodev - Apply sysctl kernel hardening (SYN cookies, reverse path filtering, ASLR)
- Restrict kernel pointers and dmesg access
- Install and configure
auditdwith rules for critical files - Monitor
/etc/passwd,/etc/shadow, SSH config, cron, sudo changes - Disable unnecessary services (avahi, cups, rpcbind)
- Verify open ports with
ss -tlnp— nothing unexpected - Disable core dumps (
* hard core 0) - Set login banner in
/etc/issue.net - Tighten umask to 027 in
/etc/login.defs - Restrict cron to authorized users via
/etc/cron.allow
Beyond the Checklist — Continuous Server Security
How often should I audit my server security?
At minimum, run through the critical items monthly: check auditd logs for unexpected changes, verify open ports with ss -tlnp, review ufw rules, and confirm unattended-upgrades is running. After any major change — new services, new users, OS upgrades — run the full checklist again.
A hardening checklist is a snapshot — it makes the server secure today. Keeping it secure requires ongoing work. Tools like Docker Compose add isolation layers, but they don't replace host-level hardening.
For servers running web applications, the next steps are a reverse proxy with SSL (Nginx + Let's Encrypt), application-level firewall rules, and centralized log shipping. If you're managing more than a handful of servers, consider automating this checklist with Ansible — but start by understanding each step manually. Automation without understanding just moves the risk somewhere you can't see it.