Skip to main content

VPS Hardening

A systematic process of configuring and reducing the attack surface of the Linux operating system on a virtual server through privilege restrictions, cryptographic isolation, and network auditing.

1. Concept Overview & Systemic Problem

Immediately after initializing a virtual server (VPS) with a cloud provider (Hetzner, DigitalOcean, AWS), the instance is subjected to scanning by global botnets within the first 5-15 minutes. By default, many OS images have password authentication enabled for the root user, open service ports, and lack basic protection against password brute-forcing.

VPS Hardening is an engineering regulation for enhancing the security of the Linux operating system according to CIS Benchmarks (Center for Internet Security). The goals of hardening are to:

  • Minimize the attack surface.
  • Completely eliminate the possibility of password login over unsecured channels.
  • Isolate process execution from the superuser (Root).
  • Configure event auditing, automatic closure of zero-day vulnerabilities, and packet filtering at the kernel level.
Initial VPS State (High Risk):
[Internet] ---> [Port 22: SSH (Root + Password)] ---> [Full System Compromise]

After Hardening (Multi-Layer Defense):
[Internet] ---> [UFW Firewall (Default Deny)]
                     |
                     v
                [Fail2ban (Blocking Scanners)]
                     |
                     v
                [SSH: Only Ed25519 Keys, Non-Root sudo]
                     |
                     v
                [Unattended Upgrades]

2. Architectural Taxonomy & Mental Model

Hardening unfolds across four isolated security planes:

  1. Identity & Access Level:
    • Complete deactivation of direct root login via SSH (PermitRootLogin no).
    • Disabling password authentication (PasswordAuthentication no).
    • Creating a non-privileged user with mandatory membership in the sudo group.
    • Setting up Ed25519 cryptographic keys.
  2. Network Perimeter Level:
    • Enabling ufw with a policy to deny all incoming packets (default deny incoming).
    • Opening only essential ports (e.g., 2222/tcp, 80/tcp, 443/tcp).
    • Deploying fail2ban for automatic banning of hosts generating anomalous requests.
  3. OS & Kernel Level:
    • Tuning sysctl parameters to protect the network stack (disabling ICMP redirects, protection against SYN-flood attacks).
    • Enabling extended security updates (unattended-upgrades).
  4. Runtime Isolation Level:
    • Running Docker containers and AI agents under separate non-privileged UID/GID without access to /var/run/docker.sock.

3. Technical Pipeline & Internal Mechanics

Step 1. Create a sudo user and deploy SSH key

After connecting to the clean server with a temporary root password, immediately create an engineering user:

# Create user deploy with home directory and zsh/bash
adduser --gecos "" deploy
usermod -aG sudo deploy

# Copy the Ed25519 public key
mkdir -p /home/deploy/.ssh
cp /root/.ssh/authorized_keys /home/deploy/.ssh/
chown -R deploy:deploy /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
chmod 600 /home/deploy/.ssh/authorized_keys

Step 2. Hardening the OpenSSH daemon (/etc/ssh/sshd_config.d/99-hardening.conf)

Create an isolated SSH configuration file to protect against overwriting during package updates:

# Change port (optional but recommended)
Port 2222

# Full prohibition of root access and passwords
PermitRootLogin no
PasswordAuthentication no
ChallengeResponseAuthentication no
KbdInteractiveAuthentication no

# Allow only trusted cryptographic keys
PubkeyAuthentication yes
AuthenticationMethods publickey

# Protect sessions from hanging
ClientAliveInterval 300
ClientAliveCountMax 2

# Disable dangerous forwarding features
X11Forwarding no
AllowTcpForwarding yes

Testing the configuration before restart:

# Validate syntax (if empty output — config is correct)
sudo sshd -t
sudo systemctl restart sshd

Step 3. Configure Linux kernel parameters (/etc/sysctl.d/99-security.conf)

# Protection against TCP SYN Flood attacks
net.ipv4.tcp_syncookies = 1

# Disable packet routing redirects (protection against Man-in-the-Middle)
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0

# Ignore spoofed ICMP responses
net.ipv4.icmp_echo_ignore_broadcasts = 1

# Disable IP Source Routing
net.ipv4.conf.all.accept_source_route = 0

Applying kernel changes without reboot:

sudo sysctl --system

4. Production Engineering Scenarios

01. Automated Hardening via Cloud-Init on Hetzner Cloud

When creating a virtual server through Terraform or the Hetzner control panel, a User Data script (cloud-init) is passed. The server launches with root access closed, a pre-created user, generated UFW rules, and SSH configured on a non-standard port, completely eliminating the vulnerability window in the first minutes of the machine's life.

02. Automatic Security Patch Updates for the Kernel Without Intervention

Configuring the unattended-upgrades package in Ubuntu:

sudo apt install unattended-upgrades update-notifier-common
sudo dpkg-reconfigure --priority=low unattended-upgrades

The server autonomously downloads and installs critical security updates from the official Security repositories nightly, and the needrestart utility automatically restarts compromised services without rebooting the operating system.

03. Isolation of Internal Docker Daemon Traffic

Docker automatically attempts to expose ports on all network interfaces (0.0.0.0). During hardening, the file /etc/docker/daemon.json is configured with a default binding restriction:

{
  "iptables": true,
  "live-restore": true,
  "userland-proxy": false
}

This ensures that services without explicit mapping to the local host remain within the Docker virtual network bridge and do not expose ports to the public internet.


5. Pitfalls, Common Mistakes & Security

  1. Accidental Self-Lock (SSH Lockout): The most common engineer mistake: closing password access and prohibiting root without checking if the public key for the new user works or if the new port is allowed in UFW. Never close the initial terminal session until you have successfully connected in a parallel tab.
  2. Storing sudo Password in Scripts or .bash_history: Using echo "password" | sudo -S ... in shell scripts or entering passwords in the terminal leads to leaks in plain text in command history. Use sudo visudo to delegate specific commands without a password (NOPASSWD) for narrowly specialized service accounts in CI/CD.
  3. Ignoring Security of Out-of-Band Provider Console: Even a perfectly hardened server can be compromised if the account in the hosting provider's console (Hetzner Cloud Console, DigitalOcean Dashboard) is protected by a simple password without hardware two-factor authentication (2FA / WebAuthn). Access to the provider console is equivalent to direct physical access to the server's motherboard.
/ Frequently Asked QuestionsSchema.org FAQPage

FAQ: VPS Hardening

Root has unrestricted access to the kernel, process memory, and the file system. Upon a successful breach or operator error, an attacker gains full control over the node without the ability to audit the actions of a specific individual. Disabling PermitRootLogin and creating a dedicated sudo user enforces the principle of least privilege and logs all privileged commands in /var/log/auth.log.
/ Internal links
All terms