Testing Cloudflare Access locally with a cloudflared tunnel
Cloudflare Access is a great way to restrict a site deployed to Cloudflare Workers to specific users without building your own login flow.
How it works: in Cloudflare Access, you create an application for the hostname you want to protect and attach a policy that defines who’s allowed in. Cloudflare checks every request against this policy before it reaches your worker. Requests without a valid session are sent to a login flow, and once that’s completed the request is routed to your worker with a signed JWT attached. Your worker then verifies the JWT to confirm that the request actually came through Access.
But when integrating Cloudflare Access into your app, it’s not obvious how to test this during development. When your dev server runs on localhost, Cloudflare Access has no way to intercept the requests, so a JWT is never passed to your worker.
A good way to solve this is to use cloudflared to set up a tunnel that exposes your local dev server at a real public hostname. Cloudflare Access can then protect it exactly like the production deployment.
Setting up the tunnel
First, authenticate cloudflared and create a tunnel:
cloudflared tunnel login
cloudflared tunnel create devserver
Then create the cloudflared configuration file at ~/.cloudflared/config.yml. Replace <Tunnel-UUID> with the UUID printed by tunnel create (you can also find it with cloudflared tunnel list), and tunnel.example.com with the hostname you want to expose the dev server on:
tunnel: <Tunnel-UUID>
credentials-file: ~/.cloudflared/<Tunnel-UUID>.json
ingress:
- hostname: tunnel.example.com
service: http://localhost:8787
- service: http_status:404
The service port must match the port your dev server runs on. If you’re using Wrangler, you can pin it with a --port flag in your dev script in your package.json:
{
"scripts": {
"dev": "wrangler dev --port 8787"
}
}
Point the hostname at the tunnel by creating a CNAME record in the Cloudflare dashboard with the value <Tunnel-UUID>.cfargotunnel.com. Then add the tunnel hostname to the same Access application that protects your production site, so it’s covered by the same policy.
If you’re running the Cloudflare One Client (formerly WARP), cloudflared’s traffic needs to bypass it—otherwise the tunnel won’t connect. Follow the instructions in the Cloudflare docs to add two host entries to the split tunnels for your device’s profile:
region1.v2.argotunnel.com
region2.v2.argotunnel.com
You will also need to create a cache rule for the tunnel hostname and set it to bypass cache, so the resulting expression should look like:
(http.host eq "tunnel.example.com")
Finally, start the tunnel alongside your dev server:
cloudflared tunnel run devserver
With the tunnel running, visiting your tunnel hostname will trigger the Cloudflare Access login flow, and the authenticated request (with its JWT) will be forwarded to your local worker.