A single admin with root access works for a homelab. It doesn’t work when multiple people or teams share the same Proxmox cluster. Who can see what? Who can modify what? What happens when someone leaves?
Access control isn’t a feature you enable. It’s a product you design. Every permission is a decision: who needs this access, why, and what’s the blast radius if it’s misused?
Proxmox has robust RBAC (Role-Based Access Control). The question is whether you use it intentionally or let it grow organically into chaos.
Access Control Model
Proxmox permissions combine:
Permission = User/Group + Role + Path
Example:- User: developer@pve- Role: PVEVMUser- Path: /pool/dev-team
Result: developer can use VMs in dev-team poolUsers and Groups
Users: Individual accounts. Can be in multiple groups.
# Create user in Proxmox realmpveum user add developer@pve --password <password>
# Create user in PAM realm (Linux user)pveum user add admin@pam
# List userspveum user listGroups: Collections of users. Simplify permission management.
# Create grouppveum group add developers --comment "Development team"
# Add user to grouppveum user modify developer@pve --groups developers
# List groupspveum group listAuthentication Realms
| Realm | Use Case | Notes |
|---|---|---|
| pam | Linux admins who need SSH | System users |
| pve | Web UI only users | Proxmox internal |
| ldap | Enterprise integration | External directory |
| ad | Active Directory | Windows integration |
For multi-tenancy, usually:
- Admins: PAM (SSH + Web UI)
- Regular users: PVE realm (Web UI only)
Built-in Roles
Proxmox includes these roles:
| Role | Permissions |
|---|---|
| Administrator | Everything (dangerous) |
| PVEAdmin | Almost everything (no system access) |
| PVEAuditor | Read-only access |
| PVEDatastoreAdmin | Manage datastores |
| PVEDatastoreUser | Use datastores |
| PVEPoolAdmin | Manage pools |
| PVEPoolUser | Use pools |
| PVEVMAdmin | Full VM control |
| PVEVMUser | Use VMs (console, start/stop) |
| PVETemplateUser | Clone templates |
| PVEUserAdmin | Manage users |
| NoAccess | Explicit deny |
Custom Roles
Create roles for specific needs:
# Create role with specific privilegespveum role add VMOperator --privs "VM.Console VM.PowerMgmt VM.Monitor"
# List available privilegespveum privilege list
# View rolepveum role listCommon custom roles:
# Developer: Can create/manage own VMspveum role add Developer --privs "VM.Allocate VM.Clone VM.Config.CDROM VM.Config.CPU VM.Config.Cloudinit VM.Config.Disk VM.Config.Memory VM.Config.Network VM.Console VM.Migrate VM.Monitor VM.PowerMgmt VM.Snapshot VM.Snapshot.Rollback Datastore.AllocateSpace"
# Observer: Can view, nothing elsepveum role add Observer --privs "VM.Audit Datastore.Audit"
# Backup Operator: Can backup/restorepveum role add BackupOperator --privs "VM.Backup VM.Snapshot Datastore.AllocateSpace"Resource Pools
Pools group resources (VMs, storage, nodes) for delegation:
# Create poolpveum pool add dev-team --comment "Development team resources"
# Add VM to poolqm set 100 --pool dev-team
# Add storage to poolpveum pool modify dev-team --storage local-lvm
# List poolspveum pool listPool-Based Permissions
Grant access to pool, not individual VMs:
# Developers can manage VMs in their poolpveum acl modify /pool/dev-team --users developer@pve --roles Developer
# Or by grouppveum acl modify /pool/dev-team --groups developers --roles DeveloperNew VMs in the pool automatically inherit permissions.
Pool Strategy
Organize by:
Option 1: By team/pool/dev-team/pool/qa-team/pool/production
Option 2: By environment/pool/development/pool/staging/pool/production
Option 3: By project/pool/project-alpha/pool/project-betaMatch your organization structure.
API Tokens
API tokens are better than passwords for automation:
- Separate from user password
- Can have different permissions
- Easily revoked without changing user password
- Audit trail shows token ID
Creating Tokens
# Create token for userpveum user token add developer@pve automation --privsep 0
# Output shows token secret (save it!)# Token: developer@pve!automation# Secret: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
# With privilege separation (token can have fewer privs than user)pveum user token add admin@pam ci-cd --privsep 1pveum acl modify /pool/production --tokens admin@pam!ci-cd --roles BackupOperatorToken Best Practices
# Good: Specific tokens for specific purposesadmin@pam!terraform # Infrastructure automationadmin@pam!ansible # Configuration managementadmin@pam!monitoring # Read-only metricsdeveloper@pve!ci-build # CI pipeline builds
# Bad: Generic tokens with admin accessadmin@pam!api # Too broad, no purpose documentedUsing Tokens in Automation
# API call with tokencurl -k -H "Authorization: PVEAPIToken=developer@pve!automation=xxxx-xxxx-xxxx" \ https://proxmox:8006/api2/json/version
# Terraform providerprovider "proxmox" { pm_api_url = "https://proxmox:8006/api2/json" pm_api_token_id = "terraform@pve!automation" pm_api_token_secret = var.proxmox_token}
# Ansibleproxmox_kvm: api_host: proxmox api_user: ansible@pve api_token_id: automation api_token_secret: "{{ vault_proxmox_token }}"Permission Paths
Permissions apply to paths in the resource tree:
/ # Root (everything)├── /access # User/group management├── /nodes # All nodes│ ├── /nodes/pve1 # Specific node├── /pool # All pools│ └── /pool/dev-team # Specific pool├── /storage # All storage│ └── /storage/local # Specific storage└── /vms # All VMs (by ID) └── /vms/100 # Specific VMPermission Inheritance
Permissions cascade down:
# Grant access to all VMs in a poolpveum acl modify /pool/dev-team --users developer@pve --roles PVEVMUser
# Developer can now access:# - /pool/dev-team (pool itself)# - All VMs in that pool# - Storage assigned to that poolExplicit Deny
NoAccess role blocks inheritance:
# User has pool accesspveum acl modify /pool/dev-team --users developer@pve --roles Developer
# But NOT this specific VMpveum acl modify /vms/105 --users developer@pve --roles NoAccessMulti-Tenant Architecture
Example: Web Hosting Provider
Tenants: customer-a, customer-b, customer-c
Structure:├── /pool/customer-a│ ├── VMs 100-199│ └── Storage quota├── /pool/customer-b│ ├── VMs 200-299│ └── Storage quota└── /pool/customer-c ├── VMs 300-399 └── Storage quota
Users:- customer-a-admin@pve → /pool/customer-a (PVEVMAdmin)- customer-a-user@pve → /pool/customer-a (PVEVMUser)- customer-b-admin@pve → /pool/customer-b (PVEVMAdmin)...
Isolation:- Network: Separate VLANs per customer- Storage: Pool quotas, separate datastores- Compute: Resource limits on poolsExample: Corporate IT
Departments: dev, qa, production, infrastructure
Structure:├── /pool/development│ └── All non-prod VMs├── /pool/qa│ └── Test environments├── /pool/production│ └── Production workloads (restricted)└── /pool/infrastructure └── DNS, monitoring, etc.
Groups and roles:- developers → /pool/development (Developer)- qa-engineers → /pool/qa (Developer)- sre-team → /pool/production (PVEVMUser)- sre-leads → /pool/production (PVEVMAdmin)- infra-admins → / (PVEAdmin)Audit Logging
Track who did what:
Task History
# Recent tasks (via API)pvesh get /cluster/tasks
# Node-specific taskspvesh get /nodes/pve1/tasksWeb UI: Datacenter → Tasks → Filter by user
System Logs
# Auth logsjournalctl -u pveproxy | grep auth
# API access logstail -f /var/log/pveproxy/access.logExternal Audit
For compliance, forward logs:
# Syslog forwardingecho "*.* @syslog-server:514" >> /etc/rsyslog.d/remote.confsystemctl restart rsyslogQuotas and Limits
Prevent resource exhaustion:
Pool Quotas
Not built-in, but enforceable via custom roles and monitoring:
# Create role without VM.Allocatepveum role add PoolUser --privs "VM.Console VM.PowerMgmt VM.Monitor"
# Users can use VMs but not create new ones# Admins create VMs, assign to poolVM Resource Limits
# Limit CPUqm set 100 --cpulimit 2 # Max 2 cores worth
# Limit memoryqm set 100 --memory 4096 --balloon 2048
# Limit disk I/Oqm set 100 --bwlimit "backup=10240,restore=10240"Storage Quotas
Ceph/ZFS can enforce quotas:
# ZFS quotazfs set quota=100G rpool/data/customer-a
# Ceph quotaceph osd pool set-quota customer-pool max_bytes 107374182400Security Checklist
User Management:[ ] No shared accounts[ ] Each person has individual user[ ] Users in appropriate groups[ ] Unused users disabled/deleted
Roles:[ ] Custom roles for common use cases[ ] No one uses Administrator role directly[ ] Principle of least privilege applied
Pools:[ ] Resources organized into pools[ ] Permissions at pool level (not individual VMs)[ ] Clear ownership per pool
API Tokens:[ ] Automation uses tokens, not passwords[ ] Tokens have specific purposes[ ] Tokens documented[ ] Unused tokens revoked
Audit:[ ] Logs retained appropriately[ ] Regular review of access[ ] Alerts on sensitive operationsToken Rotation
Regular token rotation:
# Create new tokenpveum user token add admin@pam ansible-v2 --privsep 0
# Update automation to use new token
# Verify new token works
# Remove old tokenpveum user token remove admin@pam ansible-v1Schedule this quarterly or when personnel changes.
The Lesson
Access control is a product. It needs to be designed.
The lazy approach:
- Everyone is admin
- One shared account
- Permissions “we’ll figure out later”
The result:
- No audit trail
- Blast radius is entire cluster
- Personnel change = security nightmare
The designed approach:
- Users in groups
- Groups have roles
- Roles are minimal
- Resources in pools
- Permissions at pool level
- Automation uses tokens
- Regular access reviews
Access control isn’t overhead — it’s what makes multi-tenancy possible. Design it upfront, enforce it consistently, and review it regularly.