Most breached servers I've seen shared one trait: they ran default configs. SSH on port 22 with password auth, no firewall rules, root login enabled. Hardening a fresh Linux box takes ten minutes and stops ninety percent of automated attacks before they start.
This guide walks through eight commands that lock down SSH access, configure a stateful firewall, disable unnecessary services, enforce permission policies, and capture security events. Run these on any Debian, Ubuntu, CentOS, AlmaLinux, or Rocky server before you point a domain at it.
1. Disable root SSH and enforce key-only authentication
Password-based SSH is the easiest door to kick in. Brute-force bots hammer port 22 around the clock.
First, generate an SSH key pair on your local machine if you don't already have one:
ssh-keygen -t ed25519 -C "[email protected]"
Copy the public key to your server:
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@your-server-ip
Confirm you can log in with the key, then edit /etc/ssh/sshd_config:
sudo nano /etc/ssh/sshd_config
Set these directives:
PermitRootLogin no
PasswordAuthentication no
ChallengeResponseAuthentication no
PubkeyAuthentication yes
Restart SSH:
sudo systemctl restart sshd
Test the connection in a new terminal before closing your current session. If the key login fails, you still have the original session to fix it.
2. Change the default SSH port
Port 22 attracts every script kiddie on the planet. Moving SSH to a high port—say 2222 or 49152—drops automated scan traffic to near zero.
In /etc/ssh/sshd_config, change:
Port 49152
Restart SSH:
sudo systemctl restart sshd
From now on, connect with:
ssh -p 49152 user@your-server-ip
Remember to open the new port in your firewall before closing the old one, or you'll lock yourself out.
3. Configure UFW or firewalld with a default-deny policy
A firewall with no rules is a firewall that does nothing. Start with deny-all, then whitelist only the ports you need.
UFW (Ubuntu, Debian)
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 49152/tcp comment 'SSH'
sudo ufw allow 80/tcp comment 'HTTP'
sudo ufw allow 443/tcp comment 'HTTPS'
sudo ufw enable
Check status:
sudo ufw status verbose
firewalld (CentOS, AlmaLinux, Rocky)
sudo firewall-cmd --set-default-zone=drop
sudo firewall-cmd --zone=public --add-port=49152/tcp --permanent
sudo firewall-cmd --zone=public --add-service=http --permanent
sudo firewall-cmd --zone=public --add-service=https --permanent
sudo firewall-cmd --reload
Verify:
sudo firewall-cmd --list-all
Drop any port you don't recognize. If you're running a web server, 80 and 443 stay open. Everything else should have a reason.
4. Install and enable fail2ban
fail2ban watches logs for repeated failed login attempts and bans the offending IP for a configurable period.
Install it:
sudo apt install fail2ban -y # Debian/Ubuntu
sudo yum install fail2ban -y # CentOS/RHEL
Create a local config:
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo nano /etc/fail2ban/jail.local
Under [sshd], confirm:
enabled = true
port = 49152
maxretry = 3
bantime = 3600
Start and enable:
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
Check banned IPs:
sudo fail2ban-client status sshd
In support tickets I handled, fail2ban typically blocks dozens of IPs within the first day on a new server.
5. Disable unused network services
Every listening port is a potential attack surface. List what's running:
sudo ss -tulnp
You'll see SSH, your web server, maybe MySQL. Anything else? Investigate it.
Disable services you don't need:
sudo systemctl disable --now avahi-daemon
sudo systemctl disable --now cups
Common unnecessary services on a typical VPS: Avahi (Zeroconf/Bonjour), CUPS (printing), Bluetooth, and sometimes Postfix if you're using an external SMTP relay.
6. Lock down file permissions on sensitive configs
World-readable config files leak credentials. SSH keys, database passwords, and application secrets should be locked to the owning user.
Set restrictive permissions on common targets:
sudo chmod 700 ~/.ssh
sudo chmod 600 ~/.ssh/authorized_keys
sudo chmod 600 /etc/ssh/sshd_config
sudo chmod 640 /etc/mysql/my.cnf
sudo chown root:root /etc/ssh/sshd_config
For web application config files (like WordPress wp-config.php):
sudo chown www-data:www-data /var/www/html/wp-config.php
sudo chmod 640 /var/www/html/wp-config.php
Run a quick audit:
sudo find /etc -type f -perm -o+w -ls
That finds world-writable files in /etc. Investigate any hits.
7. Enable automatic security updates
Patching is the most effective defense and the one admins skip most often. Automate it.
Debian/Ubuntu (unattended-upgrades)
sudo apt install unattended-upgrades -y
sudo dpkg-reconfigure --priority=low unattended-upgrades
Edit /etc/apt/apt.conf.d/50unattended-upgrades to enable automatic reboots if needed:
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "03:00";
CentOS/AlmaLinux/Rocky (dnf-automatic)
sudo yum install dnf-automatic -y
sudo systemctl enable --now dnf-automatic.timer
Edit /etc/dnf/automatic.conf and set:
apply_updates = yes
Automatic updates won't break a well-architected stack, but test them on a staging server first if you're cautious.
8. Configure auditd to log security events
auditd records file access, system calls, and privilege escalation attempts. It's your black box after an incident.
Install:
sudo apt install auditd -y # Debian/Ubuntu
sudo yum install audit -y # CentOS/RHEL
Add rules 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
-w /var/log/auth.log -p wa -k auth_log_changes
Reload rules:
sudo augenrules --load
sudo systemctl restart auditd
Search logs for specific events:
sudo ausearch -k passwd_changes
auditd logs fill up fast on busy servers. Rotate them regularly and ship to a central log collector if you manage multiple boxes.
What about SELinux or AppArmor?
If your distro ships with SELinux (RHEL family) or AppArmor (Ubuntu), leave it enabled. Both frameworks confine processes to predefined policies, limiting damage if an application gets compromised.
Check SELinux status:
sudo sestatus
If it's disabled, enable it by editing /etc/selinux/config:
SELINUX=enforcing
For AppArmor:
sudo aa-status
Don't disable MAC (mandatory access control) frameworks just because they trigger permission errors. Fix the policy instead.
How do I verify the changes stuck?
Reboot the server and run through this checklist:
- Can you SSH in with your key on the custom port?
- Does password auth fail?
- Is the firewall active and showing your rules?
- Are fail2ban and auditd running?
- Do unused services stay stopped?
sudo systemctl is-active sshd ufw fail2ban auditd
sudo ufw status
sudo fail2ban-client ping
If any service is inactive, re-enable it and check logs:
sudo journalctl -u servicename -n 50
Start with SSH and the firewall
You don't need to run all eight commands in one sitting, but SSH and firewall config should happen before you install anything else. Disable root login, enforce keys, set a default-deny firewall policy, and you've already blocked most opportunistic attacks. The rest—fail2ban, auditd, automatic updates—add layers that catch what slips through. Run these eight commands on every new server and you'll spend less time cleaning up compromised boxes.
