Securing Proxmox VE with Cloudflare: A Complete Guide to SSL Certificates and Zero Trust Tunnels

This manual documents a real-world setup to secure a Proxmox VE hypervisor using Cloudflare’s free tools. Proxmox VE (Virtual Environment) is a powerful open-source platform for running virtual machines and containers, but its default self-signed SSL certificates trigger browser warnings and aren’t ideal for remote access. By integrating Cloudflare Origin Certificates for SSL and Cloudflare Tunnels (Zero Trust) for secure, port-free access, you can achieve end-to-end encryption without exposing ports on your firewall.

This guide covers starting with basic SSL setup, uploading custom certs, and tunneling services like the Proxmox GUI and Portainer (a Docker management UI). It’s designed for homelabs, small servers, or anyone with Proxmox on a public-facing IP. It includes prerequisites, detailed steps, screenshots (described where relevant), troubleshooting, and tips for scaling to clusters or multiple services.

Why This Setup?

  • Security: No open ports (e.g., 8006 for Proxmox); traffic tunnels outbound to Cloudflare.
  • Free: Uses Cloudflare’s free tier (Origin Certs + Tunnels).
  • Ease: No Let’s Encrypt hassles; auto-handles DNS and HTTPS.
  • Flexibility: Works with Dockerized services like Portainer.

Tested On: Proxmox VE 8.x, Cloudflare free plan, Ubuntu-based LXC for Docker (optional).


Prerequisites

Before starting:

  • Proxmox VE installed and accessible locally (e.g., https://your-ip:8006).
  • Domain managed in Cloudflare (e.g., yourdomain.com—add via dashboard if not).
  • SSH Access to your Proxmox node.
  • Docker installed if using Portainer (e.g., in an LXC container with nesting enabled: Proxmox GUI > CT Options > Features > NESTING=1).
  • Firewall: Allow outbound TCP/UDP 443 (for tunnels); no inbound ports needed.
  • Backup: Snapshot your Proxmox node/VMs before changes.

Tools Needed:

  • Browser for Cloudflare dashboard (dash.cloudflare.com).
  • Text editor (e.g., nano via SSH).
  • Optional: Portainer for managing Docker (we’ll tunnel it too).

Section 1: Setting Up SSL/TLS Certificates for Proxmox

Proxmox uses self-signed certs by default (/etc/pve/local/pveproxy-ssl.pem). We’ll replace them with a trusted Cloudflare Origin Certificate for the web GUI (pveproxy on port 8006).

1.1 Generate Cloudflare Origin Certificate

Origin Certs secure the Cloudflare-to-origin link (your server). Browsers trust them via proxying.

  1. Log in to Cloudflare Dashboard > Select your domain (e.g., yourdomain.com).
  2. Navigate to SSL/TLS > Origin Server > Create Certificate.
  3. Configure:
  • Hostnames: Include your domain and subdomains (e.g., *.yourdomain.com for wildcard).
  • Validity: 15 years (max).
  • Key Type: RSA (default) or ECC.
  1. Click Create.
  • Copy the Origin Certificate block (-----BEGIN CERTIFICATE----- to -----END CERTIFICATE-----) to pveproxy-ssl.pem.
  • Copy the Private Key block (-----BEGIN PRIVATE KEY----- to -----END PRIVATE KEY-----) to pveproxy-ssl.key.
    Note: Key is viewable only once—save securely! No passphrase needed.
  • Append any intermediates/root to .pem if provided (server cert first).

1.2 Upload Certificate to Proxmox

  1. Access Proxmox GUI: https://your-proxmox-ip:8006 (ignore self-signed warning).
  2. Select Node (left sidebar) > Certificates > Upload Custom Certificate.
  3. Upload:
  • Certificate: pveproxy-ssl.pem.
  • Key: pveproxy-ssl.key.
  1. Click Upload. Files save to /etc/pve/local/pveproxy-ssl.*.

Do NOT Delete Original Certs: The upload overwrites proxy files automatically. Originals (pve-ssl.pem, pve-root-ca.pem) are for internal cluster use—leave them. Backup first: cp /etc/pve/local/pveproxy-ssl.* /root/backup/.

  1. Restart: systemctl restart pveproxy (via SSH).
  • Verify: systemctl status pveproxy (active, no errors).

1.3 Configure Cloudflare DNS and SSL Mode

  1. In Cloudflare > DNS > Records > Add Record:
  • Type: A, Name: prox (for prox.yourdomain.com), IPv4: Your Proxmox public IP, Proxy: Proxied (orange cloud).
  1. SSL/TLS > Overview: Set Encryption Mode to Full (strict) (verifies Origin Cert).
  2. Test: https://prox.yourdomain.com:8006 (green padlock via proxy).
  • CLI: openssl s_client -connect prox.yourdomain.com:8006 -servername prox.yourdomain.com.

Screenshot Reference: In Proxmox Certificates page, you’ll see:

  • pveproxy-ssl.pem: Issuer=Cloudflare, Inc.; Valid=2025-10-02 to 2040-09-28; SANs=yourdomain.com, prox.yourdomain.com.
  • Original self-signed certs remain below.

For Clusters: Upload same certs to each node; pvecm updatecerts --force on master.

Troubleshooting SSL:

  • Mismatch: Ensure SANs match subdomain.
  • Warnings: Use proxy (orange cloud); direct IP uses self-signed.
  • Renewal: Manual (15 years); or switch to ACME/Let’s Encrypt for auto.

Section 2: Secure Remote Access with Cloudflare Tunnels (Zero Trust)

Tunnels create outbound connections—no port forwards. Ideal for Proxmox (port 8006) and services like Portainer.

2.1 Install cloudflared on Proxmox (or LXC for Isolation)

Direct on Host (simple):

  1. SSH: apt update && apt upgrade -y.
  2. Install: wget https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb && dpkg -i cloudflared-linux-amd64.deb.

In LXC Container (secure, for Docker services):

  1. Proxmox GUI: Create Ubuntu 22.04 CT (1 CPU, 512MB RAM, bridge vmbr0).
  2. Start CT, console: apt update && apt install wget.
  3. Install cloudflared as above.

2.2 Create and Configure Tunnel for Proxmox

  1. Auth: cloudflared tunnel login (browser auth, select domain).
  2. Create: cloudflared tunnel create prox-tunnel (note UUID, e.g., a1b2c3d4-e5f6-7890-abcd-ef1234567890).
  3. Config (nano /etc/cloudflared/config.yml):
   tunnel: a1b2c3d4-e5f6-7890-abcd-ef1234567890
   credentials-file: /root/.cloudflared/a1b2c3d4-e5f6-7890-abcd-ef1234567890.json

   ingress:
     - hostname: prox.yourdomain.com
       service: https://localhost:8006
       originRequest:
         noTLSVerify: false  # Uses Origin Cert
     - service: http_status:404

   warp-routing:
     enabled: true
  1. Service: cloudflared service install && systemctl start cloudflared && systemctl enable cloudflared.
  • Logs: journalctl -u cloudflared -f.
  1. Dashboard: Zero Trust > Access > Tunnels > [prox-tunnel] > Public Hostnames > Add:
  • Subdomain: prox, Service: https://localhost:8006, TLS: Full (strict).
  1. Test: https://prox.yourdomain.com (no port—proxies to 443).

For Existing Tunnel: Edit config.yml with ID, restart service, add hostname.

Troubleshooting Tunnels:

  • Not Connecting: Check outbound 443; re-auth.
  • 502: noTLSVerify: true; ensure service URL reachable.
  • Clusters: One tunnel per node or shared via LXC.

2.3 Tunneling Portainer (Docker Management UI)

Portainer runs in Docker; tunnel it similarly. Assumes Portainer accessible via http://localhost:9000.

  1. Verify Portainer: docker ps | grep portainer (running?).
  2. Create Tunnel: Dashboard > Tunnels > Create > portainer-tunnel. Copy Token.
  3. Run cloudflared Container:
   docker run -d --name cloudflared-portainer --restart unless-stopped cloudflare/cloudflared:latest tunnel --no-autoupdate run --token YOUR_TOKEN
  • Logs: docker logs -f cloudflared-portainer.
  1. Public Hostname: Subdomain: portainer, Service: http://host.docker.internal:9000, TLS: No TLS Verify.
  2. Test: https://portainer.yourdomain.com (green padlock—Cloudflare enforces HTTPS publicly).

Why Secured Despite HTTP? Cloudflare proxies inbound as HTTPS; origin is HTTP locally (Flexible mode). For full E2E: Enable Portainer HTTPS (9443), update service to https://..., TLS: Full.

Handling Multiple cloudflared Containers:

  • List: docker ps -a | grep cloudflared.
  • Inspect: docker inspect <id> | grep Tunnel.
  • Delete Unused: docker stop/rm <id>; prune: docker container prune -f.
  • Dashboard: Delete inactive tunnels (dots > Delete).
  • If Inactive: Recreate as above—old ones cause conflicts.

Docker Compose Example (for reusable setups):

version: '3.8'
services:
  cloudflared-portainer:
    image: cloudflare/cloudflared:latest
    container_name: cloudflared-portainer
    restart: unless-stopped
    command: tunnel --no-autoupdate run
    environment:
      - TUNNEL_TOKEN=your_token
    networks:
      - default  # Shares with Portainer

Run: docker-compose up -d.

Troubleshooting Portainer:

  • 502: Use container IP (docker inspect portainer | grep IPAddress); shared network.
  • Loops: Add http2: false in originRequest (config.yml).

Section 3: Advanced Tips and Security Best Practices

  • Access Policies: Zero Trust > Access > Applications > Add: Require email/2FA for prox.yourdomain.com.
  • Scaling: One tunnel per service; use wildcards for subdomains.
  • Monitoring: Dashboard tunnel logs; Proxmox: journalctl -u pveproxy.
  • Alternatives: WireGuard for VPN; Nginx reverse proxy if ports open.
  • Renewal: Origin Certs: Manual every 15y; Tunnels: Auto.
  • Costs: Free; scales to paid for advanced features.

Conclusion

This setup transforms Proxmox from a local-only tool to a secure, remote-accessible homelab. Total time: ~2 hours. Share your tweaks in comments—fork this for your blog! Questions? Refer to the resources below.

Resources:

Last Updated: October 2, 2025. Always verify with official docs.


Skip to content