I have Proxmox Virtual Environment exposed externally via a Cloudflare tunnel. Access is locked down with Cloudflare Zero Trust — a wildcard application on *.mattsouthgate.co.uk that requires either a WARP-enrolled device or email authentication before you get anywhere near the server.
That works perfectly in a browser. It does not work in the Proxmox VE Android app.
This is the story of figuring out why, and the fix that keeps the servers protected while letting the app through.
The Problem
The Proxmox VE app on Android connects directly to the server's HTTPS API at port 8006. Through a Cloudflare tunnel, that becomes https://pve8.mattsouthgate.co.uk on port 443 — Cloudflare handles the external TLS and forwards traffic to the server internally.
With Cloudflare Access in front, the first thing any unauthenticated connection receives is a challenge page. In a browser, you click through, authenticate, get a session cookie, and proceed. The Proxmox app isn't a browser. It expects a JSON API response. What it gets instead is:
Connection error. Could not establish connection.
Format exception, unexpected character (at character 1) <!DOCTYPE html>
^
The <!DOCTYPE html> at position 1 is Cloudflare's Access login page. The app has no way to handle it — it's looking for { not <.
What Doesn't Work
WARP / Cloudflare One on the phone. The instinct here is correct — enroll the device in Zero Trust, and Cloudflare should recognise it as trusted without a browser challenge. The Cloudflare One app (the replacement for 1.1.1.1 after May 2026) handles this enrollment.
The problem is in how Access policies evaluate WARP. An Allow policy with a WARP posture check doesn't skip authentication — it requires the device to be WARP-connected and complete an Access login session. The login session is the browser step. The app still can't do that.
Service tokens. Cloudflare Access supports service tokens — a client ID and secret sent as HTTP headers (CF-Access-Client-Id, CF-Access-Client-Secret) — designed exactly for non-browser clients. The Proxmox Android app has no mechanism to set custom HTTP headers on its connections. That option is closed off.
WARP posture check in a Bypass policy. A Bypass policy skips the Access login entirely for traffic matching its rules. Combining Bypass with a WARP Include rule sounds like it would work — bypass only for enrolled devices, everyone else still hits the email flow. In practice, the Cloudflare dashboard warns you when you select Bypass that it only reliably supports IP-based and group-based rules for its conditions. Device posture checks, including WARP, don't function reliably in Bypass policies.
What Does Work
The solution is a second, more specific Access application scoped only to pve8.mattsouthgate.co.uk with a Bypass + Everyone policy.
In Access controls → Applications, create a new Self-hosted application:
- Subdomain:
pve8 - Domain:
mattsouthgate.co.uk - Policy action: Bypass
- Include: Everyone
Cloudflare evaluates Access applications by specificity — the most specific hostname match wins. pve8.mattsouthgate.co.uk is more specific than *.mattsouthgate.co.uk, so traffic to pve8 hits the Bypass policy first and passes straight through to the Proxmox login page. Every other subdomain continues to be handled by the wildcard application with its WARP and email policies intact.
The Proxmox app connects immediately. No HTML. No challenge. Just the API.
What About Security?
Bypassing Cloudflare Access on pve8 means anyone who knows the URL can reach the Proxmox login page. That's worth being clear-eyed about.
What they reach is Proxmox's own authentication — username, password, and optionally two-factor. Proxmox's login is not bypassed, only Cloudflare's pre-authentication layer. The server is not open; it's just relying on its own credentials rather than having an additional Cloudflare gate in front.
For the other subdomains — services that may not have strong authentication of their own — the wildcard application continues to enforce Zero Trust access. Nothing about that changes.
This is a reasonable split: Proxmox has solid built-in auth and the app has no way to satisfy Cloudflare's browser flow, so Cloudflare steps aside and lets Proxmox handle it. Services that are less hardened stay behind the full Zero Trust wall.
Tunnel Configuration
One separate thing worth checking if the app connects but behaves oddly: the tunnel's origin settings for pve8 should have both noTLSVerify and disableChunkedEncoding enabled.
Proxmox uses a self-signed certificate internally, so noTLSVerify tells Cloudflare not to reject it on the internal leg. disableChunkedEncoding matters because Cloudflare tunnels don't support HTTP chunked transfer encoding, which Proxmox uses — without disabling it you can get HTTP 501 errors on certain operations.
Both are set under Networks → Tunnels → [tunnel] → Public Hostnames → [hostname] → Additional application settings → Origin.
App Settings
With the above in place, the Proxmox VE Android app connects with:
- Host:
pve8.mattsouthgate.co.uk - Port:
443
Port 443, not 8006 — Cloudflare terminates on 443 externally. The :8006 only appears on the internal leg between Cloudflare and the server, which the tunnel configuration already handles.
No comments:
Post a Comment