A compromised server costs you data, uptime, and reputation. I've watched production boxes get owned because someone skipped basic hardening—root login over SSH, no firewall, months-old kernel vulnerabilities sitting wide open.
You can't prevent every attack, but you can make your server hard enough that most attackers move on to easier targets. The steps below form a baseline security posture for any Linux production server.
Step 1: Lock Down SSH Access
SSH is your primary remote access method, which makes it the first thing attackers probe. Default configurations leave several doors open.
Disable Root Login
Never allow direct root login over SSH. Force users to log in with a regular account and escalate privileges with sudo. Open /etc/ssh/sshd_config and set:
PermitRootLogin no
This simple change blocks brute-force attacks targeting the root account. Restart SSH after editing:
sudo systemctl restart sshd
Use SSH Keys, Disable Passwords
Password authentication is weak. Brute-force bots hammer SSH constantly. Switch to public key authentication.
Generate a key pair on your local machine if you haven't already:
ssh-keygen -t ed25519 -C "[email protected]"
Copy the public key to your server:
ssh-copy-id user@your-server-ip
Then disable password authentication in /etc/ssh/sshd_config:
PasswordAuthentication no
ChallengeResponseAuthentication no
Restart SSH. Test the key login from a new terminal before closing your current session—you don't want to lock yourself out.
Change the Default SSH Port
Port 22 gets scanned constantly. Moving SSH to a non-standard port (say, 2289 or 49222) reduces log noise and automated attacks. Edit /etc/ssh/sshd_config:
Port 2289
Update your firewall rules before restarting SSH, or you'll cut your own access. This isn't real security—it's obscurity—but it keeps your logs cleaner and filters out lazy scanners.
Limit SSH Access by IP
If you connect from a known IP range, restrict SSH to those addresses. In /etc/ssh/sshd_config:
AllowUsers [email protected]/24
Or use firewall rules (covered next) to block everything except your office IP. Be careful: if your IP changes and you lock yourself out, you'll need console access through your hosting panel.
Step 2: Configure a Firewall
An unconfigured firewall is no firewall. Most Linux distributions ship with iptables or its frontend ufw. Default-allow policies leave every service exposed.
Use UFW for Simplicity
UFW (Uncomplicated Firewall) wraps iptables with a friendlier syntax. On Ubuntu and Debian:
sudo apt install ufw
Set the default policy to deny incoming traffic:
sudo ufw default deny incoming
sudo ufw default allow outgoing
Now open only the ports you need. For a web server with SSH:
sudo ufw allow 2289/tcp # SSH on custom port
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS
Enable the firewall:
sudo ufw enable
Check the status:
sudo ufw status verbose
Rate-Limit SSH Connections
Even with key auth, you can rate-limit SSH to slow brute-force attempts:
sudo ufw limit 2289/tcp
This denies connections from an IP that attempts six or more connections within 30 seconds.
Block Suspicious Traffic with Fail2Ban
Fail2Ban watches log files and bans IPs that show malicious behavior—repeated failed logins, scanning, exploit attempts. Install it:
sudo apt install fail2ban
Copy the default config to a local override file:
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
Edit /etc/fail2ban/jail.local and enable the SSH jail:
[sshd]
enabled = true
port = 2289
logpath = /var/log/auth.log
maxretry = 5
bantime = 3600
Restart Fail2Ban:
sudo systemctl restart fail2ban
Check banned IPs:
sudo fail2ban-client status sshd
In support tickets I handled, Fail2Ban routinely blocked hundreds of IPs per week on busy servers.
Step 3: Keep the System Patched
Unpatched servers are sitting ducks. Known vulnerabilities get weaponized fast, and public exploit code spreads within hours.
Enable Automatic Security Updates
On Debian and Ubuntu, install unattended-upgrades:
sudo apt install unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades
This applies security patches automatically. Edit /etc/apt/apt.conf.d/50unattended-upgrades to control what gets updated:
Unattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}-security";
};
You can enable automatic reboots if a kernel update requires it:
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "03:00";
On RHEL-based systems (CentOS, Rocky, AlmaLinux), use dnf-automatic:
sudo dnf install dnf-automatic
sudo systemctl enable --now dnf-automatic.timer
Check for Updates Manually
Automatic updates handle security fixes, but you should still review available updates weekly:
sudo apt update
sudo apt list --upgradable
Major version upgrades and kernel updates sometimes need manual review, especially if you run custom modules or third-party software.
Step 4: Harden Kernel Parameters
The Linux kernel exposes tunable parameters through sysctl. A few tweaks improve security without breaking normal operation.
Edit /etc/sysctl.conf or create a new file under /etc/sysctl.d/99-hardening.conf:
# Disable IP forwarding unless you're routing
net.ipv4.ip_forward = 0
# Enable SYN flood protection
net.ipv4.tcp_syncookies = 1
# 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
# Log martian packets
net.ipv4.conf.all.log_martians = 1
# Disable ICMP echo (ping) responses (optional)
# net.ipv4.icmp_echo_ignore_all = 1
Apply the changes:
sudo sysctl -p /etc/sysctl.d/99-hardening.conf
These settings close common attack vectors—SYN floods, routing tricks, and spoofed packets. Disabling ping responses is optional; it can complicate troubleshooting but hides the server from casual scans.
Step 5: Monitor for Intrusions
You can't respond to a breach if you don't know it happened. Intrusion detection systems (IDS) watch for suspicious activity.
Install and Configure AIDE
AIDE (Advanced Intrusion Detection Environment) creates a database of file hashes and alerts you when system files change. Install it:
sudo apt install aide
Initialize the database:
sudo aideinit
sudo mv /var/lib/aide/aide.db.new /var/lib/aide/aide.db
Run a check:
sudo aide --check
Schedule daily checks with a cron job:
sudo crontab -e
Add:
0 2 * * * /usr/bin/aide --check | mail -s "AIDE Report" [email protected]
AIDE catches unauthorized changes to binaries, config files, and libraries. If an attacker replaces /bin/bash or modifies /etc/passwd, you'll know.
Deploy OSSEC or Wazuh
For real-time monitoring, use OSSEC or its modern fork Wazuh. These agents watch logs, file integrity, rootkit signatures, and network connections. Wazuh adds a central management dashboard.
Install the Wazuh agent:
curl -s https://packages.wazuh.com/key/GPG-KEY-WAZUH | sudo apt-key add -
echo "deb https://packages.wazuh.com/4.x/apt/ stable main" | sudo tee /etc/apt/sources.list.d/wazuh.list
sudo apt update
sudo apt install wazuh-agent
Configure the manager IP in /var/ossec/etc/ossec.conf, then start the agent:
sudo systemctl enable wazuh-agent
sudo systemctl start wazuh-agent
Wazuh requires a separate manager server, so it's better suited for multi-server environments. For a single box, AIDE plus log monitoring is enough.
Step 6: Secure Shared Services
Web servers, databases, and mail daemons run with elevated privileges. Misconfigurations here leak data or hand attackers a shell.
Run Services as Non-Root Users
Never run Apache, Nginx, MySQL, or Postfix as root. Modern packages default to dedicated users (www-data, mysql, postfix), but verify:
ps aux | grep nginx
If you see root as the owner of worker processes, check your config. Nginx should spawn workers as www-data:
user www-data;
Disable Unused Services
List enabled services:
sudo systemctl list-unit-files --state=enabled
Disable anything you don't need:
sudo systemctl disable --now avahi-daemon
sudo systemctl disable --now cups
Every running service is a potential attack surface. If you don't print from the server, kill CUPS. If you don't need mDNS, kill Avahi.
Restrict Database Access
Bind MySQL or PostgreSQL to localhost unless remote access is required:
# /etc/mysql/mysql.conf.d/mysqld.cnf
bind-address = 127.0.0.1
Use strong passwords and grant the minimum privileges each application needs. Never use the root database account in application configs.
Step 7: Enable Logging and Auditing
Logs tell you what happened, who did it, and when. Without them, forensics is guesswork.
Centralize Logs with Rsyslog
If you run multiple servers, forward logs to a central syslog server. On the client:
sudo nano /etc/rsyslog.conf
Add:
*.* @logserver.example.com:514
Restart rsyslog:
sudo systemctl restart rsyslog
On the log server, configure rsyslog to accept remote logs. Centralized logs survive even if an attacker wipes the compromised host.
Use Auditd for Detailed Syscall Tracking
The Linux Audit Framework logs system calls, file access, and user commands. Install it:
sudo apt install auditd
Add rules to watch sensitive files:
sudo auditctl -w /etc/passwd -p wa -k passwd_changes
sudo auditctl -w /etc/shadow -p wa -k shadow_changes
sudo auditctl -w /var/log/auth.log -p wa -k auth_log_changes
Make rules persistent by adding them to /etc/audit/rules.d/audit.rules. Search logs:
sudo ausearch -k passwd_changes
Auditd generates a lot of data. Review it regularly or feed it into a SIEM if you have one.
Rotate Logs to Prevent Disk Exhaustion
Logs grow fast. Without rotation, they'll fill your disk and crash services. Check /etc/logrotate.d/ for existing rules. Add custom rules as needed:
sudo nano /etc/logrotate.d/custom-app
/var/log/custom-app/*.log {
daily
rotate 14
compress
missingok
notifempty
create 0640 www-data adm
}
Test the config:
sudo logrotate -d /etc/logrotate.conf
What to Check First
Start with SSH and the firewall. Those two alone block the majority of opportunistic attacks. Patch management comes next—unpatched systems get compromised regardless of other hardening.
Intrusion detection and auditing sound optional until you need them. Logs are your only evidence after a breach, and AIDE catches rootkits and backdoors that hide from process lists.
Security is not one configuration change. It's layered defense, regular updates, and disciplined monitoring. The steps above won't stop a determined attacker, but they will stop the lazy ones—and those are the majority.
Run through this checklist on every production server you deploy. Make it part of your provisioning script or configuration management playbook. The hour you spend hardening a fresh install saves days of incident response later.
How often should I update my server?
Apply security patches automatically or at least weekly. Review and apply general updates monthly. Kernel updates that require reboots can wait for a maintenance window, but don't let them pile up for months.
Can I automate these hardening steps?
Yes. Ansible, Puppet, and Chef all support SSH hardening, firewall rules, and package updates. Write the playbook once and apply it to every server. Configuration management also documents your baseline, which helps during audits.
Should I disable IPv6?
Only if you truly don't use it. Disabling IPv6 can break some applications and doesn't add much security. Instead, apply the same firewall and sysctl rules to both IPv4 and IPv6.
What if my server is already compromised?
Hardening a compromised server is pointless. Take it offline, restore from a clean backup, and apply these steps before bringing it back online. Check logs for indicators of compromise—unusual processes, modified binaries, new user accounts, cron jobs you didn't create.
Do I need antivirus on Linux?
Not for the server itself. Linux malware exists but is rare. Focus on secure configurations, patching, and monitoring instead. If the server handles user-uploaded files (web hosting, email), scan those files with ClamAV to protect downstream Windows clients.
How do I test if my firewall is working?
From an external machine, run nmap -p- your-server-ip to scan all ports. You should see only the ports you deliberately opened. If you see unexpected open ports, check netstat -tulnp to identify the listening process and close it.
Start with SSH, End with Monitoring
The seven steps above form a reliable security baseline. SSH lockdown stops brute-force bots. Firewalls close unneeded ports. Automatic patching prevents known exploits from sitting around for weeks. Kernel hardening and IDS catch attacks that slip past the perimeter.
No server is perfectly secure, but a hardened server forces attackers to spend more time and skill. Most will move on to softer targets. That's the goal: make your infrastructure harder to compromise than the next guy's.
Review these settings quarterly. Security tools and attack methods evolve. What works today might need adjustment next year. Stay current, stay patched, and keep watching your logs.
