Expose a service running on your local machine to a remote server without opening any ports. For instance, let your OpenClaw agent (the remote server) access qBittorrent Web UI on your Mac (the local machine).
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://qbittorrent-web-ui.example.com -> Cloudflare edge server -> Cloudflare Tunnel -> qBittorrent Web UI on your local machine
You can probably do the same thing with Tailscale, but unfortunately, 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 Zero Trust dashboard
- Networks -> Connectors -> Create a tunnel -> Cloudflared
- Name your tunnel:
qbittorrent-web-ui
- Name your tunnel:
- Copy the tunnel token
- Configure the tunnel you just created -> Published application routes -> Add a published application route
- Subdomain:
qbittorrent-web-ui - Domain: select your domain from the dropdown (e.g.,
example.com) - Path:
[leave empty] - Service:
- Type:
HTTP - URL:
localhost:8080
- Type:
- Subdomain:
- 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
Still in the Cloudflare Zero Trust dashboard.
- Access controls -> Service credentials -> Service Tokens -> Create Service Token
- Token name:
your-token-name - Service Token Duration:
Non-expiring - Save the
CF-Access-Client-IdandCF-Access-Client-Secret(shown only once)
- Token name:
- Access controls -> Policies -> Add a policy
- Policy name:
your-policy-name - Action:
Service Auth - Session duration:
24 hours - Configure rules -> Include:
- Selector:
Service Token - Value: select the service token you just created (e.g.,
your-token-name)
- Selector:
- Policy name:
- Access controls -> Applications -> Add an application -> Self-hosted
- Application name:
qbittorrent-web-ui - Session Duration:
24 hours - Add public hostname:
- Input method:
Default - Subdomain:
qbittorrent-web-ui(must match the subdomain in step 1.4) - Domain: select your domain from the dropdown (e.g.,
example.com) - Path:
[leave empty]
- Input method:
- Select existing policies (this text is a clickable button, not a label!)
- Check the policy you created in step 2.2
- Application name:
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, connects outbound to Cloudflare. No browser auth ever 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://qbittorrent-web-ui.example.com/api/v2/auth/login
Note: 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
Why Cloudflare Tunnel Over Tailscale
- No VPN conflicts —
cloudflaredis just outbound HTTPS, Mullvad VPN doesn't care - Zero attack surface — Both local machine and remote server have no open ports, service invisible to scanners
- No login on endpoints — The tunnel token is scoped to one tunnel, can't access your Cloudflare account
- Free — Cloudflare Zero Trust free tier covers this