Most server breaches I've handled started with something embarrassingly simple: a weak SSH password, an unpatched kernel, or firewall rules left wide open. The attacks aren't sophisticated. They're automated scanners probing thousands of servers an hour, looking for the easiest target.
You don't need a $50,000 security appliance or a SOC team to stop them. You need nine baseline controls, correctly configured and maintained. These aren't exotic zero-day defenses—they're the boring, proven techniques that block the attacks you'll actually face.
1. SSH Key Authentication (and Rotate Those Keys)
Password authentication over SSH is the single biggest gift you can give an attacker. Disable it.
Generate an Ed25519 key pair on your local machine:
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
Then edit /etc/ssh/sshd_config and set these directives:
PasswordAuthentication no
PubkeyAuthentication yes
PermitRootLogin no
ChallengeResponseAuthentication no
Restart SSH:
systemctl restart sshd
Now rotate those keys every 90-180 days. I've seen compromised workstations where an SSH private key sat in a backup folder for three years. Generate a new pair, deploy the new public key, revoke the old one from ~/.ssh/authorized_keys on every server. Make it a calendar reminder.
2. Fail2ban with Aggressive Jail Rules
Fail2ban watches your logs and bans IPs after repeated failed login attempts. Out of the box it's too forgiving.
Install it:
apt install fail2ban # Debian/Ubuntu
yum install fail2ban # RHEL/CentOS
Create /etc/fail2ban/jail.local and tighten the defaults:
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 3
[sshd]
enabled = true
port = ssh
logpath = /var/log/auth.log
maxretry = 3
bantime = 7200
Three failed attempts in ten minutes gets you banned for two hours. Restart fail2ban:
systemctl restart fail2ban
Check active jails and banned IPs:
fail2ban-client status sshd
I've watched fail2ban block hundreds of brute-force attempts per day on a single server. It's not a silver bullet—an attacker can rotate IPs—but it raises the cost enough that most bots move on.
3. Change the Default SSH Port
Security through obscurity isn't a strategy, but moving SSH off port 22 drops your log noise by 95%. The drive-by scanners hit 22 and nothing else.
Edit /etc/ssh/sshd_config:
Port 2849
Pick a high port (1024-65535) that isn't already in use. Restart SSH and update your firewall rules to allow the new port:
ufw allow 2849/tcp
ufw delete allow 22/tcp
Your SSH commands now need the -p flag:
ssh -p 2849 user@your-server
This won't stop a determined attacker, but it stops the lazy 99%.
4. Kernel Hardening with sysctl
The Linux kernel ships with settings optimized for compatibility, not security. You can tighten them.
Edit /etc/sysctl.conf or create /etc/sysctl.d/99-hardening.conf:
# Disable IP forwarding
net.ipv4.ip_forward = 0
net.ipv6.conf.all.forwarding = 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 TCP SYN cookies (SYN flood protection)
net.ipv4.tcp_syncookies = 1
# Log martian packets
net.ipv4.conf.all.log_martians = 1
# Disable ICMP echo requests (optional)
net.ipv4.icmp_echo_ignore_all = 1
Apply the changes:
sysctl -p
These tweaks won't break anything unless you're running a router or need specific routing protocols. They close off a handful of network-layer attack vectors that crop up in support tickets more often than you'd think.
5. Firewall: Default Deny, Explicit Allow
Your firewall should block everything except the ports you explicitly need. If you're running ufw (Debian/Ubuntu), start clean:
ufw default deny incoming
ufw default allow outgoing
ufw allow 2849/tcp # SSH on custom port
ufw allow 80/tcp # HTTP
ufw allow 443/tcp # HTTPS
ufw enable
For firewalld (RHEL/CentOS):
firewall-cmd --set-default-zone=drop
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --permanent --add-port=2849/tcp
firewall-cmd --reload
Review open ports regularly:
ufw status verbose
ss -tuln
Every open port is a potential attack surface. Close what you don't need.
So what if you need more granular control?
Use firewall zones or IP whitelisting for admin services. If you only SSH from a fixed office IP, lock SSH to that IP:
ufw allow from 203.0.113.50 to any port 2849 proto tcp
For databases, allow connections only from your app server's internal IP. Never expose MySQL or PostgreSQL to the internet.
6. Automatic Security Updates (Unattended Upgrades)
Manual patching is a fantasy. You'll forget, you'll be busy, or you'll wait for a "maintenance window" that never comes. Automate it.
On Debian/Ubuntu, install and configure unattended-upgrades:
apt install unattended-upgrades
dpkg-reconfigure -plow unattended-upgrades
Edit /etc/apt/apt.conf.d/50unattended-upgrades to auto-reboot if needed:
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "03:00";
On RHEL/CentOS, enable automatic updates with dnf-automatic:
yum install dnf-automatic
systemctl enable --now dnf-automatic.timer
Edit /etc/dnf/automatic.conf and set:
apply_updates = yes
Yes, auto-reboots can be disruptive. But an unpatched kernel is worse. Schedule reboots for low-traffic windows and monitor for issues.
7. Audit Logging with auditd
When something goes wrong, you need to know who did what and when. The auditd daemon records system calls, file access, and authentication events.
Install it:
apt install auditd audispd-plugins # Debian/Ubuntu
yum install audit # RHEL/CentOS
Start the service:
systemctl enable --now auditd
Add rules to watch sensitive files. Edit /etc/audit/rules.d/audit.rules:
# Watch changes to passwd and shadow files
-w /etc/passwd -p wa -k passwd_changes
-w /etc/shadow -p wa -k shadow_changes
# Watch SSH config changes
-w /etc/ssh/sshd_config -p wa -k sshd_config_changes
# Watch sudo usage
-w /var/log/sudo.log -p wa -k sudo_log_changes
Reload the rules:
augenrules --load
Search the audit log:
ausearch -k passwd_changes
In support tickets where an account was compromised, audit logs let you trace the exact sequence of commands the attacker ran. Without them, you're guessing.
8. Disable Unnecessary Services
Every running service is a potential vulnerability. List active services:
systemctl list-units --type=service --state=running
Disable anything you don't recognize or need:
systemctl disable --now cups.service
Common candidates: print services (cups), Bluetooth (bluetooth.service), Avahi (avahi-daemon.service). If you're running a headless server, you don't need them.
Check listening ports:
ss -tuln
If you see a port you can't explain, track down the process:
sudo lsof -i :8080
Kill it or disable the service.
9. Intrusion Detection with AIDE
AIDE (Advanced Intrusion Detection Environment) creates a baseline snapshot of your filesystem and alerts you when critical files change.
Install it:
apt install aide
Initialize the database:
aide --init
mv /var/lib/aide/aide.db.new /var/lib/aide/aide.db
Run a check:
aide --check
Schedule daily checks with cron:
echo "0 5 * * * root /usr/bin/aide --check | mail -s 'AIDE Report' [email protected]" >> /etc/crontab
AIDE will flag unauthorized changes to binaries, config files, or system libraries. It's a canary in the coal mine for rootkits and backdoors.
What to Check First
These nine controls aren't glamorous. They don't involve machine learning or threat intelligence feeds. But they stop the attacks you'll actually face: brute-force SSH, unpatched exploits, and opportunistic bots.
Start with SSH keys and fail2ban—those two alone eliminate most drive-by attacks. Add the firewall rules and automatic updates next. Kernel hardening, audit logging, and AIDE take more time to configure, but they're worth it the first time you need to investigate an incident.
Security isn't a one-time setup. Review your config every quarter, rotate your SSH keys, and watch your logs. The attackers aren't getting less persistent.
FAQ
Do I really need to rotate SSH keys if they're already secure?
Yes. Workstations get compromised, backups get leaked, and ex-employees keep copies. Regular rotation limits the damage if a key is exposed.
Will automatic updates break my server?
Rarely, but it can happen. Test updates in staging first if you have that luxury. The risk of running unpatched software is higher than the risk of a bad update.
Can I use password authentication if I add fail2ban?
No. Fail2ban raises the cost of brute-force attacks but doesn't eliminate them. Password authentication is inherently weaker than key-based auth.
How often should I review audit logs?
Weekly at minimum. Set up automated alerts for high-priority events like changes to /etc/passwd or repeated sudo failures.
Is changing the SSH port really worth it?
Yes, purely for noise reduction. Your logs will be cleaner and you'll spot real threats faster when you're not wading through thousands of failed attempts on port 22.
Your Next Steps
Pick three controls from this list and implement them today. SSH keys, fail2ban, and firewall rules take less than an hour. Schedule the rest over the next two weeks. Security is a habit, not a checklist you complete once.
Check your logs weekly, rotate your keys quarterly, and keep your kernel patched. The automated attacks won't stop, but your server won't be the easy target they're looking for.
