Every week I see a fresh batch of tickets where a server got popped because someone skipped the basics. The attack surface on a default install is huge. Hardening is not a one-time setup—it's a layered defense you build in steps and maintain.
This checklist walks through eight practical moves that stop the most common intrusion paths. I've used these on hundreds of production boxes running everything from cPanel to bare Kubernetes nodes. They work because they close real gaps, not theoretical ones.
Step 1: Lock down SSH access
SSH is your front door. Leave it wide open and you'll see brute-force attempts within minutes of provisioning a new server.
Disable root login and password authentication entirely. Create a dedicated user with sudo rights, then force key-based auth. Edit /etc/ssh/sshd_config:
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
Port 2222
Changing the port from 22 to something higher cuts down log noise from automated scanners. It's not real security, but it keeps your logs readable.
Restart SSH after every config change:
systemctl restart sshd
Before you close your current session, open a second terminal and confirm you can still log in. I've locked myself out more times than I care to admit.
For Windows Server running OpenSSH, the config lives in C:\ProgramData\ssh\sshd_config and the same directives apply. Restart the service through Services.msc or PowerShell.
Step 2: Configure a firewall with default-deny rules
A firewall should block everything except what you explicitly allow. Default-allow configurations are a liability.
On Linux, ufw (Uncomplicated Firewall) is the easiest starting point for most distributions:
ufw default deny incoming
ufw default allow outgoing
ufw allow 2222/tcp comment 'SSH'
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable
For granular control, iptables or nftables give you more options. On CentOS/RHEL systems, firewalld is the standard.
Windows Server ships with Windows Defender Firewall. Open it, switch to inbound rules, and set the default action to Block. Then whitelist RDP (3389) from your office IP range only, plus HTTP/HTTPS if you're running IIS.
Check active rules regularly. Over time you'll accumulate junk from forgotten experiments.
Step 3: Enable automatic security updates
Patching is where most hardening plans fall apart. Manual updates don't happen consistently. Automate them.
On Debian/Ubuntu, unattended-upgrades handles this:
apt install unattended-upgrades
dpkg-reconfigure --priority=low unattended-upgrades
Edit /etc/apt/apt.conf.d/50unattended-upgrades to include security repos and optionally reboot if needed:
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "03:00";
For RHEL-based systems, dnf-automatic does the same job:
dnf install dnf-automatic
systemctl enable --now dnf-automatic.timer
Windows Server can auto-install updates through Group Policy or the Settings UI. Set active hours to avoid surprise reboots during business.
Yes, automatic updates can break things. In practice, the risk of running outdated packages is higher than the risk of a bad patch. Test updates on a staging box if you're paranoid.
Step 4: Disable unnecessary services and ports
Every running service is another attack vector.
List what's listening:
ss -tulnp
You'll see a bunch of stuff you didn't know was there. Telnet, FTP, old RPC services—kill them. On systemd-based distros:
systemctl disable servicename
systemctl stop servicename
For Windows, open Services.msc and set unused services to Disabled. Print Spooler is a favorite target if you're not actually printing. Same with Remote Registry.
If you're running a web host, you probably need HTTP, HTTPS, SSH, and maybe SMTP. Everything else should justify its existence.
Step 5: Set up fail2ban or equivalent intrusion prevention
Brute-force attacks are constant. Fail2ban watches your logs and bans IPs after repeated failed login attempts.
Install on Debian/Ubuntu:
apt install fail2ban
cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
Edit /etc/fail2ban/jail.local to enable the SSH jail:
[sshd]
enabled = true
port = 2222
maxretry = 3
bantime = 3600
Restart fail2ban and watch it work:
systemctl restart fail2ban
fail2ban-client status sshd
You can add jails for Apache, Nginx, Postfix, or any service that logs auth failures.
Windows doesn't have a direct equivalent, but you can script similar behavior with PowerShell and Windows Firewall. Third-party tools like EvlWatcher or IPBan fill the gap.
Step 6: Harden kernel parameters and system limits
The Linux kernel exposes dozens of tunables that affect security. A few key ones:
Edit /etc/sysctl.conf or create a file in /etc/sysctl.d/:
# Disable IP forwarding if not routing
net.ipv4.ip_forward = 0
# Enable SYN cookies to mitigate SYN flood
net.ipv4.tcp_syncookies = 1
# Ignore ICMP redirects
net.ipv4.conf.all.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
# Disable source packet routing
net.ipv4.conf.all.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0
# Log suspicious packets
net.ipv4.conf.all.log_martians = 1
Apply changes:
sysctl -p
These settings make life harder for network-level attacks. They won't stop an application vulnerability, but they close kernel-level holes.
On Windows, Group Policy offers comparable controls under Computer Configuration > Windows Settings > Security Settings > Local Policies.
Step 7: Install and configure an intrusion detection system
Firewalls and fail2ban are reactive. An IDS watches for suspicious patterns in real time.
OSSEC is a solid open-source choice. It monitors file integrity, log patterns, rootkit signatures, and more. Install the agent on each server and point it to a central manager if you're running multiple boxes.
Basic installation on Ubuntu:
wget https://github.com/ossec/ossec-hids/archive/refs/tags/3.7.0.tar.gz
tar -xzf 3.7.0.tar.gz
cd ossec-hids-3.7.0
sudo ./install.sh
Follow the prompts to set it up as a local installation. The default rules catch common threats. Tune them as you learn what normal traffic looks like on your stack.
For Windows, OSSEC has a native agent. Alternatively, Wazuh (an OSSEC fork) offers better Windows integration and a modern UI.
An IDS won't stop an attack, but it'll alert you when something's wrong. That's the difference between catching a breach in minutes versus weeks.
Step 8: Enforce strong password policies and two-factor authentication
Weak passwords still cause more breaches than zero-days.
On Linux, libpam-pwquality enforces complexity requirements. Install it, then edit /etc/security/pwquality.conf:
minlen = 14
minclass = 3
maxrepeat = 2
For SSH, you already disabled passwords. Good. For any other service that requires auth—cPanel, Webmin, database consoles—turn on 2FA.
Google Authenticator integrates with PAM for Linux services:
apt install libpam-google-authenticator
Run google-authenticator as each user to generate a secret. Then edit /etc/pam.d/sshd to require it.
Windows Server supports 2FA through Azure AD, smart cards, or third-party providers. If you're running RDP exposed to the internet (you shouldn't be), 2FA is non-negotiable.
What about compliance and audit logs?
Hardening is pointless if you can't prove you did it. Most compliance frameworks—PCI DSS, HIPAA, SOC 2—require evidence of security controls.
Centralize your logs. Use rsyslog or syslog-ng to ship everything to a dedicated log server or a service like Papertrail. That way if a box gets compromised, the attacker can't erase the evidence.
Enable auditd on Linux for kernel-level logging:
apt install auditd
systemctl enable auditd
Create rules in /etc/audit/rules.d/ to watch sensitive files:
-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
Windows Event Logs capture similar data. Forward them to a SIEM or at least increase the log size so they don't rotate too quickly.
Maintenance: revisit your checklist quarterly
Hardening is not a weekend project you finish and forget. New vulnerabilities, new services, new team members with their own SSH keys—things drift.
Schedule a quarterly review. Check your firewall rules, audit active user accounts, verify that automatic updates are still running, and scan for deprecated software.
Use a tool like Lynis for automated audits:
wget https://downloads.cisofy.com/lynis/lynis-3.0.8.tar.gz
tar -xzf lynis-3.0.8.tar.gz
cd lynis
sudo ./lynis audit system
It'll spit out a scored report with specific recommendations. Some are nitpicky, but it catches things you'll miss manually.
FAQ: Common hardening questions
Do I really need to change the SSH port?
No, but it cuts automated scan noise by 90%. Real attackers will find it anyway, so don't rely on it as actual security.
Will automatic updates break my production server?
They can. Test on staging first if you're risk-averse, but running outdated packages is riskier long-term.
What if fail2ban bans my own IP?
Add your IP to the ignoreip list in /etc/fail2ban/jail.local. Keep a secondary access method (console, IPMI) just in case.
Is an IDS overkill for a small server?
Not if you care about catching breaches early. The overhead is minimal and the alert value is high.
How do I test if my hardening actually worked?
Run a vulnerability scanner like OpenVAS or Nessus from an external IP. Check that only intended ports respond and that your IDS flags the scan.
Start with SSH and the firewall
If you do nothing else, fix SSH and lock down your firewall. Those two steps stop the majority of opportunistic attacks.
The rest of the checklist builds defense in depth—each layer catches what the previous one missed. Hardening is cumulative. Pick one step, finish it, move to the next. Your server will thank you when the next botnet comes knocking.
