Monitor a Cloudflare Tunnel or Access-Protected App
The honest version: To monitor an app behind Cloudflare Access you have to authenticate the probe, because a naive status-only monitor pointed at the hostname goes green on the Access login page, not on your app. The monitor typically follows a 302 redirect into the login flow and lands on a login interstitial that returns 200, so your check reports up while never touching the real app behind the gate. Cloudflare Tunnel and Cloudflare Access are two different layers (Tunnel is the connector that gives a no-public-IP origin a hostname; Access is the Zero Trust auth gate in front of it), and each fails in its own way. The fix for the Access side is an external probe that authenticates with a service token: add an Access policy with action Service Auth, send the CF-Access-Client-Id and CF-Access-Client-Secret headers, and assert on post-gate content. That is exactly what Velprove does with a free API monitor that sends custom headers, plus a no-code browser login monitor for the human sign-in path. Start free, no credit card required.
1. The check is green and nobody can reach your app
A status-only monitor goes green on a Cloudflare Access app because it measures Cloudflare's login page, not your app behind the gate. Here is the failure that sends people looking for this post. You put a basic uptime check on https://app.example.com, an app sitting behind Cloudflare Access. The check goes green and stays green. Then a colleague pings you because the app has been throwing errors for an hour. The monitor never noticed, because the monitor was never looking at your app.
When an unauthenticated client hits an Access-protected hostname, Cloudflare Access does not hand it the app. It sends the client into the login flow instead. For a default self-hosted Access application, that is typically a 302 redirect toward the Access and identity provider login. A status-only monitor that follows redirects walks right into the login interstitial, which itself returns a clean HTTP 200. So an "is it 200?" check reports green. It has measured that Cloudflare's login page is up, which it almost always is, and it has learned nothing about the app behind the gate.
The other version of the same bug is a false red. A stricter check that does not follow redirects, or that demands a specific status, sees the 302 and trips. Now you get paged for an app that is perfectly healthy. In neither case is the monitor evaluating your app. It is arguing with the Access gate.
One nuance worth stating, because it changes what you assert on. Apps that use Cloudflare's newer Managed OAuth may not return a 302 at all. A non-browser client can instead receive a 401 response with a WWW-Authenticate header that points at the OAuth discovery endpoints. So you cannot hard-code "Access always returns code X." The response depends on how the application is configured. What is constant is the lesson: a status code alone, from an unauthenticated probe, tells you about the gate, not the app.
This is the same structural problem behind every silent outage. An internal signal looks green while the external reality is broken. We have written about why uptime monitors miss real outages in general. The Cloudflare Access login page is one of the cleanest specific instances of it, because the green you get is not even your server. It is Cloudflare's.
2. Tunnel down vs origin down vs gate misconfigured: three different failures
Before the fix, get the failure taxonomy straight, because an external probe has to tell these three apart, and Cloudflare serves a different page for each.
First, some context on the Tunnel itself. A Cloudflare Tunnel app has no publicly routable IP and no open inbound ports. The cloudflared daemon makes outbound-only connections to Cloudflare's network, and you can block all inbound traffic to the origin so it is reachable only through Cloudflare. A monitor cannot hit the origin directly. It must go through the Cloudflare hostname. That is the whole point of a tunnel, and it is also why "just curl the origin" is not an option here.
For giving a NAT'd box a public hostname with cloudflared in the first place, that setup is covered in the self-hosted stack guide. This post assumes the tunnel already exists and focuses on monitoring through it and through Access. With that out of the way, here are the three failures.
Error 1033, "Cloudflare Tunnel error." The tunnel itself is not connected. Cloudflare cannot find a healthy cloudflared instance to receive the traffic, usually because the connector process has stopped. Cloudflare serves its own 1033 error page from the edge. Your app is not involved at all. A monitor that only checks "did I get a response?" sees a response and may even see a 2xx-shaped error page, which is why content assertions matter.
502 Bad Gateway. This is a different failure. The tunnel IS connected to Cloudflare, but cloudflared cannot reach the origin service defined in your ingress rule. The service may be down or not responding to traffic from cloudflared. The connector is healthy. The thing behind it is not. Do not conflate this with 1033. One means the tunnel is down; the other means the origin behind a working tunnel is down.
Access gate misconfigured or service token rejected. You reach Cloudflare and you reach the gate, but you never reach the app, because the policy does not admit you. This is the failure the rest of the post is about, and the one a status-only check most often hides as a false green. It is covered in section four.
The takeaway: an external monitor that asserts on a real string from your app distinguishes all three. A 1033 page, a 502 page, and an Access login page are all Cloudflare-edge-served responses. None of them contain the post-gate content your app renders. Assert on that content and any of the three turns the check red, for the right reason.
3. Why Cloudflare's own Tunnel-health and Access dashboards are not enough
Cloudflare ships real first-party signals here, and an honest post says so. Tunnel monitoring reports connector health as Healthy, Degraded, or Down, and you can wire notifications to tunnel status. The Access dashboard confirms an application exists and that a policy is attached to it. Both are genuinely useful for what they do.
The gap is what they do not do. Connector-healthy tells you cloudflared is connected to Cloudflare. It does not tell you the origin behind the connector answers, and it cannot tell you a real user gets through the Access gate to a working app. Policy-exists tells you a rule is configured. It does not tell you the rule admits the right callers, or that the app returns content rather than a 500 once they are through. Connector up plus policy exists is not the same claim as end-to-end reachable.
There is also a vantage-point problem. Cloudflare's own signals observe from inside Cloudflare's network. An external probe runs from outside it, which is the only way to witness the full path a user actually traverses: client to Cloudflare edge, through the Access gate, through the tunnel, to your origin, and back. Use Cloudflare's observability for debugging connector state and policy configuration. Use an external monitor for the ground-truth question of whether the app responds end to end.
One disambiguation, because Cloudflare is a large surface. This post is about apps reached through Cloudflare Tunnel and Cloudflare Access, which is Cloudflare One, the Zero Trust side. If instead you run your app on Cloudflare's developer platform (Workers, Pages, KV, R2, D1, Durable Objects), the subsystem probes for that live in the Workers, Pages, KV, R2, and D1 monitoring guide. Different product family, different failure story. One link is enough; the rest of this post stays on Tunnel and Access.
4. The fix: monitor an app behind Cloudflare Access with a service token
The recommended path is a service token. A service token is a Client ID and Client Secret pair that a machine client (your monitor) presents to satisfy an Access policy without an interactive identity provider login. Critically, this does not bypass Access. It satisfies an Access policy. Access still evaluates the policy on every request. You are authenticating, not skipping the gate.
Here is the self-contained recipe for monitoring a Cloudflare Access protected app:
- Create an Access service token in Cloudflare Zero Trust. You get a Client ID and a Client Secret.
- On the Access application, add a policy with action Service Auth that admits that token. If you skip the Service Auth action, Access will prompt for an identity provider login and the token will not get you in.
- Configure your monitor to send two request headers on every request:
CF-Access-Client-IdandCF-Access-Client-Secret. If the app has only Service Auth policies, the headers must be sent on every request, which is fine for a stateless monitor that hits the URL each run. - Assert on post-gate content: a string that only your real app renders, never the login page. Now a green check means the monitor passed Access and reached the app, not that it reached a login interstitial.
A few honest caveats that come straight from how this works. The token must be permitted on that exact application. A token valid for app A does not authenticate app B; Access scopes it to the application whose policy admits it. And service tokens expire. Cloudflare's documentation uses an example lifetime of about a year, configurable, and the token needs rotation before it lapses. A forgotten rotation turns a previously-green monitor into an authentication failure. That is not the monitor breaking. That is the monitor correctly surfacing an expired credential. Cloudflare can alert you ahead of expiry; rotate the token, update the two headers in the monitor, and you are green again. Treating token expiry as a monitorable event is part of doing this properly, not a flaw in the approach.
This service-token-plus-custom-headers pattern is exactly the shape an API team needs. If you are here to monitor an API behind Cloudflare Access, the same two headers and a post-gate body assertion are the whole play.
5. The lesser option: a scoped Bypass on a health path (and why not to default to it)
If you search this problem, the most common community answer is " just expose an unauthenticated URL so the monitor can reach it." You can do a scoped version of that with an Access Bypass policy on a dedicated /healthz-style path, so an unauthenticated monitor reaches that one path. But it is the weaker answer, and here is why.
The Bypass action disables Access enforcement for the matching traffic, and those requests are not logged. That means two real losses on that path: you remove the Zero Trust protection, and you remove the audit trail. For a Zero Trust deployment, punching an unauthenticated, unlogged hole in your own gate to make monitoring easier is exactly the posture you were trying to avoid. The service token is strictly better: Access keeps enforcing the policy, the request is still evaluated and logged, and the monitor still gets through.
So lead with the service token. Use Bypass only if you have a concrete reason you cannot use a token, and if you do, scope it to a minimal, non-sensitive path that returns no protected data, never to the whole app. Do not disable Access on the application and do not remove the identity provider requirement to "simplify" monitoring. The point of this post is that you do not have to.
6. Set it up in Velprove: an API monitor with the service-token headers, and the login monitor
Here is the concrete setup against your own Access-protected app. Two monitors cover the two paths a real user takes: a machine path (service token) and a human path (interactive login).
1. API monitor on the protected URL, sending the two service-token headers. Add an API monitor on the Access-protected hostname, or on a specific post-gate path. Set two request headers, CF-Access-Client-Id and CF-Access-Client-Secret, to your service token's Client ID and Client Secret. Then add two Success Conditions on verification: a 200 status, and a Response Body Contains assertion on a post-gate string the real app always renders. The body assertion is what makes a pass mean "reached the app," not "reached the login page." Without it, you are back to the false green from section one.
2. Browser login monitor for the human path. A service token covers the machine path. It does not prove a real person can sign in. For that, the no-code browser login monitor opens a real browser, loads the Access and identity provider login, fills a dedicated low-privilege test user's credentials, and asserts on a string that only renders after a successful login. It authenticates the way a human does, not with a token. One honest scope note: a form-fill login monitor handles a standard email-and-password login well, but external identity providers vary. For the nuances of OAuth, SSO, or passkey logins, see the guide on monitoring logins that are not email and password so you do not over-rely on a form fill against an IdP that does something else.
3. Pick a region and wire alerts. Each monitor runs from one region you choose. The free plan gives you ten monitor slots to spread across regions, one no-code browser login monitor, and email alerts. A useful trick: put the API monitor on a second region from a different monitor so you can catch a regional Tunnel or edge divergence that a single vantage point would miss. If your service token then starts failing in only one region, you have isolated a regional problem rather than a global one.
Use a low-privilege, monitor-only test account for the browser login, never a real admin user, and never a broadly scoped credential. The service token should be permitted on exactly the application you are monitoring and nothing else. The whole point was to keep Zero Trust intact while still proving the app is reachable.
Frequently Asked Questions
Why does my uptime check go green when my Cloudflare Access app is actually unreachable?
Because a status-only check follows Cloudflare Access into the login flow and lands on the Access login page, which itself returns 200. Your monitor reports green having never touched the real app behind the gate. Default self-hosted Access typically returns a 302 redirect to the login, and the login page returns 200. Apps using Cloudflare's newer Managed OAuth may instead return a 401 with a WWW-Authenticate header. The fix is to authenticate the probe with an Access service token and assert on content that only renders after the gate.
How do I monitor an app behind Cloudflare Access without disabling Access?
Create an Access service token, add a policy with action Service Auth on the application, and have an API monitor send the CF-Access-Client-Id and CF-Access-Client-Secret headers on every request. The token satisfies the Service Auth policy, so the request passes the gate and reaches your app while Access still enforces the policy. You do not disable or weaken Access. The monitor simply authenticates the way a permitted machine client does. Velprove does this on a free API monitor that sends custom request headers, paired with a free no-code browser login monitor for the human sign-in path.
What is the difference between Error 1033 and a 502 on a Cloudflare Tunnel app?
Error 1033, "Cloudflare Tunnel error," means the tunnel itself is not connected because Cloudflare cannot find a healthy cloudflared instance, usually because the connector process has stopped. A 502 Bad Gateway means the tunnel is connected to Cloudflare but cloudflared cannot reach the origin service defined in your ingress rule. They are two different failures: one is the tunnel being down, the other is the origin behind a working tunnel being down. An external monitor that asserts on real app content tells them apart from the Cloudflare error pages.
Should I just expose an unauthenticated health URL so my monitor can reach the app?
You can, with a scoped Access Bypass policy on a dedicated health path, but it is the weaker option. Bypass disables Access enforcement for that traffic and those requests are not logged, so you lose protection and the audit trail on that path. A service token is the better answer: it keeps Access enforcing the policy and still lets the monitor through. If you do use Bypass, scope it to a minimal, non-sensitive path that returns no protected data.
Do Cloudflare's Tunnel-health and Access dashboards already tell me my app is up?
They tell you the connector is healthy and that an Access policy exists, which is useful, but neither confirms that a real user gets through the tunnel and past the Access gate to a working app. The connector can be up while the origin behind it is down, and the policy can exist while the app returns errors. An external probe that authenticates through Access and asserts on post-gate content is what proves the end-to-end path, from outside Cloudflare's network.
Do Cloudflare Access service tokens expire, and will that break my monitor?
Yes. Service tokens have a configurable lifetime (Cloudflare's documentation uses an example of about a year) and must be rotated. A forgotten rotation turns a previously-green monitor into an authentication failure, which is actually the monitor doing its job by surfacing the expiry. Cloudflare can alert before a token expires. Rotate the token, update the headers in your monitor, and the check returns to green.
Monitor your Cloudflare Access app free
The trap is the false green: a status-only monitor that measures Cloudflare's login page and calls your app healthy. The fix is an external probe that authenticates through the gate. Create an Access service token, add a Service Auth policy, send the CF-Access-Client-Id and CF-Access-Client-Secret headers, and assert on post-gate content, so green means reached-the-app. Add a no-code browser login monitor for the human sign-in path, and pick a region per monitor to catch regional divergence.
Velprove's free plan covers this: ten monitor slots you can spread across regions, custom headers on API monitors for the service token, one no-code browser login monitor, email alerts, and no credit card. Sign up free and point a monitor through your own Cloudflare Access gate.