Proxmox VE TLS Certificate Replacement: Installing a Trusted Certificate for the Web UI

Proxmox VE ships with a self-signed certificate. The browser warning it produces is not just an annoyance — users clicking through “Your connection is not private” warnings train themselves to ignore certificate errors, which is dangerous. More practically: in environments with multiple admins, the self-signed certificate means you cannot distinguish a legitimate Proxmox node from a rogue one without manually verifying the fingerprint every time. This article covers replacing the default self-signed certificate with a Let’s Encrypt or internal CA certificate, and automating renewal.

How Proxmox VE uses TLS

Proxmox VE uses TLS in several places:

  • Web UI and API (port 8006) — served by pveproxy
  • VNC/SPICE console access — proxied through pveproxy
  • Cluster communication — uses its own certificates managed by pvecm
  • Corosync — uses its own authentication

This article focuses on the web UI and API certificate (port 8006). Cluster certificates are managed separately and should not be replaced manually.

Where Proxmox stores the web UI certificate

The certificate and key used by pveproxy live at:

  • /etc/pve/local/pve-ssl.pem — the certificate (PEM format)
  • /etc/pve/local/pve-ssl.key — the private key (PEM format)

/etc/pve/ is a cluster filesystem (pmxcfs). Writes to it are replicated across all cluster nodes. However, each node has a local/ directory that is node-specific — so you must replace the certificate on each node separately.

Backing up the current certificate before replacing it:

cp /etc/pve/local/pve-ssl.pem /root/pve-ssl.pem.bak
cp /etc/pve/local/pve-ssl.key /root/pve-ssl.key.bak

Option A: Let’s Encrypt using the built-in ACME client

Proxmox VE 6.1+ has a built-in ACME/Let’s Encrypt client accessible from the web UI and command line. This is the easiest approach when the Proxmox node has a public DNS record.

Via the web UI

  1. Log in to the Proxmox web UI at https://your-node:8006.
  2. Select your node in the left panel.
  3. Go to Certificates.
  4. Under ACME, click Add ACME Account and register with Let’s Encrypt.
  5. Add a domain — enter the node’s FQDN (e.g., pve1.example.com).
  6. Choose a validation method:
  • Standalone — opens port 80 temporarily (the node must be publicly reachable)
  • DNS plugin — uses your DNS provider’s API to create TXT records (works behind firewalls)
  1. Click Order Certificates Now.

The certificate is automatically renewed 30 days before expiry by the pveproxy renewal service.

Via the command line

Register an ACME account:

pvenode acme account register default your@email.com --directory https://acme-v02.api.letsencrypt.org/directory

Add a domain and request the certificate:

# Using HTTP-01 challenge (requires port 80 to be reachable)
pvenode config set --acmedomain0 pve1.example.com

# Request the certificate
pvenode acme cert order

Check the result:

pvenode acme cert info

Using DNS challenge (for nodes behind a firewall)

Add the DNS plugin for your provider first. Proxmox supports many DNS providers (Cloudflare, Route53, etc.):

# Cloudflare example
pvenode acme plugin add dns cloudflare --api cf \
  --data "CF_Token=your-cloudflare-api-token"

pvenode config set --acmedomain0 pve1.example.com,plugin=cloudflare

pvenode acme cert order

Option B: Installing a certificate from an internal CA or external provider

If you cannot use Let’s Encrypt (air-gapped environment, internal domain), replace the certificate manually.

Step 1: Generate a certificate

If using an internal CA:

# On the CA host — not on the Proxmox node
openssl genrsa -out pve1.key 4096
openssl req -new -key pve1.key -out pve1.csr \
  -subj "/CN=pve1.example.com/O=example/C=US" \

openssl x509 -req -in pve1.csr \
  -CA /etc/pki/ca/ca.crt -CAkey /etc/pki/ca/ca.key \
  -CAcreateserial -out pve1.crt -days 365 \
  -extfile <(echo "subjectAltName=DNS:pve1.example.com,IP:192.168.1.10")

Step 2: Copy the certificate and key to the Proxmox node

scp pve1.crt root@pve1:/tmp/
scp pve1.key root@pve1:/tmp/

On the Proxmox node:

# Copy to pveproxy certificate locations
cp /tmp/pve1.crt /etc/pve/local/pve-ssl.pem
cp /tmp/pve1.key /etc/pve/local/pve-ssl.key

# Set permissions
chown root:www-data /etc/pve/local/pve-ssl.key
chmod 640 /etc/pve/local/pve-ssl.key
chmod 644 /etc/pve/local/pve-ssl.pem

Step 3: Restart pveproxy

systemctl restart pveproxy

Verify:

openssl s_client -connect pve1.example.com:8006 -servername pve1.example.com </dev/null 2>&1 \
  | openssl x509 -noout -subject -dates

Distributing the CA certificate to browsers and clients

After installing a certificate signed by an internal CA, browsers will still show a warning unless the CA is trusted. Deploy the CA certificate:

Windows (Group Policy):

Create a GPO that deploys the CA to Trusted Root Certification Authorities on all machines.

macOS:

sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain ca.crt

Ubuntu/Debian:

sudo cp ca.crt /usr/local/share/ca-certificates/internal-ca.crt
sudo update-ca-certificates

Firefox (which uses its own cert store):

Firefox does not use the OS certificate store by default. Go to Settings → Privacy & Security → Certificates → View Certificates → Authorities → Import and import the CA certificate.


Automating renewal for manually installed certificates

For manually installed certificates from an internal CA, create a renewal script run by cron or systemd timer:

nano /usr/local/bin/renew-proxmox-cert.sh
#!/bin/bash
# Renew Proxmox VE TLS certificate from internal CA ACME endpoint or CFSSL
# Adapt to your CA's renewal mechanism

CA_API="https://ca.internal/api/v1/issue"
DOMAIN="pve1.example.com"
CERT_DIR="/etc/pve/local"

# Example: use cfssl or step-ca to issue a new cert
# Substitute with your CA's CLI or API call
# cfssl gencert -profile=server -ca=... -ca-key=... server.json | cfssljson -bare new-cert

# After issuing:
# cp new-cert.pem ${CERT_DIR}/pve-ssl.pem
# cp new-cert-key.pem ${CERT_DIR}/pve-ssl.key
# chown root:www-data ${CERT_DIR}/pve-ssl.key
# chmod 640 ${CERT_DIR}/pve-ssl.key
# systemctl restart pveproxy

echo "Update this script with your CA's renewal command"

Verify the certificate is being served correctly

# Check certificate details
curl -k https://pve1.example.com:8006 -v 2>&1 | grep -A5 "Server certificate"

# Check expiry
echo | openssl s_client -connect pve1.example.com:8006 2>/dev/null | \
  openssl x509 -noout -enddate

Multi-node clusters: replace on each node

In a Proxmox cluster, each node serves its own certificate. /etc/pve/local/ is node-specific, so you must repeat the certificate installation on each node separately. The Let’s Encrypt built-in client runs per-node.

If you manage certificates centrally, create a script that deploys to all nodes:

for node in pve1 pve2 pve3; do
  echo "Deploying certificate to ${node}..."
  scp /tmp/${node}.crt root@${node}:/etc/pve/local/pve-ssl.pem
  scp /tmp/${node}.key root@${node}:/etc/pve/local/pve-ssl.key
  ssh root@${node} "chmod 640 /etc/pve/local/pve-ssl.key && systemctl restart pveproxy"
done

Troubleshooting

ProblemCauseFix
Browser still shows old self-signed cert after restartBrowser cached the old certClear browser TLS cache or test with curl
pveproxy: error loading certificateWrong file format or permissionsCheck PEM format with openssl x509 -in pve-ssl.pem -text -noout
pveproxy fails to startInvalid private keyVerify key matches cert: openssl x509 -noout -modulus -in pve-ssl.pem | md5sum vs openssl rsa -noout -modulus -in pve-ssl.key | md5sum
ACME order fails with port 80 blockedFirewall blocks HTTP challengeUse DNS challenge plugin instead
Certificate renewed but old cert still servedpveproxy not restartedRun systemctl restart pveproxy

Summary

Proxmox VE’s web UI certificate is stored at /etc/pve/local/pve-ssl.pem and /etc/pve/local/pve-ssl.key. The easiest replacement method is the built-in ACME client (pvenode acme cert order) with either HTTP or DNS challenge. For internal CAs, copy the certificate and key manually and restart pveproxy. In a cluster, each node’s certificate is independent — deploy and renew separately on each node. Distribute the CA certificate to all administrator browsers to eliminate the security warning completely.

Scroll to Top