Post-Install Baseline: Users, SSH, Firewall, Updates, and Hardening

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:

Terminal window
# Create user with admin access
useradd -m -s /bin/bash admin
passwd admin
# Add to sudo group
usermod -aG sudo admin

Now add this user to Proxmox:

Terminal window
# Create Proxmox user (realm = pam for Linux users)
pveum user add admin@pam
# Grant Administrator role
pveum acl modify / --user admin@pam --role Administrator

You 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:

  1. Web UI → Datacenter → Permissions → Two Factor
  2. Add TOTP for your user
  3. 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:

Terminal window
ssh-keygen -t ed25519 -C "admin@proxmox"

Copy to the server:

Terminal window
ssh-copy-id admin@pve1.lab.local

Harden sshd_config

Edit /etc/ssh/sshd_config:

Terminal window
# Disable root login
PermitRootLogin no
# Disable password authentication
PasswordAuthentication no
# Only allow specific users
AllowUsers admin
# Use only strong algorithms
KexAlgorithms curve25519-sha256@libssh.org,diffie-hellman-group16-sha512
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
# Reduce login grace time
LoginGraceTime 30
# Limit authentication attempts
MaxAuthTries 3
# Disable unused features
X11Forwarding no
AllowTcpForwarding no
AllowAgentForwarding no

Apply changes:

Terminal window
systemctl restart sshd

Test before disconnecting. Open a new terminal, verify you can log in with your key. Don’t lock yourself out.

Even with key-only auth, bots still try. Fail2ban reduces log noise:

Terminal window
apt install fail2ban
# Create local config
cat > /etc/fail2ban/jail.local << 'EOF'
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600
findtime = 600
EOF
systemctl enable --now fail2ban

Check banned IPs:

Terminal window
fail2ban-client status sshd

Host 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:

Terminal window
# Enable firewall at datacenter level
pvesh set /cluster/firewall/options --enable 1
# Enable for this node
pvesh set /nodes/pve1/firewall/options --enable 1

Or via Web UI: Datacenter → Firewall → Options → Enable: Yes

Default Policies

Set default to drop, then allow what you need:

Terminal window
# Set default input policy to DROP
pvesh set /cluster/firewall/options --policy_in DROP
# Allow established connections
pvesh create /cluster/firewall/rules --action ACCEPT --type in --enable 1 --macro ESTABLISHED
# Allow SSH
pvesh create /cluster/firewall/rules --action ACCEPT --type in --enable 1 --dport 22 --proto tcp --comment "SSH"
# Allow Proxmox web UI
pvesh 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:

Terminal window
# Only allow SSH from management network
pvesh 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 network
pvesh 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:

Terminal window
# Corosync
pvesh create /cluster/firewall/rules --action ACCEPT --type in --enable 1 --dport 5405:5412 --proto udp --source 10.0.0.0/24 --comment "Corosync"
# Live migration
pvesh 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 API
pvesh 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

Terminal window
apt install unattended-upgrades apt-listchanges
# Enable automatic updates
dpkg-reconfigure -plow unattended-upgrades

Configure /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 notification
Unattended-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 dependencies
Unattended-Upgrade::Remove-Unused-Dependencies "true";

Manual Update Workflow

For major updates or kernel changes, manual process is safer:

Terminal window
# Check what will be updated
apt update
apt list --upgradable
# Apply updates
apt full-upgrade
# Check if reboot needed
cat /var/run/reboot-required 2>/dev/null && echo "Reboot required"
# Check Proxmox version
pveversion -v

Schedule maintenance windows. Don’t reboot during business hours if you can avoid it.

Additional Hardening

Disable Unused Services

Check what’s listening:

Terminal window
ss -tlnp

If you’re not using Spice or VNC consoles directly:

Terminal window
# Don't disable if you use them!
# systemctl disable --now spiceproxy

Kernel Parameters

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

Terminal window
# Disable IP forwarding (enable if needed for routing VMs)
# net.ipv4.ip_forward = 0
# Ignore ICMP redirects
net.ipv4.conf.all.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
# Don't send ICMP redirects
net.ipv4.conf.all.send_redirects = 0
# Enable SYN flood protection
net.ipv4.tcp_syncookies = 1
# Log martian packets
net.ipv4.conf.all.log_martians = 1
# Ignore broadcast pings
net.ipv4.icmp_echo_ignore_broadcasts = 1
# Restrict dmesg
kernel.dmesg_restrict = 1

Apply:

Terminal window
sysctl -p /etc/sysctl.d/99-security.conf

Filesystem Hardening

Mount options for security:

Terminal window
# Check current mounts
mount | 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:

Terminal window
apt install auditd audispd-plugins
# Basic rules
cat >> /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 group
EOF
systemctl restart auditd

Backup Your Config

Before you forget what you configured:

Terminal window
# Backup host config
tar -czf /root/pve-config-$(date +%Y%m%d).tar.gz /etc/pve /etc/ssh /etc/apt
# Copy off-host
scp /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 up

What 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.