How to Secure & Harden Your VPS: Complete Guide for 2026
One-sentence verdict: An unhardened VPS gets scanned within minutes of going live — following this checklist takes about 30 minutes and blocks 99% of automated attacks before they start.
Who This Guide Is For
- New VPS owners who just provisioned their first server and want to lock it down properly.
- Developers and indie hackers running side projects, SaaS apps, or APIs on a VPS.
- Self-hosters deploying services like Nextcloud, Gitea, or AI tools who need a secure foundation.
- Small business owners with web apps handling customer data who need baseline security.
- Anyone migrating from shared hosting where the provider handled security for you.
If you’re running a large enterprise infrastructure with compliance requirements (PCI-DSS, SOC 2), you’ll need additional measures beyond this guide — but this remains a solid starting point.
Why VPS Hardening Matters
A freshly provisioned VPS with a public IP starts receiving automated attacks within 2–15 minutes. Here’s what happens if you do nothing:
| Threat | Timeline | Impact |
|---|---|---|
| SSH brute-force bots | Within 5 minutes | Root access compromise |
| Port scanners | Within 15 minutes | Service enumeration |
| Vulnerability scanners | Within 1 hour | Exploit attempts on known CVEs |
| Cryptomining malware | Within 24 hours | CPU hijacked, bandwidth drained |
| Botnet recruitment | Within 48 hours | Your IP blacklisted, used for DDoS |
These are not hypothetical — honeypot research consistently shows thousands of login attempts per day on exposed SSH ports.
Security Hardening Checklist (Quick Reference)
| Step | Time | Impact | Difficulty |
|---|---|---|---|
| Create non-root user | 2 min | High | Easy |
| Disable root SSH login | 1 min | High | Easy |
| SSH key authentication only | 5 min | Critical | Easy |
| Change SSH port | 1 min | Medium | Easy |
| Configure UFW firewall | 5 min | Critical | Easy |
| Install Fail2Ban | 5 min | High | Easy |
| Enable automatic updates | 3 min | High | Easy |
| Set up log monitoring | 5 min | Medium | Moderate |
| Configure 2FA for SSH | 5 min | High | Moderate |
| Install rootkit scanner | 3 min | Medium | Easy |
Total time: ~35 minutes for a fully hardened server.
Step 1: Create a Non-Root User with Sudo Access
Never run services as root. Create a dedicated user immediately after first login:
# Log in as root (first time only)
ssh root@your-server-ip
# Create a new user
adduser deploy
# Add to sudo group
usermod -aG sudo deploy
# Verify sudo works
su - deploy
sudo whoami # Should output: root
Why this matters: If any service running as root gets compromised, the attacker owns everything. A non-root user with sudo limits the blast radius.
Step 2: Set Up SSH Key Authentication
Passwords are brute-forceable. SSH keys are not (with proper key length).
On your local machine:
# Generate an Ed25519 key (most secure, recommended)
ssh-keygen -t ed25519 -C "your-email@example.com"
# Or RSA 4096 if Ed25519 isn't supported
ssh-keygen -t rsa -b 4096 -C "your-email@example.com"
# Copy the public key to your server
ssh-copy-id deploy@your-server-ip
Verify you can log in with the key before proceeding:
ssh deploy@your-server-ip
# Should log in without asking for a password
Step 3: Lock Down SSH Configuration
Edit the SSH daemon config:
sudo nano /etc/ssh/sshd_config
Apply these settings:
# Disable root login entirely
PermitRootLogin no
# Disable password authentication (key only)
PasswordAuthentication no
# Disable empty passwords
PermitEmptyPasswords no
# Change default port (pick something between 10000-65535)
Port 2222
# Limit login attempts
MaxAuthTries 3
# Disconnect idle sessions after 5 minutes
ClientAliveInterval 300
ClientAliveCountMax 2
# Only allow your user
AllowUsers deploy
# Disable X11 forwarding (unless you need it)
X11Forwarding no
# Use only SSH protocol 2
Protocol 2
Restart SSH:
sudo systemctl restart sshd
⚠️ Critical: Keep your current SSH session open and test the new config in a separate terminal before closing it. If something is wrong, you can still fix it from the open session.
# Test from a new terminal
ssh -p 2222 deploy@your-server-ip
Step 4: Configure UFW Firewall
UFW (Uncomplicated Firewall) provides a simple interface to iptables:
# Install UFW (usually pre-installed on Ubuntu)
sudo apt install ufw -y
# Default policy: deny all incoming, allow all outgoing
sudo ufw default deny incoming
sudo ufw default allow outgoing
# Allow your custom SSH port
sudo ufw allow 2222/tcp comment 'SSH'
# Allow web traffic (if running a web server)
sudo ufw allow 80/tcp comment 'HTTP'
sudo ufw allow 443/tcp comment 'HTTPS'
# Enable the firewall
sudo ufw enable
# Verify rules
sudo ufw status verbose
Important: Always allow your SSH port before enabling UFW, or you’ll lock yourself out.
Firewall Rules for Common Self-Hosted Services
| Service | Port | UFW Command |
|---|---|---|
| SSH (custom) | 2222 | sudo ufw allow 2222/tcp |
| HTTP | 80 | sudo ufw allow 80/tcp |
| HTTPS | 443 | sudo ufw allow 443/tcp |
| WireGuard | 51820 | sudo ufw allow 51820/udp |
| PostgreSQL (local only) | 5432 | Don’t expose — use SSH tunnel |
| Docker API | 2376 | Don’t expose — bind to localhost |
Step 5: Install and Configure Fail2Ban
Fail2Ban monitors log files and bans IPs that show malicious behavior:
# Install
sudo apt install fail2ban -y
# Create local config (never edit jail.conf directly)
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo nano /etc/fail2ban/jail.local
Add or modify these settings:
[DEFAULT]
bantime = 3600 # Ban for 1 hour
findtime = 600 # Within a 10-minute window
maxretry = 3 # After 3 failed attempts
banaction = ufw # Use UFW for banning
[sshd]
enabled = true
port = 2222 # Match your SSH port
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 86400 # Ban SSH attackers for 24 hours
Start Fail2Ban:
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
# Check status
sudo fail2ban-client status sshd
Step 6: Enable Automatic Security Updates
Don’t let known vulnerabilities sit unpatched:
# Install unattended-upgrades
sudo apt install unattended-upgrades apt-listchanges -y
# Enable automatic security updates
sudo dpkg-reconfigure -plow unattended-upgrades
Edit /etc/apt/apt.conf.d/50unattended-upgrades to configure what gets updated:
Unattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}-security";
"${distro_id}ESMApps:${distro_codename}-apps-security";
};
// Auto-reboot if needed (at 4 AM)
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "04:00";
// Email notifications (optional)
Unattended-Upgrade::Mail "your-email@example.com";
Step 7: Set Up Basic Intrusion Detection
Install rkhunter (Rootkit Hunter)
sudo apt install rkhunter -y
# Update the database
sudo rkhunter --update
# Run a scan
sudo rkhunter --check --skip-keypress
# Set up daily scans via cron
echo '0 3 * * * root /usr/bin/rkhunter --check --skip-keypress --report-warnings-only' | sudo tee /etc/cron.d/rkhunter
Monitor File Changes with AIDE
sudo apt install aide -y
# Initialize the database
sudo aideinit
# Copy the new database
sudo cp /var/lib/aide/aide.db.new /var/lib/aide/aide.db
# Run a check
sudo aide --check
Step 8: Secure Shared Memory and Kernel Parameters
Edit /etc/fstab to secure shared memory:
echo 'tmpfs /run/shm tmpfs defaults,noexec,nosuid 0 0' | sudo tee -a /etc/fstab
Harden kernel parameters in /etc/sysctl.conf:
sudo tee -a /etc/sysctl.conf << 'EOF'
# Prevent IP spoofing
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
# Disable source routing
net.ipv4.conf.all.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0
# Ignore ICMP broadcast requests
net.ipv4.icmp_echo_ignore_broadcasts = 1
# Disable ICMP redirects
net.ipv4.conf.all.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
# Log suspicious packets
net.ipv4.conf.all.log_martians = 1
net.ipv4.conf.default.log_martians = 1
# Disable IPv6 if not needed
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
EOF
# Apply changes
sudo sysctl -p
Step 9: Set Up Log Monitoring (Optional but Recommended)
Install Logwatch for daily security summaries:
sudo apt install logwatch -y
# Run a test report
sudo logwatch --detail Med --range today --output stdout
# Configure daily email reports
sudo tee /etc/cron.daily/00logwatch << 'EOF'
#!/bin/bash
/usr/sbin/logwatch --output mail --mailto your-email@example.com --detail high
EOF
sudo chmod +x /etc/cron.daily/00logwatch
Step 10: Docker-Specific Security (If Using Containers)
Docker bypasses UFW by default — this catches many people off guard:
# Prevent Docker from manipulating iptables directly
sudo tee /etc/docker/daemon.json << 'EOF'
{
"iptables": false,
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
EOF
sudo systemctl restart docker
Best practices for Docker security:
- Never run containers as root — use
user:in docker-compose - Don’t expose ports to 0.0.0.0 — bind to 127.0.0.1 and use a reverse proxy
- Keep images updated —
docker pullregularly or use Watchtower - Use read-only filesystem where possible —
read_only: true - Limit container resources — set
mem_limitandcpus
VPS Provider Security Features Comparison
| Provider | Free Firewall | DDoS Protection | Backups | 2FA Dashboard | Private Networking |
|---|---|---|---|---|---|
| Hetzner | ✅ Cloud Firewall | Basic | €2.88/mo (20%) | ✅ | ✅ |
| DigitalOcean | ✅ Cloud Firewall | Free basic | $2.40/mo (20%) | ✅ | ✅ VPC |
| Vultr | ✅ Firewall Groups | Free basic | $1/mo per VPS | ✅ | ✅ VPC 2.0 |
| Linode (Akamai) | ✅ Cloud Firewall | Free basic | $2.50/mo (20%) | ✅ | ✅ VLAN |
| Contabo | ❌ (OS-level only) | Basic | Snapshots only | ✅ | Limited |
| RackNerd | ❌ (OS-level only) | Free SolusVM | Manual only | ❌ | ❌ |
Recommendation: Use provider-level firewalls as an additional layer on top of UFW — they filter traffic before it reaches your server, reducing load.
Recommended Configurations by Use Case
| Use Case | Min RAM | Key Security Measures | Monthly Cost |
|---|---|---|---|
| Personal blog/portfolio | 1 GB | SSH keys + UFW + Fail2Ban + auto-updates | $4–$6 |
| SaaS app with user data | 2 GB | All above + AIDE + encrypted backups + WAF | $8–$12 |
| Self-hosted AI tools | 4 GB+ | All above + Docker hardening + resource limits | $12–$24 |
| E-commerce store | 4 GB+ | All above + PCI compliance measures + CDN | $20–$40 |
Common Mistakes to Avoid
| Mistake | Risk | Fix |
|---|---|---|
| Running everything as root | Total compromise if any service is exploited | Use non-root user + sudo |
| Leaving SSH on port 22 with password auth | Thousands of brute-force attempts daily | Key-only auth + custom port |
| Not enabling a firewall | All ports exposed to the internet | UFW deny-all + explicit allows |
| Exposing database ports | Direct database access from anywhere | Bind to localhost, use SSH tunnels |
| Docker binding to 0.0.0.0 | Bypasses UFW, exposes services directly | Bind to 127.0.0.1 + reverse proxy |
| No backups | Ransomware or disk failure = total loss | Automated off-server backups |
| Ignoring updates | Known CVEs exploited within hours | Unattended-upgrades for security patches |
Risk Warnings
⚠️ Locking yourself out: Always test SSH changes in a new terminal while keeping the old session open. If you lose access, you’ll need provider console/VNC access to fix it.
⚠️ Breaking services: Overly restrictive firewall rules can break applications. Test after each change.
⚠️ False security: Hardening reduces attack surface but doesn’t make you invulnerable. Keep backups, monitor logs, and stay updated on CVEs affecting your stack.
⚠️ Auto-updates rebooting production: If uptime is critical, disable automatic reboots and schedule maintenance windows manually.
What to Do Next
- Set up automated backups — Use provider snapshots + off-site backups (BorgBackup to a second VPS or Backblaze B2).
- Install a reverse proxy — Caddy or Nginx Proxy Manager for automatic HTTPS on all your services.
- Add uptime monitoring — Use UptimeRobot (free) or self-hosted Uptime Kuma to catch outages.
- Review logs weekly — Check Fail2Ban bans, auth.log, and any service-specific logs.
- Harden individual applications — Each service (WordPress, Nextcloud, databases) has its own security best practices.
Conclusion
Securing a VPS isn’t optional — it’s the first thing you should do after provisioning. The 30-minute investment in this guide protects against the vast majority of automated attacks. The steps are cumulative: each layer makes exploitation significantly harder.
Start with the basics (non-root user, SSH keys, firewall, Fail2Ban), then add monitoring and intrusion detection as your setup grows. The cheapest VPS at $4/month becomes a liability if it’s compromised and used to attack others — hardening is cheaper than incident response.