A fresh Linux server with default settings is an open door. I've seen too many compromised boxes where the first step was a dictionary attack on the root password or an exploit against an unpatched service listening on all interfaces.
This checklist walks you through twelve hardening steps that close the most common attack vectors. You won't need exotic tools or deep kernel knowledge—just SSH access and the patience to lock things down properly.
1. Disable Root Login Over SSH
Root access via SSH is the #1 brute-force target. Create a regular user with sudo privileges and turn off root SSH entirely.
Add a new user and give it sudo rights:
adduser yourname
usermod -aG sudo yourname
On CentOS or RHEL, replace sudo with wheel. Test that the new user can run sudo ls /root before you touch the SSH config.
Edit /etc/ssh/sshd_config:
PermitRootLogin no
Restart SSH:
systemctl restart sshd
Log out and back in as your regular user to confirm you can still connect and escalate privileges. Only then close the root session.
2. Use SSH Keys, Disable Password Authentication
Passwords can be guessed. SSH keys cannot. Generate a key pair on your local machine (not the server) if you haven't already:
ssh-keygen -t ed25519 -C "[email protected]"
Copy the public key to the server:
ssh-copy-id yourname@server_ip
Verify key-based login works, then disable password auth in /etc/ssh/sshd_config:
PasswordAuthentication no
ChallengeResponseAuthentication no
Restart sshd and test from a new terminal window before closing your existing session. If you lock yourself out at this stage, you'll need console access to fix it.
3. Change the Default SSH Port
Port 22 gets hammered by automated scanners. Moving SSH to a high port (above 1024, below 65535) drops noise in your auth logs by ninety percent or more.
Pick a port that isn't already in use—check with ss -tuln. Edit /etc/ssh/sshd_config:
Port 2849
If you're running a firewall (you should be; see step 4), open the new port before restarting SSH. Then:
systemctl restart sshd
Connect on the new port:
ssh -p 2849 yourname@server_ip
Leave your old session open until you confirm the new one works.
4. Configure a Firewall (UFW or firewalld)
A firewall blocks everything except the services you explicitly allow. On Debian and Ubuntu, ufw is the simplest choice. On CentOS and RHEL, use firewalld.
UFW (Debian/Ubuntu)
Install it:
sudo apt install ufw
Allow your SSH port (use the port you set in step 3):
sudo ufw allow 2849/tcp
If you're running a web server, allow HTTP and HTTPS:
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
Enable the firewall:
sudo ufw enable
Check status:
sudo ufw status verbose
firewalld (CentOS/RHEL)
It's usually installed by default. Start and enable it:
sudo systemctl start firewalld
sudo systemctl enable firewalld
Allow SSH on your custom port:
sudo firewall-cmd --permanent --add-port=2849/tcp
Allow HTTP and HTTPS:
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
Reload:
sudo firewall-cmd --reload
List active rules:
sudo firewall-cmd --list-all
5. Install and Configure Fail2Ban
Fail2Ban watches your logs and bans IPs that fail authentication too many times. It's your second line of defense after strong SSH config.
Install it:
sudo apt install fail2ban # Debian/Ubuntu
sudo yum install fail2ban # CentOS/RHEL
Copy the default config:
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
Edit /etc/fail2ban/jail.local and find the [sshd] section. Set:
enabled = true
port = 2849
maxretry = 3
bantime = 3600
Restart Fail2Ban:
sudo systemctl restart fail2ban
sudo systemctl enable fail2ban
Check banned IPs:
sudo fail2ban-client status sshd
You'll see bans start appearing within hours if your server is publicly reachable.
6. Keep the System Patched
Unpatched software is the easiest way in. Automate updates so you don't have to remember.
Debian/Ubuntu: unattended-upgrades
Install:
sudo apt install unattended-upgrades
Enable automatic security updates:
sudo dpkg-reconfigure --priority=low unattended-upgrades
Edit /etc/apt/apt.conf.d/50unattended-upgrades to configure which updates to apply. The default installs security patches automatically.
CentOS/RHEL: dnf-automatic
Install:
sudo yum install dnf-automatic
Edit /etc/dnf/automatic.conf and set:
apply_updates = yes
Enable the timer:
sudo systemctl enable --now dnf-automatic.timer
You can still run manual updates (apt update && apt upgrade or yum update) whenever you want. The automation just ensures you don't fall months behind.
7. Disable Unused Services
Every running service is a potential entry point. List what's listening:
sudo ss -tuln
You'll see port numbers and process names. If you don't recognize a service or don't need it, stop and disable it:
sudo systemctl stop service_name
sudo systemctl disable service_name
Common candidates: avahi-daemon, cups, bluetooth. On a headless server, you don't need them.
8. Configure User Permissions and sudo
Never share user accounts. Each person gets their own account and their own SSH key. If someone leaves, you remove one key—not all of them.
Add a new user:
sudo adduser newuser
Give them sudo access only if they need it:
sudo usermod -aG sudo newuser # Debian/Ubuntu
sudo usermod -aG wheel newuser # CentOS/RHEL
For finer control, edit /etc/sudoers with visudo. You can restrict which commands a user can run as root. Example:
newuser ALL=(ALL) /usr/bin/systemctl restart nginx
That line lets newuser restart nginx with sudo but nothing else.
9. Set Up File Integrity Monitoring with AIDE
AIDE (Advanced Intrusion Detection Environment) takes a snapshot of your file system and alerts you when critical files change.
Install:
sudo apt install aide # Debian/Ubuntu
sudo yum install aide # CentOS/RHEL
Initialize the database (this takes a few minutes):
sudo aideinit
On Debian/Ubuntu, copy the new database:
sudo mv /var/lib/aide/aide.db.new /var/lib/aide/aide.db
Run a check:
sudo aide --check
No output means no changes since the baseline. Schedule daily checks with a cron job:
sudo crontab -e
Add:
0 2 * * * /usr/bin/aide --check | mail -s "AIDE report" [email protected]
You'll get an email every morning if something changed in /etc, /bin, or other monitored directories.
10. Harden Kernel Parameters with sysctl
The Linux kernel exposes hundreds of tunable parameters. A few of them improve security with zero downside.
Edit /etc/sysctl.conf (or create a new file in /etc/sysctl.d/) and add:
# Ignore ICMP ping requests
net.ipv4.icmp_echo_ignore_all = 1
# Disable IP forwarding (unless you're building a router)
net.ipv4.ip_forward = 0
net.ipv6.conf.all.forwarding = 0
# Enable SYN cookies (defense against SYN flood)
net.ipv4.tcp_syncookies = 1
# 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:
sudo sysctl -p
Verify:
sysctl net.ipv4.icmp_echo_ignore_all
You should see 1.
11. Enable and Review System Logs
Logs tell you what happened before, during, and after an incident. Make sure logging is enabled and you actually look at the logs.
Key log files:
/var/log/auth.log(Debian/Ubuntu) or/var/log/secure(CentOS/RHEL): SSH logins, sudo usage, authentication failures/var/log/syslogor/var/log/messages: general system messages/var/log/fail2ban.log: Fail2Ban actions
Watch SSH auth attempts in real time:
sudo tail -f /var/log/auth.log
You'll see every connection attempt and whether it succeeded. If you see hundreds of failed logins from unfamiliar IPs, your hardening is working.
For centralized logging or long-term retention, forward logs to a remote syslog server or set up rsyslog with rotation policies.
12. Use a Mandatory Access Control System (AppArmor or SELinux)
MAC systems enforce policies that restrict what processes can do—even if they're running as root. AppArmor (Debian/Ubuntu default) and SELinux (CentOS/RHEL default) add a security layer that stops attackers who gain shell access.
AppArmor (Debian/Ubuntu)
Check status:
sudo aa-status
You'll see a list of profiles in enforce or complain mode. Enforce mode blocks violations; complain mode logs them. Most distros ship with profiles for common services (Apache, nginx, MySQL).
If a service doesn't have a profile, you can generate one with aa-genprof, but that's beyond this checklist. The important part: leave AppArmor enabled and don't disable profiles unless you have a specific reason.
SELinux (CentOS/RHEL)
Check status:
sudo sestatus
SELinux has three modes: enforcing, permissive, and disabled. Enforcing is what you want. If it's disabled, edit /etc/selinux/config:
SELINUX=enforcing
Reboot for the change to take effect. SELinux can be tricky—context errors will prevent services from starting. When that happens, check the audit log:
sudo ausearch -m avc -ts recent
You'll see what was blocked and why. Most issues are fixed by restoring correct file contexts:
sudo restorecon -Rv /path/to/directory
Don't set SELinux to permissive just because something breaks. Fix the underlying issue instead.
What have you patched this month?
These twelve steps cover the baseline. You won't stop a determined attacker with deep pockets, but you will stop opportunistic scans and automated exploit attempts—which account for the majority of compromised servers I've dealt with in support.
Hardening isn't a one-time task. New vulnerabilities appear every week. Set a calendar reminder to review your firewall rules, check for outdated packages, and rotate SSH keys every six months. The effort is small. The cost of a breach is not.
FAQ
Do I need all twelve steps, or can I skip some?
Steps 1-6 are non-negotiable: SSH hardening, firewall, Fail2Ban, and patching. The rest add defense in depth. If you're running a production server or handling customer data, do all twelve.
What if I lock myself out after changing SSH settings?
You'll need console access through your hosting provider's control panel or KVM. That's why you test each SSH change in a new terminal window before closing your existing session.
How often should I update the AIDE database?
After every intentional system change—installing software, editing configs, etc. Run sudo aideinit and replace the old database. Otherwise AIDE will flag your own changes as suspicious.
Can I automate all of this with a script?
Yes, with Ansible, Bash, or similar tools. But walk through it manually at least once so you understand what each step does and how to troubleshoot when something breaks.
Where to focus first
If you only have an hour, do steps 1-4: disable root login, switch to SSH keys, change the default port, and turn on a firewall. That blocks the lowest-hanging fruit. Schedule the remaining steps for later in the week, and don't skip the automated patching—unpatched boxes get owned faster than misconfigured ones.
