If you run a home lab on Proxmox and want to reach your services from anywhere without exposing ports on your router, Cloudflare Tunnel is one of the cleanest options available. The tunnel daemon, cloudflared, makes an outbound-only connection to Cloudflare’s edge, and traffic reaches your services through that connection. No port forwarding, no dynamic DNS scripts, no publicly reachable inbound ports.
This guide walks through the design choices and the concrete steps to get cloudflared running on Proxmox. It stays evergreen by focusing on the mechanics that rarely change, and it points out where you should check current Cloudflare documentation rather than trust a snapshot in time.
Why cloudflared on Proxmox
Proxmox VE is a hypervisor, so the first decision is where the tunnel should live. You generally do not want to install cloudflared on the Proxmox host itself. The host should stay lean and focused on virtualization. Instead, run the tunnel inside a lightweight container or VM that you can snapshot, back up, and rebuild independently.
The benefits of this approach are practical:
- No inbound ports. Your router firewall stays closed. The tunnel dials out to Cloudflare.
- Centralized access control. You can layer Cloudflare Access policies in front of internal apps.
- Clean separation. The tunnel is isolated from your other workloads and easy to redeploy.
Choosing a container or VM
For most home labs, an unprivileged LXC container running a minimal Debian or Ubuntu template is the sweet spot. It uses very little memory and starts quickly. If you prefer stronger isolation or plan to run additional tooling alongside the tunnel, a small VM works equally well.
Create the container in the Proxmox web UI or with pct. A reasonable starting allocation is 1 vCPU, 512 MB of RAM, and a few gigabytes of disk. The tunnel is not resource hungry.
One gotcha worth knowing: some networking features behave differently inside unprivileged LXC containers. If you hit permission issues with TUN devices later, you have two options. Enable the required device access in the container config, or switch to a VM where kernel networking is fully available. For a plain HTTP tunnel, the standard LXC setup is usually enough.
Installing cloudflared
Inside your container or VM, update the system and install cloudflared from Cloudflare’s official package repository. Cloudflare publishes both a Debian/Ubuntu repository and standalone binaries. Prefer the repository so you get updates through your normal package manager.
# Update and install prerequisites
apt update
apt install -y curl gnupg lsb-release
# Add Cloudflare's package signing key and repo
# (Follow the current commands in Cloudflare's docs, as key
# and repo URLs can change over time.)
apt update
apt install -y cloudflared
# Verify
cloudflared --versionConfirming the version after install is a good habit. It tells you the binary is on your PATH and the package installed cleanly.
Authenticating and creating the tunnel
There are two common ways to configure a tunnel: the locally managed method using config files, and the remotely managed method using the Cloudflare Zero Trust dashboard. The dashboard method is easier to maintain because your routing lives in Cloudflare, and you can move the tunnel token between hosts.
Option A: dashboard-managed tunnel
In the Cloudflare Zero Trust dashboard, create a new tunnel and choose the connector installation for your platform. Cloudflare gives you a token. You then run the connector with that token:
cloudflared service install <YOUR_TUNNEL_TOKEN>This installs and starts a systemd service that keeps the tunnel connected and restarts it on reboot. You then define public hostnames and route them to internal services entirely from the dashboard.
Option B: locally managed tunnel
If you prefer keeping configuration in files under version control, authenticate and create the tunnel from the command line:
# Authenticate (opens a browser flow, produces a cert.pem)
cloudflared tunnel login
# Create a named tunnel
cloudflared tunnel create homelab
# List tunnels to confirm the UUID
cloudflared tunnel listThe create command generates a credentials JSON file tied to the tunnel UUID. Keep that file secure and back it up.
Writing the config file
For a locally managed tunnel, create /etc/cloudflared/config.yml. This file maps public hostnames to the internal addresses of your Proxmox services. A typical config that exposes two internal web apps looks like this:
tunnel: <TUNNEL_UUID>
credentials-file: /etc/cloudflared/<TUNNEL_UUID>.json
ingress:
- hostname: proxmox.example.com
service: https://192.168.1.10:8006
originRequest:
noTLSVerify: true
- hostname: app.example.com
service: http://192.168.1.20:8080
# Catch-all rule is required and must be last
- service: http_status:404A few notes on this config:
- The Proxmox web UI uses HTTPS on port 8006 with a self-signed certificate by default, so
noTLSVerify: trueis common for that origin. If you install a proper certificate on the origin, you can drop it. - The final
http_status:404catch-all rule is mandatory. Without it, cloudflared will reject the config. - Ingress rules are evaluated top to bottom, so put specific hostnames before broad ones.
Routing DNS
Each public hostname needs a DNS record pointing at the tunnel. For a locally managed tunnel, create the routes from the CLI:
cloudflared tunnel route dns homelab proxmox.example.com
cloudflared tunnel route dns homelab app.example.comThis creates the proxied CNAME records in your Cloudflare zone. For a dashboard-managed tunnel, you add these public hostnames directly in the tunnel’s configuration screen and Cloudflare handles the DNS.
Running as a service
You want the tunnel to survive reboots. Install it as a systemd service so Proxmox can restart the container and the tunnel comes back automatically:
cloudflared service install
systemctl enable cloudflared
systemctl start cloudflared
systemctl status cloudflaredCheck the logs if it fails to connect:
journalctl -u cloudflared -fCommon issues are a bad credentials path, a missing catch-all ingress rule, or DNS records that were not created. The logs are specific and usually point straight at the problem.
Locking down access
Exposing a service through a tunnel makes it reachable, but reachable is not the same as protected. For sensitive dashboards like the Proxmox UI, put a Cloudflare Access policy in front of the hostname. Access can require email-based one-time passcodes, an identity provider login, or specific email domains before any request reaches your origin. This is the single most valuable thing you can add, because it authenticates users at Cloudflare’s edge instead of relying only on the app’s own login page.
Also keep the container itself minimal. Fewer packages means fewer things to patch. Snapshot the container after you have it working so you can roll back cleanly.
Maintenance and updates
Because you installed from the package repository, updating is part of your normal patch cycle:
apt update && apt upgrade -y
systemctl restart cloudflaredKeep an eye on Cloudflare’s release notes for connector changes, and re-check their documentation when you set up a new tunnel, since the exact repository URLs and dashboard flow can evolve. The core model, an outbound connector that maps hostnames to internal services, has stayed stable and is safe to build on.
Takeaway
Running cloudflared in a dedicated LXC container or VM on Proxmox gives you secure, portless access to your home lab. Keep the tunnel off the hypervisor host, use a minimal container, choose dashboard-managed or file-based config based on how you like to work, always include the catch-all ingress rule, and put Cloudflare Access in front of anything sensitive. Snapshot it once it works, patch it on your normal schedule, and you have a durable setup that rarely needs attention.
