Cloudflare Tunnel on Proxmox for Zero-Trust Remote Access
Expose Proxmox and self-hosted services remotely with Cloudflare Tunnel — no open ports, no dynamic DNS, no VPN clients needed. Step-by-step LXC setup guide.
On this page
If you want remote access to Proxmox and your self-hosted services without opening a single port on your router, Cloudflare Tunnel is the cleanest option I have run in a homelab. You deploy cloudflared in a dedicated LXC container, authenticate with your Cloudflare account, and within 30 minutes you are hitting the Proxmox web UI over HTTPS from any browser — no dynamic DNS updates, no NAT rules, no VPN client to install on every device you travel with.
Key Takeaways
- No open ports: Cloudflare Tunnel uses an outbound-only connection; your router firewall stays completely unchanged.
- Free tier covers homelabs: Zero Trust tunnels are free for personal use with no bandwidth cap on admin traffic.
- 512 MB LXC is enough: A single unprivileged Debian 12 container runs
cloudflaredfor an entire homelab stack. - Access policies add a second gate: Cloudflare Access sits in front of Proxmox's login, requiring email or SSO auth before the browser even gets there.
- Not a full VPN replacement: Tunnel exposes services over HTTPS; for raw TCP or SSH without a browser, WireGuard or Tailscale is still the right tool.
How Cloudflare Tunnel Compares to WireGuard and Tailscale
WireGuard and Tailscale are both solid choices I use elsewhere. But both require a client installed on every machine you connect from. Cloudflare Tunnel exposes services over HTTPS through Cloudflare's edge, so any browser works without installing anything on the client side.
| Feature | Cloudflare Tunnel | WireGuard | Tailscale |
|---|---|---|---|
| Client install required | No (browser only) | Yes | Yes |
| Inbound port on router | None | UDP 51820 | None |
| Free tier | Yes (unlimited tunnels) | Self-hosted, free | Yes (up to 100 devices) |
| Access policies / SSO | Yes (Cloudflare Access) | No | Yes (ACLs) |
| Traffic routing | Through Cloudflare edge | Direct P2P | Direct or relayed P2P |
| Full network / raw TCP | No (HTTP/S natively) | Yes | Yes |
| Best for | Web UIs, APIs, dashboards | Full network, SSH | SSH, inter-device mesh |
The honest tradeoff: your traffic passes through Cloudflare's infrastructure. For admin interfaces this is fine, but I would not route database replication or bulk file transfers through a public tunnel. Keep a WireGuard VPN alongside for those workloads.
Prerequisites
Before starting, you need:
- A domain with DNS managed by Cloudflare (free account works; the domain just needs to be on their nameservers)
- Proxmox VE 8.x or 9.x with outbound internet from the node
- Shell access to a Proxmox node
No static IP required. Cloudflare Tunnel is specifically designed to work from behind CGNAT, dynamic IPs, and hotel WiFi. The connection is always outbound from your homelab to Cloudflare's edge.
How to Create a Dedicated cloudflared LXC Container
Running cloudflared in its own LXC keeps it isolated — you can restart it without touching other workloads, snapshot before updates, and cap its resources. A Debian 12 unprivileged container with 512 MB RAM and 4 GB disk is more than adequate.
Create and Start the Container
First confirm which templates are available:
pveam list local
Download Debian 12 if it is not already present:
pveam download local debian-12-standard_12.7-1_amd64.tar.zst
Create the container — adjust the template filename and storage names to match your setup:
pct create 200 local:vztmpl/debian-12-standard_12.7-1_amd64.tar.zst \
--hostname cloudflared \
--memory 512 \
--swap 256 \
--cores 1 \
--rootfs local-lvm:4 \
--net0 name=eth0,bridge=vmbr0,ip=dhcp \
--unprivileged 1 \
--onboot 1 \
--start 1
The --onboot 1 flag is critical. Skip it and your tunnel will not come back after a host reboot — you will find yourself locked out with no remote path in.
Install cloudflared from the Official Repository
Enter the container:
pct enter 200
Add Cloudflare's APT repository and install:
apt update && apt install -y curl gnupg lsb-release
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg \
| gpg --dearmor -o /usr/share/keyrings/cloudflare-main.gpg
echo "deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] \
https://pkg.cloudflare.com/cloudflared $(lsb_release -cs) main" \
> /etc/apt/sources.list.d/cloudflared.list
apt update && apt install -y cloudflared
cloudflared --version
The version output should show cloudflared 2025.x.x or later. If you see an older version cached from a previous install, run apt upgrade cloudflared to pull the latest.
Authenticate and Create the Tunnel
cloudflared tunnel login
This prints a URL. Open it in any browser, log in to Cloudflare, and select the domain you want to use. A certificate saves to ~/.cloudflared/cert.pem.
Create a named tunnel:
cloudflared tunnel create homelab
Cloudflare generates a UUID and writes credentials to ~/.cloudflared/<UUID>.json. The output shows the UUID — copy it for the config file.
Configuring Ingress Rules for Proxmox and Other Services
Create /etc/cloudflared/config.yml:
tunnel: <your-tunnel-UUID>
credentials-file: /root/.cloudflared/<your-tunnel-UUID>.json
ingress:
- hostname: proxmox.yourdomain.com
service: https://192.168.1.100:8006
originRequest:
noTLSVerify: true # Required: Proxmox uses a self-signed cert
- hostname: portainer.yourdomain.com
service: http://192.168.1.110:9000
- hostname: homeassistant.yourdomain.com
service: http://192.168.1.120:8123
- service: http_status:404 # Catch-all — required, must be the last entry
Replace 192.168.1.100 with your Proxmox node's actual IP. The noTLSVerify: true flag is mandatory for Proxmox: it tells cloudflared to skip certificate validation when connecting to the backend, because Proxmox ships with a self-signed cert. Without it, the Proxmox console shows a blank screen with no error — one of those failures that wastes an hour if you do not know what to look for.
Gotcha: omitting the catch-all http_status:404 rule causes cloudflared to refuse to start. It must be the final entry in every ingress block.
Route DNS Subdomains to the Tunnel
cloudflared tunnel route dns homelab proxmox.yourdomain.com
cloudflared tunnel route dns homelab portainer.yourdomain.com
cloudflared tunnel route dns homelab homeassistant.yourdomain.com
Each command creates a CNAME pointing the subdomain at <UUID>.cfargotunnel.com. Cloudflare DNS propagates in under 60 seconds. Verify:
dig proxmox.yourdomain.com
Starting the Tunnel as a systemd Service
Do not leave cloudflared running in a terminal session. Install the systemd unit:
cloudflared service install
systemctl enable cloudflared
systemctl start cloudflared
Tail the logs to confirm the tunnel connected:
journalctl -u cloudflared -f
A healthy tunnel establishes four connections:
INF Connection established connIndex=0 ip=198.41.200.13
INF Connection established connIndex=1 ip=198.41.200.33
INF Connection established connIndex=2 ip=198.41.200.23
INF Connection established connIndex=3 ip=198.41.200.43
Four connections to separate Cloudflare edge PoPs is the expected healthy state — your tunnel stays up even if one PoP has a problem. A single connection still works but is not HA.
Adding Cloudflare Access Policies to Gate the Proxmox UI
Exposing the Proxmox login page to the internet — even behind a tunnel — is a risk I would not take without an extra authentication layer. Cloudflare Access puts an SSO gate in front so that no browser reaches Proxmox without first passing identity verification.
In the Cloudflare dashboard:
- Go to Zero Trust → Access → Applications → Add an application
- Select Self-hosted
- Set the Application domain to
proxmox.yourdomain.com - Under Policies, create an Allow rule where Emails is in
[your@email.com] - Select an identity provider — Google OAuth is the fastest to configure for personal use
After saving, every request to proxmox.yourdomain.com hits a Cloudflare-hosted login page first. Only after passing that does the browser reach the Proxmox UI. Two authentication barriers with zero changes to your Proxmox configuration.
For broader cluster-level protection, combining this with CrowdSec on Proxmox for cluster-wide brute-force defense gives you reputation-based IP blocking at the Cloudflare edge before attackers ever reach the tunnel connection itself.
Troubleshooting Common Problems
Backend returns 502: cloudflared cannot reach the service. Test from inside the container:
curl -k https://192.168.1.100:8006
If that fails, check your Proxmox datacenter firewall and node firewall rules. If you have VLANs configured on Proxmox with Linux bridges, confirm the cloudflared LXC and the Proxmox management IP are on the same VLAN — or that routing exists between them — otherwise the curl will time out silently.
Proxmox console blank or WebSocket errors: Almost always noTLSVerify: true is missing or incorrectly indented in config.yml. YAML indentation matters — noTLSVerify must be nested under originRequest, not at the ingress block level.
DNS not resolving after route dns: Wait two minutes and retry. If it still fails, check the Cloudflare dashboard DNS tab to confirm the CNAME was written. Duplicate route dns calls are safe to rerun.
Only one tunnel connection instead of four: A network policy upstream is blocking connections to multiple Cloudflare PoPs. Common in corporate or ISP-restricted networks. The tunnel is still functional; it just lacks redundancy. Proxmox behind a home router with a standard firewall should always get four.
Tunnel does not start after host reboot: Either the LXC --onboot 1 flag was not set, or the systemd unit is not enabled inside the container. Fix both:
# On the Proxmox host
pct set 200 --onboot 1
# Inside the container
systemctl enable cloudflared
What You Can and Cannot Expose Through the Tunnel
Cloudflare Tunnel natively proxies HTTP and HTTPS. A few additional capabilities are worth knowing:
- SSH via browser:
cloudflared access sshrenders an SSH session in the browser without a local client — useful for a jump-host VM - RDP via browser:
cloudflared access rdpworks the same way for Windows VMs - Arbitrary TCP: supported, but requires
cloudflaredinstalled on the client machine, which eliminates the client-install-free advantage
For running Docker inside LXC containers on Proxmox, Cloudflare Tunnel integrates cleanly — add each Docker service port as an ingress entry in config.yml. The one common failure: Docker services that bind to 127.0.0.1 inside the container are unreachable from cloudflared. The service must bind on 0.0.0.0 for the tunnel to proxy it.
For services where you need full network access — SSH from a native client, database connections, or inter-VM traffic — a WireGuard or Tailscale tunnel is still the right tool. Running both side-by-side is reasonable; they do not conflict.
Conclusion
After this setup, a 512 MB LXC maintains four outbound connections to Cloudflare's edge and serves your Proxmox UI and the rest of your homelab stack over HTTPS with no open ports on your router. Cloudflare Access policies mean no browser reaches the Proxmox login without passing identity verification first. The natural next step is extending the same tunnel to expose your Proxmox Backup Server's web interface for remote monitoring — or pairing this zero-trust access layer with the automated backup workflows already running on PBS so you can verify backup status remotely without exposing any additional ports.