Hardening Proxmox VE
A Proxmox node is a hypervisor, which means compromising it is not compromising a server, it is compromising every VM and container on it at once. That raises the stakes of the ordinary things, the web interface, the login, the SSH, the firewall, because each is now a door into the whole fleet rather than one machine. The hardening splits cleanly: protect the management plane that controls everything and understand which of the two workload types actually isolates and which is a host-root escape waiting for an excuse.
The management interface is not an internet service
The Proxmox web UI listens on 8006, ships with a self-signed certificate and is perfectly happy to answer the internet, which is where a surprising number of them sit. It should not. The management interface belongs on a management network reached over VPN or through a bastion, never directly exposed, with the firewall allowing 8006 and the node’s SSH only from the admin range:
# Datacenter or node firewall: management reachable from admin net only
IN ACCEPT -source 10.0.10.0/24 -p tcp -dport 8006
IN ACCEPT -source 10.0.10.0/24 -p tcp -dport 22
IN DROP
Behind that, replace the self-signed certificate with one from your internal CA (or ACME, which Proxmox integrates) so administrators stop training themselves to click through certificate warnings, which is the habit that makes a man-in-the-middle trivial.
Login: not root and not without a second factor
Proxmox authenticates against realms and the reflex is to log in as root@pam and share the password. Two changes matter. Give administrators named accounts in an appropriate realm, mapped to Proxmox roles, so actions are attributable and root@pam is reserved for break-glass rather than daily use. And make a second factor mandatory, because the web UI controls the whole fleet and a single phished password should not: Proxmox has built-in TOTP and WebAuthn and TFA can be required per realm.
RBAC is the other half of the login story. Proxmox roles are granular and scoped to a path (a pool, a VM, a storage), so an operator who manages one set of VMs gets a role on that pool and nothing at the datacenter level. API tokens are where this is most often undone: a token with PVEAdmin or the full root@pam privileges, pasted into an automation, is a full-cluster credential in a config file. Scope every token to the minimum role and path it needs and prefer separate tokens per integration so one can be revoked without breaking the others.
LXC: the privileged container is the real escape
Proxmox runs two workload types with very different isolation and conflating them is the classic mistake. A KVM virtual machine is a strong boundary: it is a real guest kernel behind the hypervisor and a guest-to-host escape is a genuine QEMU/KVM exploit, rare and valuable. An LXC container shares the host kernel, exactly like Docker, so the same rule applies: a privileged LXC is a host-root escape with a config flag.
Proxmox defaults new containers to unprivileged, which maps container-root to an unprivileged host UID through a user namespace and that default is the right one. The hardening is to keep it and to resist the “just make it privileged, it was the only way to get the mount working” that produces the dangerous config:
# unprivileged container, no risky features
unprivileged: 1
features: nesting=0,keyctl=0
A privileged LXC (unprivileged: 0), especially with features: nesting=1 or device passthrough, is the Proxmox equivalent of docker run --privileged: inside it, reaching the host filesystem and devices is routine. When a workload genuinely needs that level of host access, it belongs in a KVM VM, where the boundary is real, not in a privileged container where the boundary is a polite fiction.
Turn the firewall on
The Proxmox firewall exists at datacenter, node and VM level and it is disabled by default, which means out of the box every VM can talk to every other VM and to the management network. Enable it, default-deny and write the allows per VM so the guest network is segmented rather than flat:
# /etc/pve/firewall/cluster.fw
[OPTIONS]
enable: 1
policy_in: DROP
Per-VM rules then permit only the flows each guest needs, which turns one compromised VM from “can reach everything” into “can reach the two services it was allowed to”. SDN zones and VNets give you proper tenant separation on top when the estate needs it, but the per-VM default-deny is the floor.
The cluster and the host underneath
A Proxmox cluster coordinates over Corosync and Corosync should have its own dedicated network, both because it is latency-sensitive (a congested link causes false failovers and fencing) and because the cluster’s internal traffic has no business sharing a wire with guest traffic. The same separation applies to storage: Ceph or the storage replication network is its own 10GbE segment, as much for isolation as for the throughput it needs.
Underneath all of it, the node is a Debian host, so the Linux and SSH hardening apply in full: keys-only SSH restricted to the admin network, the automatic security updates kept current (the no-subscription repository is fine for that, the mistake is running an unpatched node for months) and encrypted, tested backups, because a hypervisor’s backups are a copy of every VM on it and deserve the protection the originals get.
Where to land
Keep the 8006 interface off the internet, behind VPN or a bastion and a default-deny firewall, with a real certificate. Log in as named accounts with mandatory two-factor, reserve root@pam for emergencies and scope every role and API token to the minimum path rather than handing out PVEAdmin. Keep LXC containers unprivileged and send anything needing real host access to a KVM VM instead, because the VM boundary is real and the privileged-container one is not. Enable the firewall default-deny at the datacenter and per VM, give Corosync and storage their own networks and harden the underlying Debian host like the critical server it is. The node holds every VM you run; its security is their security.
[edit: 2026] The Ansible implementation of all this: the proxmox role .