A fresh Proxmox install is accessible from your entire LAN with a password. That’s convenient for getting started and a security liability for anything beyond that. This post covers the hardening steps that should happen before you start deploying workloads — SSH keys, the Proxmox firewall, certificate management, and the checks that verify your changes worked.
This is part of the Building a Homelab series. Your cluster should already be formed — see Forming a Proxmox Cluster first.
Why a homelab needs security hardening
“It’s just on my home network” is a reasonable starting point, but it’s not the whole picture:
- Any device on your home network — phones, smart TVs, game consoles — can potentially reach your Proxmox web UI and attempt authentication
- If Tailscale is installed, your cluster is reachable from anywhere you have a Tailscale client — which means a stolen phone is a path to your cluster
- Bad habits built on an “it’s just a homelab” mindset carry forward to production environments
The hardening in this post takes about 30 minutes and significantly raises the bar for unauthorized access without making legitimate administration harder. If you want an independent checklist to cross-reference, cr0x.net’s Proxmox security checklist and Virtualization Howto’s PVE 9 hardening guide cover the same ground and are worth reading alongside this one.
Task 1: Set up SSH key authentication
Password-based SSH is vulnerable to brute force attacks. SSH keys are cryptographically much stronger — a correctly generated key pair is practically impossible to brute-force.
On your laptop (the machine you’ll administer the cluster from):
# Generate an Ed25519 key (modern, fast, secure)
ssh-keygen -t ed25519 -C "homelab-admin"
# When prompted:
# - File: press Enter to accept default (~/.ssh/id_ed25519)
# - Passphrase: enter a passphrase (strongly recommended)
# A passphrase protects the key if your laptop is stolen
This creates two files:
~/.ssh/id_ed25519— your private key (never share this, never leave it on a server)~/.ssh/id_ed25519.pub— your public key (safe to share — goes on servers)
ssh-copy-id root@10.0.0.70 # pvelab01
ssh-copy-id root@10.0.0.71 # pvelab02
ssh-copy-id root@10.0.0.72 # pvelab03
ssh-copy-id root@10.0.0.73 # pvelab04
Each ssh-copy-id command prompts for the root password (the last time you’ll need it for SSH). It appends your public key to ~/.ssh/authorized_keys on the node.
Test that key auth works:
ssh root@10.0.0.70 "echo 'Key auth working on pvelab01'"
# Should succeed without asking for a password
Once you’ve verified key-based login works, disable password auth on all nodes:
sed -i 's/^#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
sed -i 's/^PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config
# Verify the change
grep "PasswordAuthentication" /etc/ssh/sshd_config
# Reload SSH daemon (does not disconnect existing sessions)
systemctl reload sshd
Run this on all four nodes. After this, only SSH key authentication works — password guessing is impossible via SSH.
Stay connected to pvelab01 in your current terminal while testing the change. Open a new terminal window and verify you can still SSH in with key auth before closing the old session. If you get locked out, you’ll need console access (Proxmox web terminal or physical keyboard).
For maximum security, disable direct root SSH access. You’d SSH in as your admin user and sudo/su to root only when needed:
# First, make sure your admin user is set up (from the install guide)
# Then disable root SSH:
sed -i 's/^PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config
sed -i 's/^#PermitRootLogin prohibit-password/PermitRootLogin no/' /etc/ssh/sshd_config
systemctl reload sshd
The Proxmox web UI still uses root credentials for its internal communication — this doesn’t break the web UI. It only affects SSH connections.
Task 2: Configure the Proxmox firewall
The Proxmox built-in firewall operates at three levels: cluster (Datacenter), node, and VM/CT. It uses iptables/nftables under the hood, but is managed through the web UI and API.
Before enabling the firewall, define your trusted networks as named groups. This makes rules readable and easier to update.
In the Proxmox web UI: Datacenter → Firewall → IPSet → Create.
| Set name | CIDR to add | Purpose |
|---|---|---|
lablan |
10.0.0.0/24 |
Your cluster subnet |
homelan |
192.168.1.0/24 |
Your home network (adjust to match yours) |
tailscale |
100.64.0.0/10 |
Tailscale address space |
In Datacenter → Firewall → Rules → Add, create these rules in order:
| # | Direction | Action | Source | Proto | Ports | Comment |
|---|---|---|---|---|---|---|
| 1 | in | ACCEPT | +lablan |
any | any | All traffic from cluster subnet |
| 2 | in | ACCEPT | +homelan |
tcp | 22, 8006 | SSH + web UI from home network |
| 3 | in | ACCEPT | +tailscale |
any | any | Tailscale peer access |
Rules are evaluated in order. Add the most permissive (lab LAN) rule first. If you later need to block specific cluster IPs, add more specific rules above the general one.
Datacenter → Firewall → Options:
- Firewall: Yes
- Input policy: DROP
- Output policy: ACCEPT
Click Save.
Before closing the web UI, verify you can still reach it (refresh the page). Keep your SSH session open. If the page loads and SSH still works, your rules are correct.
If you lose access: SSH to any node and run pvesh set /cluster/firewall/options --enable 0 to disable the firewall from the command line.
Select any node → Firewall → Options. Enable:
- SMURFS: Yes — blocks SMURF amplification attacks
- TCP flags filter: Yes — blocks malformed/crafted TCP packets
These are simple checkboxes that add additional iptables rules for common attack vectors. Enable them on all four nodes.
Task 3: Secure the web UI
By default, Proxmox uses a self-signed certificate. Browsers warn about it and connections aren’t authenticated. For a homelab, this is acceptable — the certificate still provides encryption, just not server identity verification.
If you want a trusted certificate (no browser warning), you have two options:
Option A: Let’s Encrypt via ACME (requires a public domain)
In Node → Certificates → ACME, configure your domain. This requires your Proxmox web UI to be reachable from the internet (or use DNS-01 challenge with a domain registrar API). Not recommended for a homelab unless you have a specific reason.
Option B: Add the self-signed cert to your browser’s trust store
Export the current cert from any node and import it into your browser/OS as trusted. This eliminates the warning without needing a public domain.
# View the Proxmox certificate details
openssl x509 -in /etc/pve/pve-ssl.pem -text -noout | grep -E "Subject|DNS|Validity|Not After"
Proxmox supports TOTP (authenticator app) and WebAuthn (hardware security key) as second factors.
To enable TOTP for your admin user:
- Log into the Proxmox web UI
- Click your username in the top-right corner → My Account
- Under Two Factor Auth, click Add
- Select TOTP
- Scan the QR code with Google Authenticator, Bitwarden Authenticator, Aegis (recommended for Android — open source), or similar
- Enter a test code to confirm it’s working
- Click Save
From this point, logging in requires both your password and the current TOTP code. Even if someone gets your password, they can’t log into the web UI without your phone.
If you’re using any automation (scripts, the Flutter app, Ansible) to access Proxmox, use an API token instead of the root password. API tokens can be scoped to specific roles and revoked without changing the root password.
# Create a token for your admin user
pveum user token add youradmin@pam automation-token --privsep=1
# The token secret is shown once — save it securely
# Format: youradmin@pam!automation-token=<uuid>
Grant the token only the roles it needs:
# PVEAuditor = read-only access (monitoring scripts, dashboards)
pveum aclmod / -token youradmin@pam!automation-token -role PVEAuditor
# PVEAdmin = full access (if your automation creates/manages containers)
# pveum aclmod / -token youradmin@pam!automation-token -role PVEAdmin
Task 4: Harden the underlying Debian system
Proxmox runs on Debian. The OS-level hardening matters as much as the Proxmox-specific settings.
apt update && apt dist-upgrade -y
Run this on all nodes. Consider setting up unattended security updates:
apt install -y unattended-upgrades
cat > /etc/apt/apt.conf.d/50unattended-upgrades << 'EOF'
Unattended-Upgrade::Allowed-Origins {
"Debian:bookworm-security";
};
Unattended-Upgrade::Automatic-Reboot "false";
EOF
dpkg-reconfigure -plow unattended-upgrades
Even with password auth disabled, failed key attempts can flood your logs and waste resources. fail2ban automatically bans IPs that make too many failed authentication attempts:
apt install -y fail2ban
cat > /etc/fail2ban/jail.d/sshd.conf << 'EOF'
[sshd]
enabled = true
port = 22
filter = sshd
maxretry = 5
bantime = 3600
findtime = 600
EOF
systemctl enable --now fail2ban
With this config, an IP making 5 failed SSH attempts within 10 minutes is blocked for 1 hour.
Proxmox runs several services by default. Review and disable anything you don’t use:
systemctl list-units --state=running --type=service
Services you probably don’t need on a homelab:
postfix— local mail server. Unless you’re routing alert emails through Proxmox directly (most people use Grafana/Alertmanager for this), disable it:
systemctl disable --now postfix
Don’t disable Proxmox’s own services (pvedaemon, pveproxy, pvestatd, pve-cluster, corosync) — those are required.
Task 5: Security audit — verify your hardening worked
# Install nmap if needed: sudo apt install nmap
nmap -sS -O 10.0.0.70
Expected open ports on a hardened Proxmox node:
- 22 — SSH (key auth only)
- 8006 — Proxmox web UI (HTTPS)
- 111, 2049 — NFS (only if NFS is enabled)
- 3128 — Spice proxy (only if VM console access is enabled)
Ports that should NOT be open unless you specifically configured them:
- 80 (plain HTTP)
- 3306 (MySQL)
- 5432 (PostgreSQL)
- Any high port not corresponding to a service you installed
If you see unexpected open ports, identify the service (ss -tlnp | grep <port>) and disable it.
# This should fail with "Permission denied (publickey)"
ssh -o PasswordAuthentication=yes -o PubkeyAuthentication=no root@10.0.0.70
If you see a password prompt, password auth is still enabled. Go back and recheck the /etc/ssh/sshd_config change and systemctl reload sshd.
From a device NOT in your homelan or lablan IPSETs (e.g., a device on a guest WiFi network):
# Web UI access should time out (not "connection refused" — DROPPED)
curl -k --connect-timeout 5 https://10.0.0.70:8006
# Expected: curl: (28) Connection timed out
# SSH should also time out
ssh -o ConnectTimeout=5 root@10.0.0.70
# Expected: ssh: connect to host 10.0.0.70 port 22: Connection timed out
“Connection timed out” means the firewall is silently dropping the packets — correct behavior. “Connection refused” means the port is open but nothing is listening — the service is down, not the firewall. “Connected” means the firewall isn’t working.
Ongoing security practices
A one-time hardening pass degrades over time. Build these habits:
- Monthly:
apt update && apt dist-upgrade -yon all nodes - After any incident or staff change: Rotate API tokens, review
authorized_keys - When adding new services: Review what ports they expose and add appropriate firewall rules
- Before exposing anything to the internet: Re-run the port scan, review all firewall rules, ensure TLS is configured
Summary of what this hardening achieves
| Attack vector | Before | After |
|---|---|---|
| SSH brute force | Possible (password auth) | Impossible (key auth only + fail2ban) |
| Web UI from untrusted network | Open to any LAN device | Restricted to homelan + lablan + Tailscale |
| Root access via weak password | Possible | Impossible via SSH (key auth); web UI requires password + TOTP |
| Open unnecessary ports | Default Debian services may be exposed | Audited and unused services disabled |
| Outdated packages | Manual | Automatic security updates enabled |
With this configuration in place, your cluster is meaningfully more secure than the default while remaining fully accessible from your usual devices and locations.
Next steps
With a secure, running cluster, it’s time to start deploying workloads. A good first container is a monitoring stack — Prometheus + Grafana — so you have visibility into the cluster before adding anything else.
→ See: Node Exporter + pve-exporter: Complete Proxmox Metrics in Grafana
Also consider:
- Proxmox Firewall Guide: Rules, Zones, and Container Isolation — depth on the Proxmox firewall touched in this guide
- Proxmox Backup Server: Automated Container Backups — protect your data after you’ve protected your access
- Tailscale Subnet Router: Remote Access to Your Entire Homelab — the safe way to access a hardened cluster remotely
- Proxmox VM vs LXC: When to Use Each — isolation trade-offs that decide when a workload needs a VM’s hard boundary
This post contains Amazon affiliate links (tag: buildahomelab-20). I earn a small commission on qualifying purchases at no extra cost to you.