Every server I've deployed for clients starts with the same conversation: "Is it secure?" The answer is never yes out of the box. Default configurations prioritize ease of setup over defense, which means an unhardened server is a sitting target.
This walkthrough covers five baseline steps that turn a vulnerable VPS into something attackers will move past. No vendor pitches, no optional extras—just the core hardening every production server needs before you point a domain at it.
Lock down SSH access
SSH is how you manage the server, and it's also the most hammered service on the internet. Default port 22 gets thousands of brute-force attempts daily. Start here.
Disable password authentication entirely. Use SSH keys instead:
sudo nano /etc/ssh/sshd_config
Find and change these lines:
PasswordAuthentication no
PermitRootLogin no
Port 2222
Changing the port to something non-standard (like 2222) cuts noise dramatically. Root should never log in directly; use a regular user with sudo privileges. After editing, restart SSH:
sudo systemctl restart sshd
Before you close your current session, open a second terminal and test the new config. If you lock yourself out, you'll need console access through your hosting panel to fix it.
For the SSH key itself, generate it on your local machine:
ssh-keygen -t ed25519 -C "[email protected]"
Copy the public key to your server:
ssh-copy-id -p 2222 user@your_server_ip
Now password auth is off and only your key works. Store that private key securely—losing it means losing access.
Configure a firewall with default-deny
An open firewall is an open door. The principle is simple: block everything, then poke specific holes for services you actually run.
Most Ubuntu/Debian servers come with ufw (Uncomplicated Firewall). If it's not installed:
sudo apt install ufw
Set the default policy to deny:
sudo ufw default deny incoming
sudo ufw default allow outgoing
Now open only what you need. For a web server with SSH on port 2222:
sudo ufw allow 2222/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
Enable the firewall:
sudo ufw enable
Check the status:
sudo ufw status verbose
If you're running other services—mail on 25/587, MySQL on 3306—add those explicitly. Everything else stays blocked. On CentOS or RHEL, use firewalld instead:
sudo firewall-cmd --set-default-zone=drop
sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
Default-deny means if you forget to open a port, the service won't work. That's the correct failure mode.
Automate security updates
Patches fix vulnerabilities. Unpatched servers get owned. Manual updates are easy to forget, so automate them.
On Debian/Ubuntu, install unattended-upgrades:
sudo apt install unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgrades
Edit the config if you want tighter control:
sudo nano /etc/apt/apt.conf.d/50unattended-upgrades
By default it installs security updates only, which is what you want. If you enable all updates, you risk breaking compatibility with your app stack—stick to security patches.
On CentOS/RHEL, use dnf-automatic:
sudo dnf install dnf-automatic
sudo systemctl enable --now dnf-automatic.timer
Edit /etc/dnf/automatic.conf and set:
apply_updates = yes
Automatic updates won't catch zero-day exploits, but they close known holes before attackers scan for them. In tickets I've handled, the usual culprit for compromised servers was running software six months out of date.
Block brute-force with fail2ban
Even with SSH keys, attackers will hammer your login endpoints. Fail2ban watches logs and drops IPs that make repeated failed attempts.
Install it:
sudo apt install fail2ban
Create a local config:
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo nano /etc/fail2ban/jail.local
Find the [sshd] section and enable it:
[sshd]
enabled = true
port = 2222
maxretry = 3
bantime = 3600
This bans an IP for an hour after three failed SSH attempts. Restart fail2ban:
sudo systemctl restart fail2ban
Check active jails:
sudo fail2ban-client status
And see banned IPs:
sudo fail2ban-client status sshd
If you run a web app with login forms, add jails for Apache or Nginx too. Fail2ban won't stop sophisticated attacks, but it shuts down script kiddies and botnets scanning the internet for low-hanging fruit.
Audit file permissions and disable unused services
World-writable files and unnecessary daemons expand your attack surface. Clean them up.
Find files with overly permissive settings:
sudo find / -type f -perm 0777 2>/dev/null
Anything in that list should be reviewed. Web roots, config files, and scripts should never be world-writable. Fix them:
sudo chmod 644 /path/to/file
For directories, use 755 for most cases and 750 for sensitive ones.
Next, list all running services:
sudo systemctl list-units --type=service --state=running
Disable anything you don't recognize or use. Common culprits: Bluetooth daemons on a headless server, print services, Avahi. Example:
sudo systemctl stop bluetooth
sudo systemctl disable bluetooth
Every running service is potential attack surface. If you're not using it, turn it off.
Finally, check which ports are listening:
sudo ss -tuln
Anything unexpected? MySQL open to the world on 0.0.0.0:3306 instead of 127.0.0.1:3306 is a red flag. Bind services to localhost if they don't need external access.
What about AIDE or rkhunter?
File integrity monitors like AIDE and rootkit checkers like rkhunter add another layer, but they're not baseline. If you're managing a few servers, the five steps above give you the most security per minute invested. Intrusion detection makes sense once your core hardening is solid and you want alerting for post-compromise changes.
Do I need to reboot after hardening?
Not usually. SSH and firewall changes take effect when you restart their services. Kernel updates require a reboot, but those come through your automated patch process. If you changed sysctl settings or loaded kernel modules, reboot to be certain.
How often should I check security settings?
After the initial setup, spot-check monthly. Review firewall rules if you add services. Watch your fail2ban ban list weekly—if you see your own IP, adjust maxretry or whitelist yourself. The automated update system handles ongoing patches; you just need to confirm it's running.
What to check first
Hardening is not a one-time task, but these five steps give you a defensible baseline in under an hour. Start with SSH because that's your control plane. Add the firewall so only needed ports are exposed. Automate patches so you're not six months behind. Deploy fail2ban to drop noisy scanners. Audit permissions and services to shrink the attack surface.
Skip any of these and you're giving attackers an easy path. Deploy all five and your server stops being the easiest target on the block.
![Server Security Hardening: 5 Steps [Solved]](/images/blog/server-security-hardening-5-steps-solved.jpg)