Expose a service running on your local machine to a remote server without opening any ports. For instance, let your Codex Cloud agent (the remote server) access qBittorrent Web UI on your Mac (the local machine), to download a movie for you.
The local machine makes an outbound-only connection to Cloudflare. The remote server hits your subdomain on Cloudflare's edge. Traffic flows:
Your remote server -> https://your-tunnel-name.example.com -> Cloudflare edge servers -> Cloudflare Tunnel (cloudflared) -> qBittorrent Web UI on your local machine
You can probably do the same thing with Tailscale, but unfortunately, the Tailscale app doesn't work well with Mullvad VPN on macOS (and I don't want to use Tailscale's Mullvad VPN add-on).
ref:
https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/
https://tailscale.com/docs/features/exit-nodes/mullvad-exit-nodes
Setup
1. Create Cloudflare Tunnel
Do this from any device where you're logged into Cloudflare. No login needed on the local machine or the remote server.
- Go to Cloudflare dashboard -> Networking -> Tunnels
- Create Tunnel
- Tunnel name:
your-tunnel-name - Click
Create Tunnel
- Tunnel name:
- Open the tunnel -> Overview -> Add a replica -> macOS
- Copy the tunnel token from the installation command (the value after
service install)
- Copy the tunnel token from the installation command (the value after
- Open the tunnel -> Routes -> Add route -> Published application
- Subdomain:
your-tunnel-name - Domain: select your domain from the dropdown (e.g.,
example.com) - Path:
[leave empty] - Service URL:
http://localhost:8080
- Subdomain:
- Click
Add route- After you create the published application route, Cloudflare will automatically create the DNS record for your subdomain
ref:
https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/get-started/tunnel-useful-terms/
https://developers.cloudflare.com/cloudflare-one/networks/routes/add-routes/
2. Access Controls for Cloudflare Tunnel
Protect the local service with an access policy and let your cloud agent authenticate with a service token instead of a browser login.
- Go to Cloudflare dashboard -> Zero Trust -> Access controls -> Service credentials
- Service Tokens -> Create Service Token
- Service token name:
your-token-name - Service token duration:
Non-expiring - Click
Generate token - Save the
CF-Access-Client-IdandCF-Access-Client-Secret(shown only once)
- Service token name:
- Policies -> Add a policy
- Policy details:
- Policy Name:
your-policy-name - Action:
Service Auth - Policy session duration:
24 hours
- Policy Name:
- Policy rules -> Include:
- Selector:
Service Token - Value: select the service token you just created (e.g.,
your-token-name)
- Selector:
- Click
Save policy
- Policy details:
- Applications -> Create new application -> Self-hosted and private -> Continue with Self-hosted and private
- Destinations -> Public hostnames:
- Subdomain:
your-tunnel-name(must match the subdomain in step 1.4) - Domain: select your domain from the dropdown (e.g.,
example.com) - Path:
[leave empty]
- Subdomain:
- Access policies -> Add current policies:
- Select the policy you created in step 2.3
- Details:
- Name:
your-tunnel-name - Session Duration:
24 hours
- Name:
- Click
Create
- Destinations -> Public hostnames:
ref:
https://developers.cloudflare.com/cloudflare-one/access-controls/service-credentials/service-tokens/
https://developers.cloudflare.com/cloudflare-one/access-controls/policies/
https://developers.cloudflare.com/cloudflare-one/access-controls/applications/http-apps/
3. Run cloudflared on Local Machine (macOS)
Make cloudflared run on boot, connecting outbound to Cloudflare. No browser auth needed.
brew install cloudflared
# install as a LaunchAgent using the tunnel token from step 1
sudo cloudflared service install YOUR_TUNNEL_TOKEN
ref:
https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/downloads/
To verify it's running:
sudo launchctl list | grep cloudflared
4. Access the Local Service on Remote Server
Test that the tunnel and access policy work. We're accessing qBittorrent Web UI here:
curl \
-H "CF-Access-Client-Id: $YOUR_CF_ACCESS_CLIENT_ID" \
-H "CF-Access-Client-Secret: $YOUR_CF_ACCESS_CLIENT_SECRET" \
-d "username=YOUR_USERNAME&password=YOUR_PASSWORD" \
https://your-tunnel-name.example.com/api/v2/auth/login
The CF-Access-XXX headers must be included on every request. Without them, Cloudflare returns a 302 redirect to a login page.
ref:
https://github.com/qbittorrent/qBittorrent/wiki/#webui
5. Disable Browser Integrity Check on Cloudflare (Optional)
If Cloudflare blocks requests from your remote server and returns HTTP 403 with Cloudflare error 1010, you need to disable Browser Integrity Check:
- Go to Cloudflare dashboard ->
example.com-> Rules -> Overview - Configuration Rules -> Create rule
- Rule name:
Disable Browser Integrity Check for your-tunnel-name - If incoming requests match:
Custom filter expression - When incoming requests match:
- Field:
Hostname - Operator:
equals - Value:
your-tunnel-name.example.com(must match the hostname in step 1.4)
- Field:
- Then the settings are -> Browser Integrity Check:
- Click
+ Add - Leave the toggle off
- Click
- Rule name:
- Click
Deploy, then retry the request from your remote server
This disables Browser Integrity Check only for that hostname instead of the entire domain.
ref:
https://developers.cloudflare.com/waf/tools/browser-integrity-check/
https://developers.cloudflare.com/rules/configuration-rules/
Why Cloudflare Tunnel Over Tailscale?
- A good fit if your infrastructure is already on Cloudflare
- Works with Mullvad VPN if you use split tunneling to exclude
cloudflared - Covered by Cloudflare Zero Trust's free tier