A fresh VPS is a target the moment it boots. Automated bots scan for weak SSH passwords, outdated kernels, and open services within minutes of your server going live. I've seen root accounts with dictionary passwords get compromised in under an hour.
These five hardening steps won't make your server bulletproof, but they will stop the bulk of automated attacks and buy you time to notice anything serious. Do them in order on a clean install before you install your application stack.
Step 1: Lock down SSH access
SSH is the front door. Password authentication is asking for trouble.
Generate an SSH 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 root@your-server-ip
Test that key-based login works before you disable passwords. Open a second terminal and confirm you can connect without typing a password.
Now edit /etc/ssh/sshd_config on the server:
Port 2222
PermitRootLogin prohibit-password
PasswordAuthentication no
PubkeyAuthentication yes
ChallengeResponseAuthentication no
UsePAM no
Changing the default port from 22 to something higher cuts log noise from port scanners by ninety percent. It's security through obscurity, but it works for reducing noise. More important is disabling password auth entirely.
Restart SSH carefully:
systemctl restart sshd
Keep that second terminal open until you verify the new settings work. If you lock yourself out, most hosting panels offer console access to fix it.
Step 2: Configure a firewall
By default most VPS distributions ship with every port open. You need exactly three things exposed to the internet: SSH, HTTP, and HTTPS. Everything else should be blocked at the network layer.
UFW makes this simple on Ubuntu and Debian:
apt update && apt install ufw
ufw default deny incoming
ufw default allow outgoing
ufw allow 2222/tcp comment 'SSH'
ufw allow 80/tcp comment 'HTTP'
ufw allow 443/tcp comment 'HTTPS'
ufw enable
On CentOS or AlmaLinux, use firewalld:
firewall-cmd --permanent --remove-service=cockpit
firewall-cmd --permanent --add-port=2222/tcp
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --reload
Check your active rules:
ufw status numbered
# or
firewall-cmd --list-all
If you're running a database, don't open port 3306 or 5432 to the world. Bind MySQL or PostgreSQL to localhost and have your application connect locally, or use SSH tunneling if you need remote access for admin work.
Step 3: Set up automatic security updates
Patching is boring. Automating it is smart.
Most breaches I've investigated in hosting support involved kernels or packages that were months out of date. A public exploit drops, bots scan for it, and unpatched servers get owned in days.
On Ubuntu and Debian, install unattended-upgrades:
apt install unattended-upgrades
dpkg-reconfigure -plow unattended-upgrades
Edit /etc/apt/apt.conf.d/50unattended-upgrades and make sure security updates are enabled:
Unattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}-security";
};
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "03:00";
That last line schedules a reboot at 3 AM if a kernel update requires it. Adjust to match your maintenance window.
For RHEL-based systems, enable dnf-automatic:
dnf install dnf-automatic
systemctl enable --now dnf-automatic.timer
Edit /etc/dnf/automatic.conf:
[commands]
apply_updates = yes
Check logs periodically to confirm updates are applying:
grep -i "unattended-upgrade" /var/log/unattended-upgrades/unattended-upgrades.log
Automatic updates handle the ninety-five percent of patches that are safe to apply without human review. For the remaining five percent—major version upgrades, breaking changes—you still need to read changelogs and test.
Step 4: Deploy fail2ban to block brute force
Even with key-based auth, bots will hammer your SSH port trying passwords. Fail2ban watches your logs and bans IPs after repeated failed attempts.
Install it:
apt install fail2ban
# or
dnf install fail2ban
Create /etc/fail2ban/jail.local:
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 5
[sshd]
enabled = true
port = 2222
logpath = /var/log/auth.log
Restart fail2ban:
systemctl restart fail2ban
systemctl enable fail2ban
Check banned IPs:
fail2ban-client status sshd
You'll see a list of banned addresses. In my experience, most are cloud hosting ranges running automated scanners. Fail2ban won't stop a targeted attack, but it cuts noise and protects against credential stuffing.
If you run a web app with login forms, add a jail for HTTP auth failures too. The nginx-limit-req and apache-auth filters ship with fail2ban and catch common patterns.
Step 5: Disable unused services and harden the kernel
Every running service is a potential attack surface. Check what's listening:
ss -tulnp
You should see SSH, your web server, and not much else. If you spot MySQL on 0.0.0.0:3306 or a mail server you're not using, stop and disable it:
systemctl stop postfix
systemctl disable postfix
Next, apply kernel-level hardening via sysctl. Edit /etc/sysctl.conf:
# IP Forwarding (disable unless you're routing)
net.ipv4.ip_forward = 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
# Disable source packet routing
net.ipv4.conf.all.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0
# Log Martians
net.ipv4.conf.all.log_martians = 1
# Ignore ICMP ping requests
net.ipv4.icmp_echo_ignore_all = 1
# SYN flood protection
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_syn_backlog = 2048
net.ipv4.tcp_synack_retries = 2
Apply the settings:
sysctl -p
These tunables disable features that are rarely needed and often abused. Disabling ICMP ping makes your server invisible to casual scans, though targeted attackers can still probe open ports.
Finally, review installed packages and remove anything you don't recognize:
apt list --installed
# or
dnf list installed
A minimal server install is easier to patch and harder to exploit.
What about SELinux or AppArmor?
If your distribution ships with SELinux (RHEL, CentOS, Fedora) or AppArmor (Ubuntu, Debian), leave it enabled. Mandatory access control adds a layer that stops privilege escalation even when an application is compromised.
Check SELinux status:
getenforce
If it says "Permissive" or "Disabled," set it to enforcing:
setenforce 1
Make it permanent in /etc/selinux/config:
SELINUX=enforcing
SELinux can break applications that expect full filesystem access. When that happens, read the audit logs in /var/log/audit/audit.log and write a targeted policy instead of disabling SELinux entirely. The same goes for AppArmor profiles.
FAQ
Do I need all five steps on a development server?
Yes. Dev servers get scanned too, and a compromised dev box can be a stepping stone into production. At minimum, lock down SSH and enable the firewall.
Will automatic updates break my application?
Security patches rarely break userland applications. Kernel updates occasionally require a reboot. Test your deployment on a staging server first if you're worried, but leaving a server unpatched is riskier than an unexpected reboot.
Should I use a custom SSH port higher than 1024?
Ports above 1024 don't require root to bind, but that's not relevant for sshd. Any port between 1024 and 65535 works fine. I usually pick something in the 2000-3000 range that's easy to remember.
How often should I review firewall rules?
Every time you install a new service. If you deploy Redis or Memcached, confirm they're bound to localhost and not exposed through the firewall. An annual audit of open ports is a good habit.
Can fail2ban ban me by accident?
Yes, if you mistype your password five times. Keep console access handy or whitelist your office IP in the jail.local file under ignoreip.
What to check first
These five steps are a baseline, not a finish line. Schedule a monthly check: review active services, scan for outdated packages, and rotate SSH keys annually. Watch your auth logs for unusual patterns, and consider adding intrusion detection with tools like AIDE or Tripwire if you're handling sensitive data.
Hardening a server takes an hour. Cleaning up after a breach takes weeks. The choice is obvious.
![Server Security Hardening: 5 Steps [Solved]](/images/blog/server-security-hardening-5-steps-solved-2.jpg)