Install updates
See here.
-> “Updates” -> “Repositories“
Disable these repositories:
"PVE"(“pve-enterprise")- “
Ceph"(“enterprise“)
Instead add the PVE “No-Subscription” version, if needed also add “Ceph Squid No-Subscription“.
Go back to “Updates” and press “Refresh“, then “>_ Upgrade“. Terminal will show – confirm updates when requested.
When finished you may need to reboot the machine (press “Reboot” button in top right corner to do so).
Adding SSL certificate using ACME
Settings can be found in the PVE web interface under “Datacenter” (not “Node“!) -> “ACME“
linux # vi /tmp/data
NSUPDATE_KEY=/etc/certbot-update.key
NSUPDATE_SERVER=192.168.1.1
NSUPDATE_ZONE=mydomain.de
linux # pvenode acme plugin add dns BIND-RFC2136 --api nsupdate --data /tmp/data
linux # pvenode acme plugin config BIND-RFC2136
┌────────┬──────────────────────────────────────────┐
│ key │ value │
╞════════╪══════════════════════════════════════════╡
│ api │ nsupdate │
├────────┼──────────────────────────────────────────┤
│ data │ NSUPDATE_KEY=/etc/pve/certbot-update.key │
│ │ NSUPDATE_SERVER=192.168.1.1 │
│ │ NSUPDATE_ZONE=mydomain.de │
├────────┼──────────────────────────────────────────┤
│ digest │ 5d58be827e04fe8cfe86e92d28d04484a1da4943 │
├────────┼──────────────────────────────────────────┤
│ plugin │ BIND-RFC2136 │
├────────┼──────────────────────────────────────────┤
│ type │ dns │
└────────┴──────────────────────────────────────────┘
Now let’s specify a new cert:
linux # pvenode config set -acmedomain0 pve.mydomain.de,plugin=BIND-RFC2136
linux # pvenode acme cert order
ACME account config file 'default' does not exist.
So looks like we really need to define an letsencrypt account first:
linux # pvenode acme account register default root+letsencrypt@mydomain.de
Directory endpoints:
0) Let's Encrypt V2 (https://acme-v02.api.letsencrypt.org/directory)
1) Let's Encrypt V2 Staging (https://acme-staging-v02.api.letsencrypt.org/directory)
2) Custom
Enter selection: 1
Attempting to fetch Terms of Service from 'https://acme-staging-v02.api.letsencrypt.org/directory'..
Terms of Service: https://letsencrypt.org/documents/LE-SA-v1.6-August-18-2025.pdf
Do you agree to the above terms? [y|N]: y
Attempting to register account with 'https://acme-staging-v02.api.letsencrypt.org/directory'..
Generating ACME account key..
Registering ACME account..
Registration successful, account URL: 'https://acme-staging-v02.api.letsencrypt.org/acme/acct/255578983'
Task OK
As you can see for now we’ll use the staging tree for testing.
Acme will run as nobody:nogroup, /etc/pve however is fuse-mounted and all files are owned by www-data:root. So I ended up storing the certbot-update.key file in /etc (ownership nobody:nogroup).
Test:
linux # pvenode acme cert order
Loading ACME account details
Placing ACME order
Order URL: https://acme-staging-v02.api.letsencrypt.org/acme/order/255578983/30243848483
Getting authorization details from 'https://acme-staging-v02.api.letsencrypt.org/acme/authz/255578983/21039761103'
The validation for pve.mydomain.de is pending!
[Mon Jan 5 17:52:22 CET 2026] key /etc/pve/certbot-update.key is unreadable
[Mon Jan 5 17:52:22 CET 2026] Error add txt for domain:_acme-challenge.pve.mydomain.de
command 'setpriv --reuid nobody --regid nogroup --clear-groups --reset-env -- /bin/bash /usr/share/proxmox-acme/proxmox-acme setup nsupdate pve.mydomain.de' failed: exit code 1
Task command 'setpriv --reuid nobody --regid nogroup --clear-groups --reset-env -- /bin/bash /usr/share/proxmox-acme/proxmox-acme setup nsupdate pve.mydomain.de' failed: exit code 1
linux # pvenode acme cert order
Loading ACME account details
Placing ACME order
Order URL: https://acme-staging-v02.api.letsencrypt.org/acme/order/255578983/30243848483
Getting authorization details from 'https://acme-staging-v02.api.letsencrypt.org/acme/authz/255578983/21039761103'
The validation for pve.mydomain.de is pending!
[Mon Jan 5 18:10:45 CET 2026] adding _acme-challenge.pve.mydomain.de. 60 in txt "JLZk_gB_WiLdRandOmNess1"
Add TXT record: _acme-challenge.pve.mydomain.de
Sleeping 30 seconds to wait for TXT record propagation
Triggering validation
Sleeping for 5 seconds
Status is 'valid', domain 'pve.mydomain.de' OK!
[Mon Jan 5 18:11:21 CET 2026] removing _acme-challenge.pve.mydomain.de. txt
Remove TXT record: _acme-challenge.pve.mydomain.de
All domains validated!
Creating CSR
Checking order status
Order is ready, finalizing order
valid!
Downloading certificate
Setting pveproxy certificate and key
Restarting pveproxy
Task OK
Now let’s switch to production ACME servers:
linux # pvenode acme account deactivate default
Renaming account file from '/etc/pve/priv/acme/default' to '/etc/pve/priv/acme/_deactivated_default_0'
Task OK
linux # pvenode acme account register default root+letsencrypt@mydomain.de
Directory endpoints:
0) Let's Encrypt V2 (https://acme-v02.api.letsencrypt.org/directory)
1) Let's Encrypt V2 Staging (https://acme-staging-v02.api.letsencrypt.org/directory)
2) Custom
Enter selection: 0
Attempting to fetch Terms of Service from 'https://acme-v02.api.letsencrypt.org/directory'..
Terms of Service: https://letsencrypt.org/documents/LE-SA-v1.6-August-18-2025.pdf
Do you agree to the above terms? [y|N]: y
Attempting to register account with 'https://acme-v02.api.letsencrypt.org/directory'..
Generating ACME account key..
Registering ACME account..
Registration successful, account URL: 'https://acme-v02.api.letsencrypt.org/acme/acct/2937414866'
Task OK
linux # pvenode acme cert order --force
Loading ACME account details
Placing ACME order
Order URL: https://acme-v02.api.letsencrypt.org/acme/order/2937414866/466339128806
Getting authorization details from 'https://acme-v02.api.letsencrypt.org/acme/authz/2937414866/638328122866'
The validation for pve.mydomain.de is pending!
[Mon Jan 5 18:16:53 CET 2026] adding _acme-challenge.pve.mydomain.de. 60 in txt "1SJJHh-_WiLdRandOmNess2"
Add TXT record: _acme-challenge.pve.mydomain.de
Sleeping 30 seconds to wait for TXT record propagation
Triggering validation
Sleeping for 5 seconds
Status is 'valid', domain 'pve.mydomain.de' OK!
[Mon Jan 5 18:17:29 CET 2026] removing _acme-challenge.pve.mydomain.de. txt
Remove TXT record: _acme-challenge.pve.mydomain.de
All domains validated!
Creating CSR
Checking order status
Order is ready, finalizing order
valid!
Downloading certificate
Setting pveproxy certificate and key
Restarting pveproxy
Task OK
Looks like we’re done here for now 🙂
Adding OpenID Connect Auth
Ok, to be honest I don’t know if this part is related to “first steps” but for now I’ll just add it here.
In order to add OpenID Connect auth, go to “Datacenter” -> “Permissions” -> “Realms” and add a new realm of type “OpenID Connect Server“.
Make sure to select the correct settings concerning users and groups because you can’t change them later on. Selecting the “Username Claim“: “Default” will use a cryptic internal keycloak id, “email” will result in something like “email@mydomain.de@MYREALM” as username. I went for “username” to get something like “username@REALM“.
I guess you’d also want to activate “Autocreate Users” and “Autocreate Groups“.
The created groups will have an added “-<REALM>” at the end to make them unique.
The groups themselves do not come with any special meaning. They need to be given permission to have any real effect. You can do so using “Datacenter” -> “Permission” -> “Add” -> “Group Permissions“. Select “/” as “Path“, your desired group and maybe “PVEAdmin” as “Role” to get a basic setup.
Lessons learned
Also make sure that the provided group names do not contain “special” characters (like spaces!), otherwise they’ll be ignored by proxmox (as mentioned in the wiki):
In some cases, OpenID servers may send groups claims which include invalid characters for Proxmox VE group IDs. Any groups that contain characters not allowed in a Proxmox VE group name are not included and a warning will be sent to the logs.
Checking the logs resulted in something like:
<...>
openid group 'Proxmox Admins' contains invalid characters
<...>
Unfortunately I couldn’t find a clean definition of “invalid characters” in the documentation, so I had to go for the next best thing: The source code.
The code producing the above log entry seems to be part of pve-access-control. Digging through this code revealed some regular expressions in Plugin.pm:
our $realm_regex = qr/[A-Za-z][A-Za-z0-9.-_]+/;
our $user_regex = qr![^\s:/]+!;
our $groupname_regex = qr/[A-Za-z0-9.-_]+/;
Looks like user names can contain all but space, ‘:‘ and ‘/‘, but group names are further restricted to alphanumeric characters and ‘.-_‘.
