A fresh Proxmox install works. You can create VMs, manage storage, access the web UI. But “works” isn’t “secure.” The default configuration prioritizes convenience over hardening. That’s fine for the installer — you need access to finish setup. It’s not fine for production.
Security is easier to implement now, in the first hour, than “someday later.” Later never comes. And when it does, it’s usually because something bad happened.
This is the post-install hardening I do on every Proxmox host before it runs any workload.
User Management
Stop Using Root for Everything
The web UI logs you in as root. SSH defaults to root. This works, but:
- Root has no audit trail (who did what?)
- Root can destroy everything with one typo
- Shared root password = no accountability
Create personal admin accounts:
# Create user with admin accessuseradd -m -s /bin/bash adminpasswd admin
# Add to sudo groupusermod -aG sudo adminNow add this user to Proxmox:
# Create Proxmox user (realm = pam for Linux users)pveum user add admin@pam
# Grant Administrator rolepveum acl modify / --user admin@pam --role AdministratorYou can now log into the web UI as admin@pam instead of root.
Proxmox Authentication Realms
Proxmox has multiple authentication realms:
- pam: Linux system users. Best for admins who also need SSH.
- pve: Proxmox internal users. Web UI only, no SSH access.
- LDAP/AD: Enterprise directory integration.
For small deployments, PAM users are simplest. One password for SSH and web UI.
Two-Factor Authentication
Enable 2FA for all admin accounts:
- Web UI → Datacenter → Permissions → Two Factor
- Add TOTP for your user
- Scan QR code with authenticator app
This protects against password compromise. Even if someone gets your password, they need the TOTP code.
SSH Hardening
Default SSH is password authentication as root. Every botnet on the internet is scanning for this.
Generate SSH Keys
On your workstation:
ssh-keygen -t ed25519 -C "admin@proxmox"Copy to the server:
ssh-copy-id admin@pve1.lab.localHarden sshd_config
Edit /etc/ssh/sshd_config:
# Disable root loginPermitRootLogin no
# Disable password authenticationPasswordAuthentication no
# Only allow specific usersAllowUsers admin
# Use only strong algorithmsKexAlgorithms curve25519-sha256@libssh.org,diffie-hellman-group16-sha512Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.comMACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
# Reduce login grace timeLoginGraceTime 30
# Limit authentication attemptsMaxAuthTries 3
# Disable unused featuresX11Forwarding noAllowTcpForwarding noAllowAgentForwarding noApply changes:
systemctl restart sshdTest before disconnecting. Open a new terminal, verify you can log in with your key. Don’t lock yourself out.
Fail2Ban (Optional but Recommended)
Even with key-only auth, bots still try. Fail2ban reduces log noise:
apt install fail2ban
# Create local configcat > /etc/fail2ban/jail.local << 'EOF'[sshd]enabled = trueport = sshfilter = sshdlogpath = /var/log/auth.logmaxretry = 3bantime = 3600findtime = 600EOF
systemctl enable --now fail2banCheck banned IPs:
fail2ban-client status sshdHost Firewall
Proxmox VMs get their own firewall (we’ll cover that later). But the host itself needs protection too.
Proxmox Built-in Firewall
Proxmox has a built-in firewall. Enable it for the datacenter and node:
# Enable firewall at datacenter levelpvesh set /cluster/firewall/options --enable 1
# Enable for this nodepvesh set /nodes/pve1/firewall/options --enable 1Or via Web UI: Datacenter → Firewall → Options → Enable: Yes
Default Policies
Set default to drop, then allow what you need:
# Set default input policy to DROPpvesh set /cluster/firewall/options --policy_in DROP
# Allow established connectionspvesh create /cluster/firewall/rules --action ACCEPT --type in --enable 1 --macro ESTABLISHED
# Allow SSHpvesh create /cluster/firewall/rules --action ACCEPT --type in --enable 1 --dport 22 --proto tcp --comment "SSH"
# Allow Proxmox web UIpvesh create /cluster/firewall/rules --action ACCEPT --type in --enable 1 --dport 8006 --proto tcp --comment "Proxmox Web UI"
# Allow ICMP (ping)pvesh create /cluster/firewall/rules --action ACCEPT --type in --enable 1 --proto icmp --comment "ICMP"Management Network Restriction
Better: restrict management access to specific subnets:
# Only allow SSH from management networkpvesh create /cluster/firewall/rules --action ACCEPT --type in --enable 1 --dport 22 --proto tcp --source 10.0.0.0/24 --comment "SSH from mgmt"
# Only allow web UI from management networkpvesh create /cluster/firewall/rules --action ACCEPT --type in --enable 1 --dport 8006 --proto tcp --source 10.0.0.0/24 --comment "Web UI from mgmt"Cluster Communication
If you’re clustering, allow inter-node traffic:
# Corosyncpvesh create /cluster/firewall/rules --action ACCEPT --type in --enable 1 --dport 5405:5412 --proto udp --source 10.0.0.0/24 --comment "Corosync"
# Live migrationpvesh create /cluster/firewall/rules --action ACCEPT --type in --enable 1 --dport 60000:60050 --proto tcp --source 10.0.0.0/24 --comment "Migration"
# Proxmox cluster APIpvesh create /cluster/firewall/rules --action ACCEPT --type in --enable 1 --dport 85 --proto tcp --source 10.0.0.0/24 --comment "Cluster API"Automatic Updates
Security updates shouldn’t wait for you to remember. Automate them.
Unattended Upgrades
apt install unattended-upgrades apt-listchanges
# Enable automatic updatesdpkg-reconfigure -plow unattended-upgradesConfigure /etc/apt/apt.conf.d/50unattended-upgrades:
Unattended-Upgrade::Allowed-Origins { "${distro_id}:${distro_codename}"; "${distro_id}:${distro_codename}-security"; "${distro_id}:${distro_codename}-updates"; "Proxmox:${distro_codename}";};
// Email notificationUnattended-Upgrade::Mail "admin@example.com";Unattended-Upgrade::MailReport "on-change";
// Don't auto-reboot (hypervisor needs planned reboots)Unattended-Upgrade::Automatic-Reboot "false";
// Remove unused dependenciesUnattended-Upgrade::Remove-Unused-Dependencies "true";Manual Update Workflow
For major updates or kernel changes, manual process is safer:
# Check what will be updatedapt updateapt list --upgradable
# Apply updatesapt full-upgrade
# Check if reboot neededcat /var/run/reboot-required 2>/dev/null && echo "Reboot required"
# Check Proxmox versionpveversion -vSchedule maintenance windows. Don’t reboot during business hours if you can avoid it.
Additional Hardening
Disable Unused Services
Check what’s listening:
ss -tlnpIf you’re not using Spice or VNC consoles directly:
# Don't disable if you use them!# systemctl disable --now spiceproxyKernel Parameters
Add to /etc/sysctl.d/99-security.conf:
# Disable IP forwarding (enable if needed for routing VMs)# net.ipv4.ip_forward = 0
# Ignore ICMP redirectsnet.ipv4.conf.all.accept_redirects = 0net.ipv6.conf.all.accept_redirects = 0
# Don't send ICMP redirectsnet.ipv4.conf.all.send_redirects = 0
# Enable SYN flood protectionnet.ipv4.tcp_syncookies = 1
# Log martian packetsnet.ipv4.conf.all.log_martians = 1
# Ignore broadcast pingsnet.ipv4.icmp_echo_ignore_broadcasts = 1
# Restrict dmesgkernel.dmesg_restrict = 1Apply:
sysctl -p /etc/sysctl.d/99-security.confFilesystem Hardening
Mount options for security:
# Check current mountsmount | grep -E 'ext4|zfs'For non-ZFS systems, consider noexec on /tmp. For ZFS, Proxmox handles mount options appropriately.
Audit Logging
Install and configure auditd for compliance requirements:
apt install auditd audispd-plugins
# Basic rulescat >> /etc/audit/rules.d/audit.rules << 'EOF'# Monitor sudo usage-w /etc/sudoers -p wa -k sudoers-w /etc/sudoers.d/ -p wa -k sudoers
# Monitor SSH config-w /etc/ssh/sshd_config -p wa -k sshd
# Monitor user/group changes-w /etc/passwd -p wa -k passwd-w /etc/group -p wa -k groupEOF
systemctl restart auditdBackup Your Config
Before you forget what you configured:
# Backup host configtar -czf /root/pve-config-$(date +%Y%m%d).tar.gz /etc/pve /etc/ssh /etc/apt
# Copy off-hostscp /root/pve-config-*.tar.gz backup-server:/backups/Do this after any significant configuration change.
Security Checklist
Run through this after every install:
[ ] Non-root admin user created[ ] Admin user has 2FA enabled[ ] SSH key-only authentication[ ] Root SSH login disabled[ ] Host firewall enabled[ ] Management access restricted to trusted networks[ ] Unattended security updates configured[ ] Fail2ban installed (optional)[ ] Initial config backed upWhat I Don’t Do
Some hardening guides go overboard. Here’s what I skip and why:
Change SSH port: Security through obscurity. Attackers scan all ports. Fail2ban is more effective.
Disable IPv6: If you’re not using it, fine. But disabling often breaks things in unexpected ways.
Complex MAC/SELinux policies: On a hypervisor, the VMs are the workload. Host runs minimal services. Default policies are usually sufficient.
Intrusion detection (OSSEC, etc.): Good for compliance. For homelab, log monitoring and backups are more practical.
The Lesson
Security is easier to do now than “someday later.”
A fresh install is a blank slate. Every change you make is documented in your head. Wait a month, and you’ve forgotten what’s default and what you configured. Wait a year, and the system is running workloads you’re afraid to touch.
The first hour after install is when hardening is easiest:
- You haven’t forgotten what’s there
- No workloads depend on insecure defaults
- Changes don’t require maintenance windows
- Documentation is fresh
Do it now. These configurations survive upgrades. The 30 minutes you spend today prevent the 3 AM incident next year.