This comprehensive guide covers security best practices for Proxmox VE environments, from host hardening to container security.
Overview
Security should be implemented at multiple layers:
Host Security Secure the Proxmox host itself
Container Security Harden LXC containers and VMs
Network Security Firewall and network isolation
Proxmox Host Hardening
Secure SSH Access
Disable Root Login
Edit /etc/ssh/sshd_config: Create a sudo user: adduser admin
usermod -aG sudo admin
Use SSH Keys
# On your local machine
ssh-keygen -t ed25519 -C "[email protected] "
# Copy to Proxmox host
ssh-copy-id admin@proxmox-host
Disable password authentication: # In /etc/ssh/sshd_config
PasswordAuthentication no
PubkeyAuthentication yes
Change SSH Port (Optional)
# In /etc/ssh/sshd_config
Port 2222
Before disabling root login or password authentication, ensure you can login with your new user and SSH key!
Enable Two-Factor Authentication
Install Required Package
apt update
apt install libpam-google-authenticator
Configure for User
# Login as user
google-authenticator
Answer:
Time-based tokens: Y
Update .google_authenticator: Y
Disallow multiple uses: Y
Increase window: N
Enable rate-limiting: Y
Update PAM Configuration
Add to /etc/pam.d/sshd: auth required pam_google_authenticator.so
Update SSH Configuration
In /etc/ssh/sshd_config: ChallengeResponseAuthentication yes
Enable Firewall at Datacenter Level
Datacenter → Firewall → Options
Enable: Firewall
Create Security Group
Datacenter → Firewall → Security Group → Create Name: management Add rules: IN ACCEPT -p tcp -dport 22 -source 192.168.1.0/24
IN ACCEPT -p tcp -dport 8006 -source 192.168.1.0/24
IN DROP
Apply to Host
Node → Firewall → Options Enable firewall and apply security group
Automatic Security Updates
Install Unattended Upgrades
apt install unattended-upgrades apt-listchanges
Configure Updates
Edit /etc/apt/apt.conf.d/50unattended-upgrades: Unattended-Upgrade::Origins-Pattern {
"origin=Debian,codename=${ distro_codename },label=Debian-Security" ;
"origin=Proxmox" ;
};
Unattended-Upgrade::AutoFixInterruptedDpkg "true" ;
Unattended-Upgrade::Remove-Unused-Dependencies "true" ;
Unattended-Upgrade::Automatic-Reboot "false" ;
Enable Auto-Updates
Edit /etc/apt/apt.conf.d/20auto-upgrades: APT::Periodic::Update-Package-Lists "1" ;
APT::Periodic::Unattended-Upgrade "1" ;
APT::Periodic::AutocleanInterval "7" ;
Set Automatic-Reboot "true" if you want automatic reboots for kernel updates, but schedule appropriately.
Fail2Ban Protection
Configure SSH Protection
Create /etc/fail2ban/jail.local: [DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
[sshd]
enabled = true
port = ssh
logpath = /var/log/auth.log
[proxmox]
enabled = true
port = https,http,8006
logpath = /var/log/daemon.log
maxretry = 3
bantime = 1h
Start Fail2Ban
systemctl enable fail2ban
systemctl start fail2ban
Monitor Bans
# Check status
fail2ban-client status
# Check specific jail
fail2ban-client status sshd
# Unban IP
fail2ban-client set sshd unbanip 192.168.1.100
Container Security
Unprivileged Containers
Unprivileged (Recommended) Benefits:
Root in container ≠ root on host
Better security isolation
Limited escape vectors
Default for new scripts
Privileged Use Only When:
Hardware access needed
Special capabilities required
Docker in LXC (with caution)
Create unprivileged container:
pct create 100 local:vztmpl/debian-13-standard_13.0-1_amd64.tar.zst \
--unprivileged 1 \
--hostname secure-ct
All community scripts default to unprivileged containers when possible.
AppArmor Profiles
AppArmor provides additional security for containers:
Enable AppArmor for Container
pct set < CTI D > -features nesting=1,keyctl= 1
pct set < CTI D > -protection 1
Apply Specific Profile
Edit /etc/pve/lxc/<CTID>.conf: lxc.apparmor.profile: lxc-container-default-cgns
Container Hardening
Limit Capabilities
Restrict System Calls
Mount Options
Remove unnecessary Linux capabilities: # Edit /etc/pve/lxc/<CTID>.conf
lxc.cap.drop: sys_module sys_rawio mknod
Common capabilities to drop:
sys_module - Load kernel modules
sys_rawio - Raw I/O operations
mknod - Create device nodes
sys_time - Set system time
Use seccomp to filter syscalls: # Edit /etc/pve/lxc/<CTID>.conf
lxc.seccomp.profile: /usr/share/lxc/config/common.seccomp
Configure secure mount options: # Edit /etc/pve/lxc/<CTID>.conf
lxc.mount.auto: proc:mixed sys:ro cgroup:mixed
Resource Limits
Prevent resource exhaustion:
# CPU limit (cores)
pct set < CTI D > -cores 2
# CPU units (relative weight)
pct set < CTI D > -cpuunits 1024
# Memory limit
pct set < CTI D > -memory 2048
# Swap limit
pct set < CTI D > -swap 512
# Disk I/O limit (MB/s)
pct set < CTI D > -rootfs local-lvm:8,mbps_rd=100,mbps_wr= 50
Application Security
Secure Web Applications
Use Reverse Proxy
Install Nginx Proxy Manager: bash -c "$( wget -qLO - https://github.com/community-scripts/ProxmoxVE/raw/main/ct/nginxproxymanager.sh)"
Benefits:
SSL/TLS termination
Centralized access control
Hide backend services
Enable HTTPS
Use Let’s Encrypt for free SSL certificates:
Configure Nginx Proxy Manager
Add proxy host
Request SSL certificate
Force HTTPS redirect
Security Headers
Add security headers in Nginx: add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;
Database Security
# Edit /etc/postgresql/15/main/pg_hba.conf
# Only allow local connections
local all all peer
host all all 127.0.0.1/32 scram-sha-256
host all all ::1/128 scram-sha-256
# Change password encryption
# In postgresql.conf
password_encryption = scram-sha-256
# Run security script
mysql_secure_installation
# Disable remote root login
DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ( 'localhost' , '127.0.0.1', '::1' );
# Create app-specific users
CREATE USER 'appuser'@'localhost' IDENTIFIED BY 'strong_password' ;
GRANT SELECT, INSERT, UPDATE, DELETE ON appdb. * TO 'appuser'@'localhost' ;
FLUSH PRIVILEGES ;
# Edit /etc/redis/redis.conf
bind 127.0.0.1 ::1
requirepass your_strong_password_here
maxmemory 256mb
maxmemory-policy allkeys-lru
# Disable dangerous commands
rename-command FLUSHDB ""
rename-command FLUSHALL ""
rename-command CONFIG "CONFIG_a1b2c3d4"
Docker Security
Docker Daemon
Container Limits
Docker Compose
Edit /etc/docker/daemon.json: {
"userns-remap" : "default" ,
"no-new-privileges" : true ,
"icc" : false ,
"log-driver" : "json-file" ,
"log-opts" : {
"max-size" : "10m" ,
"max-file" : "3"
}
}
docker run -d \
--memory= "512m" \
--cpus= "0.5" \
--pids-limit 200 \
--read-only \
--security-opt no-new-privileges \
--cap-drop ALL \
nginx:alpine
services :
web :
image : nginx:alpine
security_opt :
- no-new-privileges:true
cap_drop :
- ALL
read_only : true
mem_limit : 512m
cpus : 0.5
pids_limit : 200
Network Security
VLAN Segmentation
Isolate different types of services:
Trusted Network VLAN 1 (Default)
Management interfaces
Proxmox host
Trusted services
IoT/Untrusted VLAN 10
Smart home devices
Home Assistant
Limited internet access
DMZ Services VLAN 20
Public-facing services
Web servers
Reverse proxies
Guest Network VLAN 30
Guest Wi-Fi
Internet-only access
No local network access
Firewall Rules by Use Case
Web Server
Database Server
IoT Container
# Allow HTTP/HTTPS from anywhere
IN ACCEPT -p tcp -dport 80
IN ACCEPT -p tcp -dport 443
# Allow SSH from management network only
IN ACCEPT -p tcp -dport 22 -source 192.168.1.0/24
# Drop everything else
IN DROP
# Allow DB connection from app servers only
IN ACCEPT -p tcp -dport 5432 -source 192.168.1.0/24
# Allow SSH from management
IN ACCEPT -p tcp -dport 22 -source 192.168.1.10
# Drop everything else
IN DROP
# Allow access from trusted network
IN ACCEPT -p tcp -dport 8123 -source 192.168.1.0/24
# Block access from IoT VLAN
IN DROP -source 10.0.10.0/24
# Allow outbound for updates
OUT ACCEPT
Intrusion Detection
Monitor for suspicious activity:
Configure Interface
Edit /etc/suricata/suricata.yaml: af-packet :
- interface : vmbr0
cluster-id : 99
cluster-type : cluster_flow
Start Service
systemctl enable suricata
systemctl start suricata
Backup Security
Encrypted Backups
Create Encrypted Storage
# Create encrypted directory
mkdir -p /backup/encrypted
# Mount encrypted filesystem
pvesm add dir encrypted-backup --path /backup/encrypted --content backup
Backup with Encryption
# Using gpg encryption
vzdump < CTI D > --storage local --compress zstd | gpg --encrypt --recipient [email protected] > backup.vma.zst.gpg
Off-Site Backups
Rsync to Remote
Cloud Storage
# Automated rsync backup
rsync -avz --delete /var/lib/vz/dump/ user@backup-server:/backups/proxmox/
# Install rclone
curl https://rclone.org/install.sh | bash
# Configure cloud storage
rclone config
# Sync backups
rclone sync /var/lib/vz/dump/ remote:proxmox-backups/
Always test backup restoration regularly. An untested backup is not a backup.
Monitoring and Auditing
Log Monitoring
Centralized Logging
Install Loki for log aggregation: bash -c "$( wget -qLO - https://github.com/community-scripts/ProxmoxVE/raw/main/ct/alpine-loki.sh)"
Configure Log Forwarding
Forward logs from containers to Loki
Set Up Alerts
Create alerts for:
Failed login attempts
High resource usage
Service failures
Unusual network traffic
Security Auditing
# Install Lynis
apt install lynis
# Run security audit
lynis audit system
# Review results
cat /var/log/lynis.log
# Install ClamAV
apt install clamav clamav-daemon
# Update virus definitions
freshclam
# Scan system
clamscan -r /home
# Install rkhunter
apt install rkhunter
# Update database
rkhunter --update
# Run check
rkhunter --check
Security Checklist
Additional Resources
Networking Guide Advanced network configuration
Docker Security Secure Docker deployment
Popular Apps Secure application deployment
Security is an ongoing process. Regularly review and update your security measures.