When a public hostname returns 502 Bad Gateway or a Cloudflare error page (often 530, 1033, or 1016), the instinct is “the app is down.” Half the time the app is fine on the LAN — the tunnel connector simply cannot reach it, or DNS never pointed at the tunnel. If you only check the browser, you will restart the wrong process for an hour.
This guide gives you a two-probe method that separates origin problems from edge/tunnel problems, explains the remote-connector trap that causes most homelab 502s, and shows how to automate the check so you stop guessing at 2 a.m.
| Symptom | Often means | First place to look |
|---|---|---|
| Browser 502 via HTTPS | Tunnel reached Cloudflare, but origin fetch failed | Service URL in tunnel config, bind address, firewall |
| Cloudflare 530 / connector errors | Tunnel not connected or mis-mapped | cloudflared status, hostname routes |
| DNS fails / NXDOMAIN | Hostname not created or wrong zone | DNS records, proxy orange-cloud |
| LAN works, public fails | Classic tunnel path problem | This guide’s two-probe method |
| LAN fails too | App/process/bind problem | Process manager, port, app logs |
Homelabs often run cloudflared on a different machine than the app (a small always-on box
as the connector, apps on a workstation or separate VM). That architecture is fine — until the tunnel
service URL is set to:
http://127.0.0.1:8100
127.0.0.1 means “this machine.” On the connector host, that is the connector — not your app.
Cloudflare gets a connection, tries the origin, and returns 502 because nothing is listening on that port
on the connector.
0.0.0.0 (or the LAN interface) and point the
tunnel at the app’s LAN IP, e.g. http://10.2.13.159:8100, not localhost.
Also check host firewalls: the connector must be allowed to TCP-connect to that LAN IP and port. “Ping works” does not prove the app port is open.
Always run both probes from a machine that can reach the LAN (or from the app host itself for probe A).
curl -sS -o /dev/null -w "%{http_code}\n" --connect-timeout 3 \
http://10.x.x.x:PORT/
curl -sS -o /dev/null -w "%{http_code}\n" --connect-timeout 10 \
https://your.hostname.example/
| LAN | Public | Conclusion |
|---|---|---|
| Fail | Fail | Fix the origin first (process, bind, port) |
| OK | 502 | Tunnel service URL / path / connector reachability |
| OK | 530 / connector errors | Tunnel not running or hostname not routed |
| OK | DNS fail | Create/fix DNS for the hostname |
| OK | OK | You are done — look at caching/auth if content is wrong |
cloudflared is connected (logs show a registered connection).127.0.0.1 on the wrong box.curl the same LAN URL the tunnel uses./ vs app under a subpath).127.0.0.1, remote connectors can never reach it — rebind to 0.0.0.0.
Another common pattern: the app was started once under nohup or a manual shell, the host rebooted
or the process died, and the tunnel still exists. Public DNS resolves, Cloudflare is healthy, origin is empty —
classic 502. Use a process supervisor (systemd, Docker restart policy) or a healthcheck that restarts dead
listeners. A tunnel is not a process manager for your app.
If you run this dance often, automate it. Two curls work. For inventory-style checks (many
services), the free
OriginReach
CLI probes origin and public URLs and prints a verdict such as ok, origin_down,
edge_break, or dns_fail.
originreach check \ --origin http://192.168.1.50:8080/ \ --public https://app.example.com/ # Many services at once (JSON inventory): originreach check -i inventory.json --exit-code
Use --exit-code in CI or cron so a broken tunnel or dead origin fails the job instead of
waiting for a customer email.
Related: Env drift checklist · OriginReach free download · Independent notes from Tactical Data Concepts; not affiliated with Cloudflare.