A freshly deployed Linux server is like a house with unlocked windows. The OS vendor ships it ready to run, not ready to resist. I've seen servers compromised within hours of going live because someone skipped the basics—default SSH port open, root login enabled, no firewall.
This checklist walks you through twelve hardening steps that stop most automated attacks and make targeted ones much harder. You won't need exotic tools or a security degree. Just SSH access, root privileges, and thirty minutes.
1. Disable root SSH login and use key authentication
Password authentication over SSH is the easiest vector for brute-force bots. They hammer common usernames and passwords around the clock.
First, create a non-root user with sudo privileges if you haven't already:
adduser deployuser
usermod -aG sudo deployuser
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 deployuser@your_server_ip
Test the key login in a new terminal before locking yourself out. Once confirmed, edit /etc/ssh/sshd_config:
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
Restart SSH:
systemctl restart sshd
Root can no longer log in remotely. Bots targeting root passwords hit a wall.
2. Change the default SSH port
Port 22 gets scanned constantly. Moving SSH to a non-standard port won't stop a determined attacker, but it drops the noise in your logs by 90%.
In /etc/ssh/sshd_config, change:
Port 2849
Pick any unused port above 1024. Update your firewall rules before restarting SSH or you'll lock yourself out:
ufw allow 2849/tcp
systemctl restart sshd
Next login, specify the port:
ssh -p 2849 deployuser@your_server_ip
3. Configure a firewall with minimal open ports
Default policies should deny everything except what you explicitly need. UFW makes this simple on Ubuntu and Debian:
ufw default deny incoming
ufw default allow outgoing
ufw allow 2849/tcp # your SSH port
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable
For CentOS or RHEL, use firewalld:
firewall-cmd --set-default-zone=drop
firewall-cmd --permanent --add-port=2849/tcp
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --reload
Verify with ufw status or firewall-cmd --list-all. If a port isn't serving a public service, close it.
4. Install and configure fail2ban
Fail2ban watches log files for repeated failed authentication attempts and temporarily bans offending IPs.
Install it:
apt install fail2ban # Debian/Ubuntu
yum install fail2ban # CentOS/RHEL
Copy the default config:
cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
Edit /etc/fail2ban/jail.local and adjust the SSH jail:
[sshd]
enabled = true
port = 2849
maxretry = 3
bantime = 3600
findtime = 600
Start and enable fail2ban:
systemctl enable fail2ban
systemctl start fail2ban
Check banned IPs:
fail2ban-client status sshd
In support tickets I handled, fail2ban stopped most SSH brute-force campaigns within the first hour.
5. Enforce strong user permissions and disable unused accounts
Every active user account is a potential entry point. Audit your user list:
cat /etc/passwd | grep -v nologin | grep -v false
Lock or remove accounts you don't recognize or don't need:
usermod -L olduser # lock
userdel -r olduser # remove with home directory
Set a strong password policy. Edit /etc/login.defs:
PASS_MAX_DAYS 90
PASS_MIN_DAYS 1
PASS_MIN_LEN 12
PASS_WARN_AGE 7
For existing users, enforce expiration:
chage -M 90 deployuser
Check sudo privileges:
cat /etc/sudoers.d/*
Remove sudo from users who don't need it.
6. Harden kernel parameters with sysctl
The kernel exposes tunable parameters that control networking and process behavior. A few tweaks reduce your attack surface.
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 send redirects
net.ipv4.conf.all.send_redirects = 0
# Enable SYN cookies
net.ipv4.tcp_syncookies = 1
# Log martian packets
net.ipv4.conf.all.log_martians = 1
# Ignore ICMP ping requests
net.ipv4.icmp_echo_ignore_all = 1
Apply changes:
sysctl -p
These settings won't break normal services but make reconnaissance and spoofing attacks harder.
7. Enable SELinux or AppArmor
Mandatory access control systems confine processes to the minimum permissions they need. Even if an attacker exploits a service, the damage stays contained.
On RHEL-based systems, check SELinux status:
getenforce
If it returns Permissive or Disabled, enable it. Edit /etc/selinux/config:
SELINUX=enforcing
Reboot. SELinux in enforcing mode will block unauthorized file access and network binds.
On Ubuntu, AppArmor is usually active by default:
aa-status
If not, install and enable it:
apt install apparmor apparmor-utils
systemctl enable apparmor
systemctl start apparmor
Don't disable SELinux or AppArmor just because an application complains. Fix the policy or the application's behavior instead.
8. Configure centralized audit logging
You can't investigate what you didn't log. The auditd daemon records security-relevant events—file access, system calls, authentication attempts.
Install auditd:
apt install auditd # Debian/Ubuntu
yum install audit # CentOS/RHEL
Start and enable it:
systemctl enable auditd
systemctl start auditd
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/sudoers -p wa -k sudoers_changes
-w /var/log/auth.log -p wa -k auth_log_changes
Reload rules:
augenrules --load
Search audit logs:
ausearch -k passwd_changes
Ship logs to a remote syslog server if you manage multiple machines. Local logs disappear if the server gets rooted.
9. Keep packages updated and enable automatic security patches
Unpatched software is the low-hanging fruit attackers pick first. Zero-days are rare; exploiting a six-month-old CVE is common.
On Debian/Ubuntu, enable unattended upgrades:
apt install unattended-upgrades
dpkg-reconfigure --priority=low unattended-upgrades
Edit /etc/apt/apt.conf.d/50unattended-upgrades to allow automatic security updates:
Unattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}-security";
};
On CentOS/RHEL, use yum-cron:
yum install yum-cron
systemctl enable yum-cron
systemctl start yum-cron
Edit /etc/yum/yum-cron.conf and set:
apply_updates = yes
Manually check for updates weekly:
apt update && apt upgrade # Debian/Ubuntu
yum update # CentOS/RHEL
10. Remove or disable unnecessary services
Every running service is another potential exploit target. If you're not using it, turn it off.
List active services:
systemctl list-units --type=service --state=running
Common services to disable on a web server:
systemctl disable avahi-daemon
systemctl stop avahi-daemon
systemctl disable cups
systemctl stop cups
systemctl disable bluetooth
systemctl stop bluetooth
Remove packages you don't need:
apt remove --purge packagename
Check listening ports:
ss -tuln
If you see a port you don't recognize, track down the process with lsof or netstat and decide if it should run.
11. Set up file integrity monitoring
File integrity monitoring detects unauthorized changes to critical system files. AIDE and Tripwire are the standard tools.
Install AIDE:
apt install aide # Debian/Ubuntu
yum install aide # CentOS/RHEL
Initialize the database:
aideinit
cp /var/lib/aide/aide.db.new /var/lib/aide/aide.db
Run a check:
aide --check
AIDE compares current file states against the baseline and reports changes. Schedule daily checks with cron:
0 3 * * * /usr/bin/aide --check | mail -s "AIDE Report" [email protected]
Update the database after legitimate changes:
aide --update
cp /var/lib/aide/aide.db.new /var/lib/aide/aide.db
So what if an attacker modifies the AIDE database itself? Store the baseline on read-only media or a separate trusted system.
12. Install an intrusion detection system
Host-based intrusion detection watches system calls, file access, and network activity for suspicious patterns. OSSEC is open-source and works across Linux distributions.
Install OSSEC:
wget https://github.com/ossec/ossec-hids/archive/refs/tags/3.7.0.tar.gz
tar -xvzf 3.7.0.tar.gz
cd ossec-hids-3.7.0
./install.sh
Follow the prompts and choose "local" installation for a standalone server. Start OSSEC:
/var/ossec/bin/ossec-control start
OSSEC monitors logs, checks file integrity, detects rootkits, and alerts on policy violations. Configure email alerts in /var/ossec/etc/ossec.conf:
<global>
<email_notification>yes</email_notification>
<email_to>[email protected]</email_to>
<smtp_server>localhost</smtp_server>
<email_from>[email protected]</email_from>
</global>
Restart OSSEC after config changes:
/var/ossec/bin/ossec-control restart
Review alerts in /var/ossec/logs/alerts/alerts.log. Tune rules over time to reduce false positives.
What to check first
Start with SSH hardening, firewall rules, and fail2ban. Those three stop the majority of automated attacks within an hour. Add the rest over the next few days.
Hardening isn't a one-time task. New vulnerabilities emerge, services get added, users come and go. Schedule monthly reviews of user accounts, firewall rules, and package updates. Check your logs weekly.
A locked-down server won't make headlines. That's the point.
