GUIDE, WITHOUT THE GUESSWORK

VPS Security Hardening: Complete Guide

Secure your VPS with SSH hardening, firewall configuration, automatic updates, fail2ban, and container security best practices.

VPS Security Hardening: Complete Guide

A practical guide to securing your VPS. These steps protect against the most common attacks: brute force, unauthorized access, and unpatched vulnerabilities.

Overview

This guide covers:

  1. SSH hardening (most important)
  2. Firewall configuration
  3. Automatic security updates
  4. Fail2ban for brute force protection
  5. Docker security
  6. Monitoring and auditing

Do these in order. SSH hardening alone blocks most attacks.

Step 1: Create a non-root user

Never use root for daily operations.

adduser deploy
usermod -aG sudo deploy

Test sudo access:

su - deploy
sudo whoami  # Should output: root

Step 2: SSH key authentication

SSH keys are more secure than passwords. Generate a key on your local machine:

ssh-keygen -t ed25519 -C "[email protected]"

Copy it to your VPS:

ssh-copy-id -i ~/.ssh/id_ed25519.pub deploy@YOUR_VPS_IP

Test key-based login:

ssh deploy@YOUR_VPS_IP

Step 3: Harden SSH configuration

Edit the SSH config:

sudo nano /etc/ssh/sshd_config

Make these changes:

# Disable root login
PermitRootLogin no

# Disable password authentication
PasswordAuthentication no

# Disable empty passwords
PermitEmptyPasswords no

# Use only SSH protocol 2
Protocol 2

# Limit authentication attempts
MaxAuthTries 3

# Set login grace time
LoginGraceTime 30

# Disable X11 forwarding (unless needed)
X11Forwarding no

# Disable TCP forwarding (unless needed)
AllowTcpForwarding no

# Only allow specific users
AllowUsers deploy

# Use strong ciphers
Ciphers [email protected],[email protected],[email protected]
MACs [email protected],[email protected]
KexAlgorithms curve25519-sha256,[email protected]

Test the configuration:

sudo sshd -t

Restart SSH:

sudo systemctl restart sshd

Important: Keep your current SSH session open and test a new connection before closing it.

Step 4: Change SSH port (optional)

Changing the default port reduces automated attacks.

sudo nano /etc/ssh/sshd_config
Port 2222  # Choose any port between 1024-65535

Update firewall before restarting SSH:

sudo ufw allow 2222/tcp
sudo systemctl restart sshd

Connect using the new port:

ssh -p 2222 deploy@YOUR_VPS_IP

Step 5: Configure the firewall (UFW)

UFW (Uncomplicated Firewall) is the easiest way to manage iptables.

Enable UFW

# Set default policies
sudo ufw default deny incoming
sudo ufw default allow outgoing

# Allow SSH (use your port if changed)
sudo ufw allow 22/tcp
# or
sudo ufw allow 2222/tcp

# Allow HTTP and HTTPS
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

# Enable the firewall
sudo ufw enable

# Check status
sudo ufw status verbose

Rate limiting

Limit connection attempts to SSH:

sudo ufw limit 22/tcp

This allows 6 connections per 30 seconds, then blocks.

Allow specific services

# PostgreSQL from specific IP only
sudo ufw allow from 10.0.0.5 to any port 5432

# Redis (local only)
sudo ufw allow from 127.0.0.1 to any port 6379

Step 6: Install Fail2ban

Fail2ban automatically blocks IPs that show malicious behavior.

Install

sudo apt update
sudo apt install fail2ban -y

Configure

Create a local config (do not edit the main config):

sudo nano /etc/fail2ban/jail.local
[DEFAULT]
# Ban for 1 hour
bantime = 3600

# Check the last 10 minutes
findtime = 600

# Ban after 5 failures
maxretry = 5

# Email notifications (optional)
# destemail = [email protected]
# action = %(action_mwl)s

[sshd]
enabled = true
port = ssh  # or your custom port
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 86400  # 24 hours for SSH

[nginx-http-auth]
enabled = true
filter = nginx-http-auth
port = http,https
logpath = /var/log/nginx/error.log

[nginx-limit-req]
enabled = true
filter = nginx-limit-req
port = http,https
logpath = /var/log/nginx/error.log

Start Fail2ban

sudo systemctl enable fail2ban
sudo systemctl start fail2ban

Check status

sudo fail2ban-client status
sudo fail2ban-client status sshd

Unban an IP

sudo fail2ban-client set sshd unbanip 1.2.3.4

Step 7: Automatic security updates

Enable unattended upgrades for security patches.

Install

sudo apt install unattended-upgrades apt-listchanges -y

Configure

sudo dpkg-reconfigure -plow unattended-upgrades

Select "Yes" to enable automatic updates.

Customize (optional)

sudo nano /etc/apt/apt.conf.d/50unattended-upgrades

Key settings:

// Automatically reboot if required
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "04:00";

// Email notifications
Unattended-Upgrade::Mail "[email protected]";

// Remove unused dependencies
Unattended-Upgrade::Remove-Unused-Dependencies "true";

Verify

sudo unattended-upgrades --dry-run --debug

Step 8: Secure shared memory

Prevent shared memory attacks:

sudo nano /etc/fstab

Add this line:

tmpfs /run/shm tmpfs defaults,noexec,nosuid 0 0

Apply:

sudo mount -o remount /run/shm

Step 9: Docker security

If running Docker, apply these security measures.

Do not run containers as root

FROM node:20-alpine
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
# ... rest of Dockerfile

Use read-only filesystems

services:
  app:
    image: your-app
    read_only: true
    tmpfs:
      - /tmp
      - /var/run

Limit container capabilities

services:
  app:
    image: your-app
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE  # Only if needed
    security_opt:
      - no-new-privileges:true

Set resource limits

services:
  app:
    image: your-app
    deploy:
      resources:
        limits:
          cpus: '1'
          memory: 512M
        reservations:
          cpus: '0.25'
          memory: 128M

Use Docker secrets for sensitive data

services:
  app:
    image: your-app
    secrets:
      - db_password

secrets:
  db_password:
    file: ./secrets/db_password.txt

Keep Docker updated

sudo apt update
sudo apt upgrade docker-ce docker-ce-cli containerd.io -y

Scan images for vulnerabilities

docker scout cves your-image:latest

Or use Trivy:

sudo apt install trivy -y
trivy image your-image:latest

Step 10: Set up log monitoring

Install Logwatch

sudo apt install logwatch -y

Configure daily email reports:

sudo nano /etc/cron.daily/00logwatch
#!/bin/bash
/usr/sbin/logwatch --output mail --mailto [email protected] --detail high

Monitor auth logs

Watch for login attempts in real-time:

sudo tail -f /var/log/auth.log

Check for rootkits

Install rkhunter:

sudo apt install rkhunter -y
sudo rkhunter --update
sudo rkhunter --check

Step 11: Disable unused services

List running services:

sudo systemctl list-units --type=service --state=running

Disable unnecessary services:

sudo systemctl disable cups  # Printing
sudo systemctl disable avahi-daemon  # mDNS
sudo systemctl disable bluetooth  # Bluetooth

Step 12: Set up intrusion detection (optional)

For more security, install AIDE:

sudo apt install aide -y
sudo aideinit
sudo mv /var/lib/aide/aide.db.new /var/lib/aide/aide.db

Run daily checks:

sudo nano /etc/cron.daily/aide-check
#!/bin/bash
/usr/bin/aide --check | mail -s "AIDE report" [email protected]
sudo chmod +x /etc/cron.daily/aide-check

Security checklist

Use this checklist to verify your setup:

Troubleshooting

Locked out of SSH

If you are locked out:

  1. Access via VPS provider console (web-based)
  2. Fix SSH config or authorized_keys
  3. Restart SSH

Fail2ban blocking legitimate IPs

Check bans:

sudo fail2ban-client status sshd

Unban:

sudo fail2ban-client set sshd unbanip YOUR_IP

Add to whitelist:

sudo nano /etc/fail2ban/jail.local
[DEFAULT]
ignoreip = 127.0.0.1/8 YOUR_IP

UFW blocking needed traffic

Check current rules:

sudo ufw status numbered

Add missing rule:

sudo ufw allow PORT/tcp

Delete a rule:

sudo ufw delete NUMBER

Automatic updates causing issues

Check logs:

cat /var/log/unattended-upgrades/unattended-upgrades.log

Disable temporarily:

sudo systemctl stop unattended-upgrades

Where to go next

Tutorials:

Comparisons:

ServerCompass:


Related in the StoicSoft network

If you work in AI-assisted coding, shared terminal sessions, or agent-driven shell workflows like the ones above, 1devtool is the StoicSoft network's tool for safer AI-assisted terminal work — shared sessions with auditing, preflight policy, and tiered model routing built in.

From across the StoicSoft network

Hand-curated reads on the same topic from sister sites in the StoicSoft family.