Hardening Proxmox VE: Firewall, fail2ban, and SSH Security

Secure your Proxmox VE installation with built-in firewall rules, fail2ban configuration, SSH hardening, and two-factor authentication. Essential security guide.

Proxmox Pulse Proxmox Pulse
14 min read
proxmox security firewall fail2ban ssh hardening
Fortified server with firewall barriers deflecting attacks and secure SSH entry point

The Problem with a Default Install

A fresh Proxmox VE installation is functional but far from secure. The web UI listens on port 8006 on all interfaces. SSH is open with password authentication enabled. Root login is allowed. There's no intrusion detection, no brute-force protection, and the built-in firewall is disabled by default.

If your Proxmox host has any exposure beyond a fully trusted LAN — and in 2026, you should assume it does — you need to fix this. I've seen Proxmox boxes get hammered with SSH brute-force attempts within hours of going online. One misconfigured port forward or one flat network with a compromised IoT device is all it takes.

This isn't theoretical paranoia. I've helped clean up after compromises where the entry point was an unhardened hypervisor. The attacker got root on the Proxmox host and had access to every VM on it. Game over. Let's make sure that doesn't happen to you.

SSH Hardening: First Priority

SSH is your biggest attack surface on a default install. Fix it first.

Generate and Deploy SSH Keys

If you're still typing passwords to log into servers, stop. SSH keys are non-negotiable.

On your workstation (not the Proxmox host):

ssh-keygen -t ed25519 -C "admin@homelab"
ssh-copy-id root@192.168.1.10

Test that key-based login works before disabling password auth:

ssh -i ~/.ssh/id_ed25519 root@192.168.1.10

Lock Down sshd_config

Edit /etc/ssh/sshd_config on the Proxmox host. Here's what I change from the defaults:

# /etc/ssh/sshd_config - hardened settings

Port 2222
PermitRootLogin prohibit-password
PasswordAuthentication no
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
ChallengeResponseAuthentication no
UsePAM yes

# Timeout and session limits
ClientAliveInterval 300
ClientAliveCountMax 2
MaxAuthTries 3
MaxSessions 3
LoginGraceTime 30

# Disable unused auth methods
KerberosAuthentication no
GSSAPIAuthentication no
X11Forwarding no

# Only allow specific users (add your admin user)
AllowUsers root adminuser

A few notes on these choices:

Port 2222 — Security through obscurity? Sort of. It won't stop a determined attacker, but it eliminates 99% of automated bot traffic hitting port 22. My auth logs went from thousands of failed attempts per day to nearly zero after changing the port. Worth it for the noise reduction alone.

PermitRootLogin prohibit-password — This allows root login via SSH key only. Some guides say to disable root SSH entirely. I keep it as key-only because Proxmox's management tools and cluster communication sometimes need root access. If you create a separate admin user with sudo, you can set this to no.

MaxAuthTries 3 — Combined with fail2ban, this means an attacker gets 3 attempts before the connection drops, and fail2ban bans their IP after a handful of failures.

Restart SSH (but keep your existing session open as a safety net):

systemctl restart sshd

Open a new terminal and test login. Only proceed if key-based login works in the new session. If you lock yourself out, you'll need console access.

Creating a Non-Root Admin User

Running everything as root is a bad habit. Create a dedicated admin user:

useradd -m -s /bin/bash adminuser
passwd adminuser
usermod -aG sudo adminuser

Copy your SSH key for the new user:

mkdir -p /home/adminuser/.ssh
cp /root/.ssh/authorized_keys /home/adminuser/.ssh/
chown -R adminuser:adminuser /home/adminuser/.ssh
chmod 700 /home/adminuser/.ssh
chmod 600 /home/adminuser/.ssh/authorized_keys

Then add this user to Proxmox with admin privileges:

pveum user add adminuser@pam
pveum acl modify / --roles Administrator --users adminuser@pam

Now you can log into the PVE web UI as adminuser@pam instead of root.

The Proxmox Firewall

Proxmox has a built-in firewall based on iptables/nftables that operates at three levels:

  1. Datacenter level — Rules that apply to all nodes
  2. Node level — Rules specific to a single host
  3. VM/Container level — Rules for individual guests

The firewall is disabled by default. Before enabling it, you need to add rules that allow your management access — otherwise you'll lock yourself out of the web UI.

Enabling the Firewall Safely

Here's my process for safely enabling the firewall. Do this from a console session (not SSH), or at minimum, set up a cron job that disables the firewall after 5 minutes in case you get locked out:

# Safety net: disable firewall in 5 minutes
(sleep 300 && pve-firewall stop) &
echo "Safety timer started - PID: $!"

Datacenter-Level Rules

Go to Datacenter → Firewall → Add and create these baseline rules:

# Type Action Source Dest Port Protocol Comment
1 in ACCEPT 10.10.10.0/24 - 8006 tcp Web UI from mgmt VLAN
2 in ACCEPT 10.10.10.0/24 - 2222 tcp SSH from mgmt VLAN
3 in ACCEPT 10.10.10.0/24 - 3128 tcp SPICE proxy
4 in ACCEPT 10.10.10.0/24 - 5900:5999 tcp VNC console
5 in ACCEPT - - - icmp Allow ping
6 in DROP - - - - Drop everything else

Important: Rule order matters. Proxmox processes rules top-to-bottom, first match wins.

If you're running a cluster, you also need to allow corosync and migration traffic between nodes:

# Type Action Source Dest Port Protocol Comment
7 in ACCEPT 10.10.10.0/24 - 5405:5412 udp Corosync
8 in ACCEPT 10.10.10.0/24 - 60000:60050 tcp Live migration

These rules can also be managed via the config files directly. The datacenter firewall config lives at:

/etc/pve/firewall/cluster.fw
[OPTIONS]
enable: 1
policy_in: DROP
policy_out: ACCEPT

[RULES]
IN ACCEPT -source 10.10.10.0/24 -p tcp -dport 8006 -log nolog # Web UI
IN ACCEPT -source 10.10.10.0/24 -p tcp -dport 2222 -log nolog # SSH
IN ACCEPT -source 10.10.10.0/24 -p tcp -dport 3128 -log nolog # SPICE
IN ACCEPT -source 10.10.10.0/24 -p tcp -dport 5900:5999 -log nolog # VNC
IN ACCEPT -p icmp # Ping
IN ACCEPT -source 10.10.10.0/24 -p udp -dport 5405:5412 -log nolog # Corosync
IN ACCEPT -source 10.10.10.0/24 -p tcp -dport 60000:60050 -log nolog # Migration

Now enable the firewall:

Datacenter → Firewall → Options → Firewall: Yes

Or via CLI:

pve-firewall start

Verify you still have web UI and SSH access. If everything works, kill the safety timer background job. If not, wait for the timer to disable the firewall and try again.

Node-Level Firewall

Each node can have additional rules. The config lives at /etc/pve/nodes/<nodename>/host.fw. I generally keep node-level rules minimal and put most rules at the datacenter level for consistency.

VM-Level Firewall

For individual VMs, enable the firewall on the VM's network interface (Hardware → Network Device → Firewall checkbox) and add rules under VM → Firewall.

A typical web server VM might have:

[RULES]
IN ACCEPT -p tcp -dport 80
IN ACCEPT -p tcp -dport 443
IN ACCEPT -source 10.10.10.0/24 -p tcp -dport 22
IN DROP

Installing and Configuring fail2ban

The Proxmox firewall handles static rules. fail2ban handles dynamic threats — it watches log files for repeated authentication failures and bans offending IPs automatically.

Installation

apt update && apt install -y fail2ban

Protecting SSH

fail2ban ships with an SSH jail that works out of the box, but we need to adjust it for our non-standard port. Create a local config:

cat > /etc/fail2ban/jail.local << 'JAILEOF'
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 3
banaction = nftables-multiport
ignoreip = 127.0.0.1/8 10.10.10.0/24

[sshd]
enabled = true
port = 2222
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600
JAILEOF

Key settings:

  • bantime = 3600 — Ban for 1 hour. Some people use longer bans. I find an hour is enough to stop automated attacks without permanently banning a legitimate user who fat-fingered their key passphrase.
  • findtime = 600 — Look for failures within a 10-minute window.
  • maxretry = 3 — Three strikes and you're out.
  • ignoreip — Never ban your management subnet. You don't want to lock yourself out because of a misconfigured SSH agent.

Protecting the Proxmox Web UI

This is the important one that most guides skip. The PVE web UI at port 8006 has its own authentication, and failed attempts get logged to /var/log/daemon.log. We need a custom filter.

Create the filter:

cat > /etc/fail2ban/filter.d/proxmox.conf << 'FILTEREOF'
[Definition]
failregex = pvedaemon\[.*authentication (verification )?failure; rhost=<HOST> user=\S+ msg=.*
ignoreregex =
journalmatch = _SYSTEMD_UNIT=pvedaemon.service
FILTEREOF

Add the jail to /etc/fail2ban/jail.local:

cat >> /etc/fail2ban/jail.local << 'JAILEOF'

[proxmox]
enabled = true
port = https,http,8006
filter = proxmox
backend = systemd
maxretry = 3
findtime = 600
bantime = 3600
JAILEOF

Restart fail2ban and check status:

systemctl restart fail2ban
fail2ban-client status
Status
|- Number of jail:      2
`- Jails:       proxmox, sshd

Check the individual jail status:

fail2ban-client status sshd
Status for the jail: sshd
|- Filter
|  |- Currently failed: 0
|  |- Total failed:     0
|  `- File list:        /var/log/auth.log
`- Actions
   |- Currently banned: 0
   |- Total banned:     0
   `- Banned IP list:

Testing fail2ban

From a different machine (not in your ignoreip range), deliberately fail a few SSH logins:

ssh -p 2222 -o PubkeyAuthentication=no root@192.168.1.10
# Enter wrong password 3 times

Then check:

fail2ban-client status sshd
Status for the jail: sshd
|- Filter
|  |- Currently failed: 0
|  |- Total failed:     3
|  `- File list:        /var/log/auth.log
`- Actions
   |- Currently banned: 1
   |- Total banned:     1
   `- Banned IP list:   192.168.1.200

To manually unban an IP:

fail2ban-client set sshd unbanip 192.168.1.200

Persistent Banning for Repeat Offenders

For IPs that keep coming back, I use a recidive jail. This catches IPs that get banned repeatedly and applies a much longer ban:

cat >> /etc/fail2ban/jail.local << 'JAILEOF'

[recidive]
enabled = true
filter = recidive
logpath = /var/log/fail2ban.log
bantime = 604800
findtime = 86400
maxretry = 3
JAILEOF

This bans repeat offenders for a week. Three bans in 24 hours triggers the recidive jail. In my experience, most IPs that hit the recidive jail are part of botnets doing credential scanning. A week-long ban keeps them away without needing manual intervention.

Two-Factor Authentication

Passwords and SSH keys cover authentication, but adding a second factor is straightforward in Proxmox and worth the minimal inconvenience.

TOTP Setup

Proxmox supports TOTP (Time-based One-Time Password) natively — works with any authenticator app (Google Authenticator, Authy, Bitwarden TOTP, etc.).

For the web UI:

  1. Log in to PVE as the user you want to enable 2FA for
  2. Go to Datacenter → Permissions → Two Factor Authentication → Add → TOTP
  3. Enter a description (e.g., "Phone Authenticator")
  4. Scan the QR code with your authenticator app
  5. Enter the current code to verify
  6. Save

From now on, logging into the web UI requires both your password and the TOTP code. The login page will show an additional field for the OTP.

Making 2FA Mandatory

To require 2FA for all users in a realm, you can enforce it via the TFA settings. Go to Datacenter → Permissions → Realms → pam → Edit and check the TFA option.

Be careful with this. If you enforce TFA on the pam realm before all users have configured it, they'll be locked out. Set up TFA for your admin account first, verify it works, then enforce it realm-wide.

Recovery Codes

When setting up TOTP, Proxmox also lets you generate recovery codes. Do this. Store them somewhere secure and separate from your 2FA device. I print them and keep them with the server documentation.

If you lose your 2FA device and don't have recovery codes, you'll need to disable 2FA from the command line:

pveum user tfa delete adminuser@pam

This requires root console access to the Proxmox host — which is exactly why you should never fully disable root console login.

API Token Security

If you use the Proxmox API (for Terraform, Ansible, monitoring, etc.), create dedicated API tokens with minimal permissions.

pveum user add terraform@pve --password "StrongPassword"
pveum acl modify /vms --roles PVEVMAdmin --users terraform@pve
pveum user token add terraform@pve terraform-token --privsep 1

The --privsep 1 flag is important — it means the token's permissions are the intersection of the user's permissions and the token's own permissions. Without it, the token inherits all user permissions.

The token output looks like:

┌──────────────┬──────────────────────────────────────────┐
│ key          │ value                                    │
├──────────────┼──────────────────────────────────────────┤
│ full-tokenid │ terraform@pve!terraform-token            │
│ info         │ {"privsep":"1"}                          │
│ value        │ aeb47abc-5bf2-4c3e-b8a1-7f5d0e2c4a81    │
└──────────────┴──────────────────────────────────────────┘

Save that token value immediately — it's shown only once. Treat it like a password.

Never put API tokens in scripts or config files on shared systems. Use environment variables or a secrets manager:

export PM_API_TOKEN_ID="terraform@pve!terraform-token"
export PM_API_TOKEN_SECRET="aeb47abc-5bf2-4c3e-b8a1-7f5d0e2c4a81"

Keeping Proxmox Updated

Security patches don't apply themselves. Set up unattended security updates:

apt install -y unattended-upgrades
dpkg-reconfigure -plow unattended-upgrades

Edit /etc/apt/apt.conf.d/50unattended-upgrades to include Proxmox repositories:

Unattended-Upgrade::Origins-Pattern {
    "origin=Debian,codename=${distro_codename},label=Debian-Security";
    "origin=Proxmox";
};

I configure these to install security updates automatically but send email notifications so I know what was patched:

Unattended-Upgrade::Mail "admin@yourdomain.com";
Unattended-Upgrade::MailReport "on-change";
Unattended-Upgrade::Automatic-Reboot "false";

I never enable automatic reboots on hypervisors. A kernel update might need a reboot, but I want to schedule that during a maintenance window when I can migrate VMs off the node first.

Additional Hardening Measures

A few more things I always do on a new Proxmox install:

Disable the Enterprise Nag

Not security-related, but while we're editing configs:

# Optional: remove subscription nag popup
sed -Ezi.bak "s/(Ext\.Msg\.show\(\{.*?title: gettext\('No valid sub)/void\(\{ \/\/\1/g" /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js
systemctl restart pveproxy

Restrict Cron Access

echo "root" > /etc/cron.allow
echo "adminuser" >> /etc/cron.allow
chmod 644 /etc/cron.allow

Harden Kernel Parameters

Add to /etc/sysctl.d/99-hardening.conf:

cat > /etc/sysctl.d/99-hardening.conf << 'EOF'
# Ignore ICMP redirects
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0

# Ignore send redirects
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0

# Log Martian packets
net.ipv4.conf.all.log_martians = 1

# Disable source routing
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0

# Enable SYN flood protection
net.ipv4.tcp_syncookies = 1

# Disable IPv6 if not needed
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
EOF

sysctl --system

Monitor Auth Logs

Keep an eye on authentication attempts. A quick one-liner to see failed login attempts:

journalctl -u pvedaemon --since "24 hours ago" | grep "authentication failure"

For ongoing monitoring, consider shipping logs to a central syslog server or a monitoring stack like Grafana + Loki on a separate VM. Don't run your monitoring on the same host you're monitoring.

Wrapping Up

Security hardening isn't a one-time task — it's an ongoing practice. But the steps in this guide cover the critical basics that every Proxmox installation should have from day one.

The order matters. SSH hardening first (it's the most commonly attacked surface), then the firewall (to control network access), then fail2ban (to handle brute-force attempts dynamically), then 2FA (defense in depth), and finally ongoing updates.

One thing I want to emphasize: don't let perfect be the enemy of good. If you only do three things from this guide, make them these: switch to SSH key-only authentication, enable the Proxmox firewall with a default-deny policy, and install fail2ban. Those three changes eliminate the vast majority of common attack vectors against a Proxmox host.

After that, schedule a quarterly security review. Check fail2ban stats, review firewall rules, verify 2FA is working, and make sure updates are being applied. Twenty minutes every three months keeps your hypervisor from becoming someone else's bitcoin miner.

Share
Proxmox Pulse

Written by

Proxmox Pulse

Sysadmin-driven guides for getting the most out of Proxmox VE in production and homelab environments.

Related Articles

View all →