DEV Community

Cover image for Securing a VPS: Essential Hardening Steps for Web Developers (Part 1)
Tomas Hornak
Tomas Hornak

Posted on • Edited on

Securing a VPS: Essential Hardening Steps for Web Developers (Part 1)

After working with various PaaS solutions for years, I decided to set up a VPS with Contabo for hosting a few personal projects. While nothing particularly sensitive will be hosted there, I still wanted to implement proper security practices from the start.

Even simple hobby projects can become targets for automated attacks or get compromised for cryptocurrency mining. Here's my systematic approach to hardening a fresh Ubuntu VPS, covering the essential security fundamentals.

The Starting Point

Fresh VPS with Ubuntu, an admin user with sudo privileges, and SSH key authentication already configured during setup. Time to implement proper security measures.

Step 1: SSH Hardening

The default SSH configuration needed immediate attention. Running SSH on the default port 22 makes your server a target for automated scanning tools that constantly probe this port.

sudo nano /etc/ssh/sshd_config
Enter fullscreen mode Exit fullscreen mode

Key changes made:

Port 2222                   # Move away from the default port
PermitRootLogin no          # Disable direct root access
PasswordAuthentication no   # Enforce key-based authentication
PubkeyAuthentication yes    # Enable SSH keys
MaxAuthTries 3              # Limit failed attempts
AllowUsers admin            # Restrict user access
Enter fullscreen mode Exit fullscreen mode

Then restart SSHD:

sudo systemctl restart sshd
Enter fullscreen mode Exit fullscreen mode

Important: Always test your SSH configuration in a separate terminal session before closing your current connection. This prevents accidental lockouts. Also, ideally, don't close your connection before Step 2.

Step 2: Firewall Configuration

Linux's UFW provides straightforward firewall management. The approach is to deny all incoming connections by default, then explicitly allow only necessary services.

# Set restrictive defaults
sudo ufw default deny incoming
sudo ufw default allow outgoing

# Allow essential services
sudo ufw allow 2222/tcp   # SSH on custom port
sudo ufw allow 80/tcp     # HTTP
sudo ufw allow 443/tcp    # HTTPS

# Enable firewall
sudo ufw enable
Enter fullscreen mode Exit fullscreen mode

Critical note: It's recommended to perform Steps 1 & 2 during one SSH session to avoid locking yourself out of access.

Step 3: Automated Intrusion Prevention with Fail2Ban

Fail2Ban monitors log files and automatically bans IP addresses showing suspicious behavior, such as repeated failed login attempts.

sudo apt install fail2ban
Enter fullscreen mode Exit fullscreen mode

Modern Ubuntu systems use systemd journaling instead of traditional log files, which requires specific configuration:

sudo nano /etc/fail2ban/jail.local
Enter fullscreen mode Exit fullscreen mode
[DEFAULT]
bantime = 3600      # Ban duration in seconds
findtime = 600      # Time window for counting failures
maxretry = 3        # Maximum attempts before ban

[sshd]
enabled = true
port = ssh
filter = sshd
backend = systemd   # Use systemd journal
journalmatch = _SYSTEMD_UNIT=ssh.service
Enter fullscreen mode Exit fullscreen mode

After that, restart the service for it to use the updated configuration:

sudo systemctl restart fail2ban
Enter fullscreen mode Exit fullscreen mode

Step 4: Docker Security Configuration

I am a big fan of Docker. I decided to go full docker. It will bring little few more steps but we'll get better security as all services accessible from internet will run in an isolated docker containers.

It also keeps our host machine clean of unecessary installed packages.

Installation is pretty simple and a lot of VPS providers offers fresh install with docker pre-istalled.

When docker is installed create /etc/docker/daemon.json with following content:

{
  "live-restore": true,
  "userland-proxy": false,
  "no-new-privileges": true,
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}
Enter fullscreen mode Exit fullscreen mode

The no-new-privileges setting is particularly important as it prevents containers from escalating their privileges during runtime, blocking a common attack vector.

As before, don't forget to restart the service:

sudo systemctl restart docker
Enter fullscreen mode Exit fullscreen mode

Current Security Status

The VPS now has:

  • SSH access secured with custom port and key-only authentication
  • Firewall blocking unauthorized connections
  • Automated intrusion detection and response
  • Hardened Docker daemon configuration

This setup effectively blocks automated attacks and provides a solid security foundation for hosting web applications.

Coming Up in Part 2

The next article will cover:

  • Setting up a reverse proxy with Nginx in Docker
  • SSL certificate management with Let's Encrypt
  • Container security best practices
  • System monitoring and maintenance

These foundational security measures provide essential protection while maintaining accessibility for legitimate use.

Top comments (1)

Collapse
 
leob profile image
leob

"Since applications run in Docker containers" - well, that's a big and unproven assumption :-) ... but for the rest, yeah nice, some useful tips/guidelines :)