A fresh server feels like a clean slate. It's also a wide-open door. Default configurations prioritize convenience over security, and attackers know it—automated bots start probing SSH within minutes of a new IP going live. I've restored servers after breaches that could have been prevented with thirty minutes of hardening work.
This checklist covers twelve configuration changes that form the security baseline for any Linux server. Skip them and you're gambling. Apply them and you close the easy entry points.
1. Disable root SSH login
Root access over SSH is the first thing brute-force scripts try. Disable it.
Edit /etc/ssh/sshd_config and set:
PermitRootLogin no
Restart SSH:
systemctl restart sshd
From now on you'll SSH as a regular user and escalate with sudo. If you don't have a non-root user yet, create one first:
adduser deployuser
usermod -aG sudo deployuser
Test the new user's SSH access before closing your root session. I've locked myself out by skipping this step.
2. Use SSH key authentication only
Passwords can be guessed. Keys cannot.
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 your server:
ssh-copy-id deployuser@your-server-ip
Now disable password authentication. Back in /etc/ssh/sshd_config:
PasswordAuthentication no
ChallengeResponseAuthentication no
PubkeyAuthentication yes
Restart SSH again. Verify you can still connect with your key before logging out.
3. Change the default SSH port
Port 22 gets hammered constantly. Moving SSH to a non-standard port won't stop a determined attacker but it will eliminate 99% of the automated noise in your logs.
In /etc/ssh/sshd_config:
Port 2849
Pick any unused port above 1024. Restart SSH and update your firewall rules to allow the new port before you close your session. Then connect using:
ssh -p 2849 deployuser@your-server-ip
Your auth logs will get quieter immediately.
4. Configure a firewall with default-deny
A firewall should block everything except the specific ports your services need. On Ubuntu and Debian, ufw makes this simple.
Install it:
apt install ufw
Set default policies:
ufw default deny incoming
ufw default allow outgoing
Allow your SSH port:
ufw allow 2849/tcp
If you're running a web server:
ufw allow 80/tcp
ufw allow 443/tcp
Enable the firewall:
ufw enable
Check status:
ufw status verbose
On RHEL-based systems use firewalld instead. The principle is the same: deny everything, then poke specific holes.
5. Install fail2ban
Fail2ban watches your logs and bans IPs that show malicious behavior—repeated failed logins, scanning attempts, exploit probes.
Install:
apt install fail2ban
Copy the default config:
cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
Edit /etc/fail2ban/jail.local and enable the SSH jail:
[sshd]
enabled = true
port = 2849
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600
Restart fail2ban:
systemctl restart fail2ban
Check banned IPs:
fail2ban-client status sshd
In support tickets I handled, servers without fail2ban averaged thousands of failed login attempts per day. With it, attackers get three tries and then they're gone.
6. Enable automatic security updates
Patching is not optional. Unpatched servers get compromised.
On Debian/Ubuntu, install unattended-upgrades:
apt install unattended-upgrades
dpkg-reconfigure --priority=low unattended-upgrades
Edit /etc/apt/apt.conf.d/50unattended-upgrades and ensure security updates are enabled:
Unattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}-security";
};
On RHEL/CentOS, use yum-cron or dnf-automatic. The goal is the same: security patches apply automatically without waiting for manual intervention.
You still need to reboot for kernel updates. Check /var/run/reboot-required on Debian systems or look for a new kernel in /boot.
7. Disable unused services
Every running service is a potential attack surface. List all enabled services:
systemctl list-unit-files --type=service --state=enabled
Do you really need Avahi? Bluetooth? Cups? Disable what you don't use:
systemctl disable avahi-daemon
systemctl stop avahi-daemon
I've seen fresh installs running a dozen services the server will never need. Each one is code that could have a vulnerability.
8. Configure audit logging with auditd
Audit logs record who did what and when. If someone compromises your server, these logs show you how.
Install:
apt install auditd audispd-plugins
Add rules to watch critical files. Create /etc/audit/rules.d/custom.rules:
-w /etc/passwd -p wa -k passwd_changes
-w /etc/shadow -p wa -k shadow_changes
-w /etc/sudoers -p wa -k sudoers_changes
-w /var/log/auth.log -p wa -k auth_log_changes
Reload rules:
augenrules --load
Search logs:
ausearch -k passwd_changes
Audit logs catch privilege escalation attempts and unauthorized file modifications that regular logs miss.
9. Set up log rotation and remote backup
Logs fill disks and attackers delete logs. Fix both problems.
Logrotate is usually installed by default. Check /etc/logrotate.d/rsyslog:
/var/log/syslog
/var/log/auth.log
{
rotate 7
daily
missingok
compress
delaycompress
}
For remote backup, forward logs to a separate syslog server or a hosted logging service. In /etc/rsyslog.conf:
*.* @@logserver.example.com:514
Restart rsyslog:
systemctl restart rsyslog
If an attacker wipes your local logs, you still have copies somewhere they can't reach.
10. Harden kernel parameters with sysctl
The kernel has dozens of security-relevant settings. Most distributions ship with reasonable defaults but a few tweaks help.
Edit /etc/sysctl.conf and add:
# Disable IP forwarding
net.ipv4.ip_forward = 0
# Ignore ICMP redirects
net.ipv4.conf.all.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
# Ignore source-routed packets
net.ipv4.conf.all.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0
# Enable SYN cookies
net.ipv4.tcp_syncookies = 1
# Log martians
net.ipv4.conf.all.log_martians = 1
Apply:
sysctl -p
These settings protect against IP spoofing and some DoS attacks.
11. Restrict cron and at to authorized users
Scheduled jobs are a common persistence mechanism for attackers. Limit who can create them.
Create /etc/cron.allow and /etc/at.allow with just the usernames that need cron access:
root
deployuser
If these files exist, only listed users can use cron and at. Everyone else is denied by default.
Also review existing cron jobs:
ls -la /etc/cron.*
crontab -l -u root
Unfamiliar entries? Investigate them.
12. Enable and configure AppArmor or SELinux
Mandatory access control (MAC) systems confine processes even if they're compromised. AppArmor ships with Ubuntu, SELinux with RHEL.
Check AppArmor status:
aa-status
If profiles are in complain mode, switch them to enforce:
aa-enforce /etc/apparmor.d/*
SELinux status:
getenforce
If it's permissive, set it to enforcing in /etc/selinux/config:
SELINUX=enforcing
Then reboot. MAC systems have a learning curve but they limit what an attacker can do even if they get shell access.
What happens after hardening?
You'll notice quieter logs, fewer alerts, and—most importantly—no evidence of compromise. Hardening doesn't make your server invincible but it raises the bar high enough that automated attacks move on to easier targets.
Schedule time every quarter to review configurations, check for new CVEs affecting your stack, and audit user accounts. Security is not a one-time checklist; it's a maintenance task. But these twelve changes give you the foundation everything else builds on.
FAQ
Do I need all twelve changes or can I skip some?
You need all of them. Each one closes a different attack vector. Skipping steps leaves gaps.
Will changing the SSH port break my automation?
Only if your scripts hard-code port 22. Update your SSH configs and automation to use the new port. It takes five minutes.
Can I run fail2ban and a firewall together?
Yes. They work at different layers. The firewall controls which ports are open; fail2ban bans IPs that abuse those open ports.
What if automatic updates break something?
Security updates are tested heavily before release. Breakage is rare. If you're worried, configure automatic updates for security patches only and handle feature upgrades manually.
How do I know if my server was compromised before I hardened it?
Check auth logs for successful logins you don't recognize, review running processes, examine cron jobs, and scan for unusual network connections. If you find anything suspicious, assume compromise and rebuild from a clean image.
What to check first
Start with SSH and the firewall—those are your biggest attack surfaces. Then add fail2ban and automatic updates. The rest of the checklist can follow over the next day or two, but get the first four done in the first hour. Your server is under active scan right now. Don't give attackers time to find an opening.
Every server I manage follows this baseline. The ones that don't are the ones that eventually appear in my inbox with "urgent: server compromised" in the subject line.
