A fresh server is an open door. No firewall rules, root login enabled, a handful of services you'll never use listening on every interface. I've seen breached boxes where the attacker walked in through the default SSH config within hours of deployment.
This checklist gives you twelve practical steps to harden any Linux server—VPS, dedicated, or cloud instance. Run through these before you deploy anything customer-facing, and you'll close the easiest attack vectors.
1. Update the system packages
Start here. Always.
Outdated packages mean known CVEs sitting on your box. Before you configure anything else, pull the latest security patches:
# Debian / Ubuntu
sudo apt update && sudo apt upgrade -y
# RHEL / CentOS / AlmaLinux
sudo dnf upgrade -y
Reboot if the kernel updated. Check with uname -r before and after to confirm.
2. Create a non-root user with sudo access
Never run daily tasks as root. Create a dedicated user account and grant it sudo privileges:
adduser deploy
usermod -aG sudo deploy # Debian/Ubuntu
usermod -aG wheel deploy # RHEL-based
Test the new account by switching to it (su - deploy) and running a privileged command with sudo. Once confirmed, you can disable root login entirely in a later step.
3. Enforce SSH key authentication and disable password login
Password auth is guessable. Key-based auth is not.
Generate an SSH key pair on your local machine if you don't have one:
ssh-keygen -t ed25519 -C "[email protected]"
Copy the public key to the server:
ssh-copy-id deploy@your-server-ip
Verify you can log in with the key. Then edit /etc/ssh/sshd_config:
PasswordAuthentication no
PermitRootLogin no
PubkeyAuthentication yes
Restart SSH:
sudo systemctl restart sshd
Test your key-based login in a new terminal before closing your current session—lock yourself out and you'll need console access to fix it.
4. Change the default SSH port
Port 22 gets hammered by bots. Moving SSH to a non-standard port won't stop a determined attacker, but it drops the noise in your auth logs to near zero.
Pick a port above 1024 and below 65535 that isn't already in use. Edit /etc/ssh/sshd_config:
Port 2849
Restart SSH and update your firewall rules (covered next) to allow the new port. From now on, connect with:
ssh -p 2849 deploy@your-server-ip
5. Configure a firewall with UFW or firewalld
Default policy should be deny. Then open only the ports you need.
On Ubuntu/Debian with UFW:
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 2849/tcp # your SSH port
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
On RHEL-based systems with firewalld:
sudo firewall-cmd --set-default-zone=drop
sudo firewall-cmd --permanent --add-port=2849/tcp
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
Verify with sudo ufw status or sudo firewall-cmd --list-all. If you lock yourself out, you'll need console access through your provider's panel.
6. Install and configure Fail2Ban
Fail2Ban watches your logs and bans IPs after repeated failed login attempts. It's a second line of defense for SSH and any other authentication endpoint.
Install:
sudo apt install fail2ban -y # Debian/Ubuntu
sudo dnf install fail2ban -y # RHEL-based
Create a local config at /etc/fail2ban/jail.local:
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 5
[sshd]
enabled = true
port = 2849
logpath = /var/log/auth.log # or /var/log/secure on RHEL
Restart the service:
sudo systemctl enable fail2ban
sudo systemctl restart fail2ban
Check banned IPs with sudo fail2ban-client status sshd.
7. Disable unused network services
Every listening service is a potential entry point. List what's running:
sudo ss -tuln
You'll see a table of ports and the services bound to them. If you spot something you don't recognize or don't need—old RPC services, a mail server you're not using, anything on 0.0.0.0 that shouldn't be public—disable it.
For systemd-managed services:
sudo systemctl disable --now service-name
Common culprits: rpcbind, avahi-daemon, cups. If you're not doing local printing or mDNS discovery, turn them off.
8. Set up automatic security updates
Patching manually works until you forget. Automate it.
On Debian/Ubuntu, install unattended-upgrades:
sudo apt install unattended-upgrades -y
sudo dpkg-reconfigure -plow unattended-upgrades
Edit /etc/apt/apt.conf.d/50unattended-upgrades to enable security updates and configure reboot behavior if needed.
On RHEL-based systems, use dnf-automatic:
sudo dnf install dnf-automatic -y
sudo systemctl enable --now dnf-automatic.timer
Edit /etc/dnf/automatic.conf and set apply_updates = yes under the [commands] section.
Check logs periodically to confirm updates are applying. Automatic doesn't mean invisible.
9. Configure file permissions and disable unnecessary SUID binaries
SUID binaries run with the owner's privileges. That's useful for tools like passwd, dangerous for anything an attacker can exploit.
Find all SUID files:
sudo find / -perm -4000 -type f 2>/dev/null
Review the list. If you see something unexpected or unused, remove the SUID bit:
sudo chmod u-s /path/to/binary
Also check world-writable directories:
sudo find / -type d -perm -0002 ! -path "/proc/*" 2>/dev/null
Anything outside /tmp or /var/tmp is worth investigating.
10. Enable and configure auditd for logging
You can't investigate what you didn't log. auditd records system calls, file access, and authentication events—essential for forensics after an incident.
Install:
sudo apt install auditd -y # Debian/Ubuntu
sudo dnf install audit -y # RHEL-based
Add a rule to watch sensitive files. Edit /etc/audit/rules.d/audit.rules:
-w /etc/passwd -p wa -k passwd_changes
-w /etc/shadow -p wa -k shadow_changes
-w /etc/ssh/sshd_config -p wa -k sshd_config_changes
Reload rules:
sudo augenrules --load
sudo systemctl restart auditd
Search logs with ausearch -k passwd_changes or aureport for summaries.
11. Harden kernel parameters with sysctl
The kernel exposes dozens of tunable parameters. A few changes block common network attacks.
Edit /etc/sysctl.conf or create a file in /etc/sysctl.d/:
# Disable IP forwarding (unless you're routing traffic)
net.ipv4.ip_forward = 0
# Enable SYN cookies to resist SYN floods
net.ipv4.tcp_syncookies = 1
# Ignore ICMP redirects
net.ipv4.conf.all.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
# Disable source packet routing
net.ipv4.conf.all.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0
# Log martian packets
net.ipv4.conf.all.log_martians = 1
Apply changes:
sudo sysctl -p
These settings won't break normal operations and they close several low-hanging attack vectors.
12. Run a security audit tool
After hardening manually, validate your work with an automated tool. Two solid options:
Lynis scans the system and produces a detailed report with a hardening index:
sudo apt install lynis -y
sudo lynis audit system
Read the report at /var/log/lynis.log. It flags weak permissions, missing packages, and misconfigurations.
OpenSCAP is heavier but useful for compliance checks (CIS benchmarks, STIG):
sudo apt install libopenscap8 -y
Download a benchmark profile and run a scan. Results show which controls passed and which need attention.
Either tool will surface issues you missed. Fix them and re-scan until the score stops climbing.
What about intrusion detection?
If your threat model includes persistent attackers or you handle sensitive data, add a host-based IDS like OSSEC or Wazuh. Both monitor file integrity, parse logs in real time, and alert on suspicious behavior.
For most hosting scenarios, the twelve steps above cover the essentials. IDS adds depth but also complexity—introduce it after you've locked down the basics.
FAQ
Do I need all twelve steps on every server?
Yes. Each step closes a different attack vector. Skipping one because the server "isn't important" just means you'll patch it after the breach.
What if I lock myself out during SSH hardening?
Use your hosting provider's console or VNC access to log in and fix /etc/ssh/sshd_config. That's why you test key auth before disabling passwords.
How often should I re-audit the server?
Run Lynis monthly and after any major configuration change. Set a recurring calendar reminder.
Can I script this entire checklist?
Absolutely. Ansible, Puppet, or a shell script with idempotent checks will let you harden dozens of servers in one pass. Build the playbook once, run it everywhere.
Does changing the SSH port actually help?
It won't stop a targeted attack, but it eliminates 99% of bot traffic. Your auth logs go from thousands of failed attempts per day to nearly zero. That makes real threats easier to spot.
Run the checklist before you deploy
A hardened server isn't invincible, but it forces attackers to work for access instead of handing it to them. Most compromises I've seen in support tickets happened because someone skipped the basics—root login enabled, firewall wide open, SSH on port 22 with password auth.
Go through these twelve steps on every new box. Script them if you manage more than a handful. The hour you spend now is cheaper than the day you'll lose cleaning up after an intrusion.
