Cloudflare Tunnel Setup
Expose AgentiBridge to the internet securely using Cloudflare Tunnel. No port forwarding, firewall changes, or public IP required.
┌──────────┐ ┌─────────────────┐ ┌─────────────┐ ┌────────────────┐
│ Remote │────▶│ Cloudflare │────▶│ cloudflared │────▶│ agentibridge │
│ Client │ TLS │ Edge Network │ │ (container) │ │ :8100 │
└──────────┘ └─────────────────┘ └─────────────┘ └────────────────┘
Quick Tunnel (Zero Config)
No Cloudflare account needed. Generates a temporary *.trycloudflare.com URL.
docker compose --profile tunnel up -d
Get the tunnel URL:
agentibridge tunnel
# or
docker logs agentibridge-tunnel
The URL looks like https://random-words.trycloudflare.com. It changes each time the container restarts.
Connect a remote client
{
"mcpServers": {
"agentibridge": {
"url": "https://random-words.trycloudflare.com/sse"
}
}
}
Named Tunnel (Persistent Hostname)
For a stable hostname that survives restarts. Uses the tunnel-named profile (a separate container from the quick-tunnel profile).
1. Create a Cloudflare Tunnel
- Go to Cloudflare Zero Trust Dashboard
- Navigate to Networks > Tunnels
- Click Create a tunnel > Cloudflared
- Name it (e.g.,
agentibridge) - Copy the tunnel token
2. Configure the route
In the tunnel configuration, add a Public Hostname:
| Field | Value |
|---|---|
| Subdomain | bridge (or your choice) |
| Domain | example.com (your domain) |
| Service Type | HTTP |
| URL | agentibridge:8100 |
3. Start with token
CLOUDFLARE_TUNNEL_TOKEN=eyJh... docker compose --profile tunnel-named up -d
Or add to .env:
CLOUDFLARE_TUNNEL_TOKEN=eyJh...
Then:
docker compose --profile tunnel-named up -d
Note: The named tunnel uses a different Docker profile (
tunnel-named) than the quick tunnel (tunnel). Do not mix them.
4. Verify
curl https://bridge.example.com/health
# {"status": "ok", "service": "agentibridge"}
Named Tunnel via CLI (Non-Docker)
If you run agentibridge directly on the host (not in Docker), you can create a named tunnel using the cloudflared CLI. This is ideal for exposing a stable URL to GitHub Actions CI.
1. Install cloudflared
# Debian/Ubuntu
curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 \
-o /usr/local/bin/cloudflared
chmod +x /usr/local/bin/cloudflared
# macOS
brew install cloudflared
2. Authenticate
cloudflared tunnel login
This opens your browser to authorize cloudflared with your Cloudflare account.
3. Create the tunnel
cloudflared tunnel create agentibridge
Note the tunnel UUID printed in the output. Credentials are saved to ~/.cloudflared/<tunnel-id>.json.
4. Add a DNS route
Pick a subdomain on any domain already in your Cloudflare account:
cloudflared tunnel route dns agentibridge mcp.yourdomain.com
This creates a CNAME record mcp.yourdomain.com -> <tunnel-id>.cfargotunnel.com automatically. No manual DNS editing needed.
5. Configure the tunnel
Create ~/.cloudflared/config.yml:
tunnel: <tunnel-id>
credentials-file: /home/<user>/.cloudflared/<tunnel-id>.json
ingress:
- hostname: mcp.yourdomain.com
service: http://localhost:8100
- service: http_status:404
The ingress list must end with a catch-all rule. Port 8100 is the default AGENTIBRIDGE_PORT.
6. Run the tunnel
# Foreground (for testing)
cloudflared tunnel run agentibridge
# As a systemd service (persistent across reboots)
sudo cloudflared service install
sudo systemctl enable --now cloudflared
7. Verify
curl -s https://mcp.yourdomain.com/health
# {"status": "ok", "service": "agentibridge"}
8. Use in GitHub Actions
Set these in your repo settings (Settings > Secrets and variables > Actions):
| Type | Name | Value |
|---|---|---|
| Variable | MCP_SERVER_URL | https://mcp.yourdomain.com/mcp |
| Secret | MCP_API_KEY | Your AGENTIBRIDGE_API_KEYS value |
The e2e-smoke.yml workflow uses these to generate .mcp.json and run smoke tests against your tunnel.
Allowing claude.ai and MCP Clients Through Cloudflare
When claude.ai (or any automated MCP client) connects to your AgentiBridge server through Cloudflare, Cloudflare’s bot protection features may block the connection. These features treat automated clients as bots and return HTTP 403 or 1020 errors, which appear as MCP connection failures in claude.ai.
Symptoms
- Claude.ai reports “could not connect to MCP server” even though
curlworks from your machine - HTTP 403 / Cloudflare error 1020 in tunnel logs
- SSE connection established then immediately dropped
Fix 1 — Disable Bot Fight Mode (simplest)
Bot Fight Mode is the most common culprit. Disable it for the hostname used by your MCP server:
- Go to Cloudflare Dashboard → your domain → Security > Bots
- Set Bot Fight Mode to Off (or switch to Super Bot Fight Mode with granular rules)
If you use Super Bot Fight Mode, configure it to allow “Verified bots” and add a custom rule to allow the MCP connection path:
- Go to Security > WAF > Custom Rules
- Create a rule:
- Field: URI Path — Operator: starts with — Value:
/sse(or/mcp) - Action: Skip → Skip all remaining custom rules
- Also skip: Bot Fight Mode managed rules
- Field: URI Path — Operator: starts with — Value:
Fix 2 — WAF Bypass Rule (recommended for production)
Create a WAF custom rule that skips Cloudflare’s managed ruleset for MCP traffic:
- Go to Security > WAF > Custom Rules → Create rule
- Configure:
(http.request.uri.path eq "/sse" or http.request.uri.path eq "/mcp") - Action: Skip → check Skip all remaining custom rules and Skip managed rules
This preserves protection for your other routes while letting MCP long-lived connections through.
Fix 3 — Cloudflare Access Service Token (for LLM backend behind Access)
If your LLM API (e.g., LiteLLM, OpenRouter proxy, or an internal model server) is itself protected by Cloudflare Access, AgentiBridge needs service-token credentials to make outbound requests to it.
Set these in your .env:
# Cloudflare Access service-token for the LLM backend
CF_ACCESS_CLIENT_ID=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.access
CF_ACCESS_CLIENT_SECRET=yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
Where to get these values:
- Go to Cloudflare Zero Trust Dashboard → Access > Service Auth > Service Tokens
- Click Create Service Token
- Copy the Client ID →
CF_ACCESS_CLIENT_ID - Copy the Client Secret →
CF_ACCESS_CLIENT_SECRET - In your Access policy protecting the LLM backend, add a rule: Service Token is the token you created
Note: These variables control outbound requests AgentiBridge makes to its LLM backend — not inbound connections from clients. They are unrelated to
CLOUDFLARE_TUNNEL_TOKEN.
Example .env with both tunnel and LLM Access:
# Named tunnel for inbound MCP connections
CLOUDFLARE_TUNNEL_TOKEN=eyJh...
# Service token for outbound calls to LiteLLM behind Cloudflare Access
CF_ACCESS_CLIENT_ID=abc123.access
CF_ACCESS_CLIENT_SECRET=supersecretvalue
# LLM backend URL (behind Cloudflare Access)
LLM_API_BASE=https://llm.internal.example.com/v1
LLM_API_KEY=your-litellm-api-key
Security Checklist
- Set API keys — Always set
AGENTIBRIDGE_API_KEYSwhen exposing to the internet:AGENTIBRIDGE_API_KEYS=my-secret-key-1,my-secret-key-2 -
Allow MCP bots — Disable Bot Fight Mode or add a WAF bypass for
/sseand/mcppaths (see above) so that claude.ai and other MCP clients can connect. -
Use Cloudflare Access (optional) — Add an Access policy in the Zero Trust dashboard for additional authentication (SSO, email OTP, etc.)
-
TLS is automatic — Cloudflare handles TLS termination at the edge. The connection between cloudflared and agentibridge stays internal to the Docker network.
- No ports exposed — The tunnel does not require any inbound ports. All connections are outbound from cloudflared to Cloudflare’s edge.
Troubleshooting
Tunnel URL not appearing
Quick tunnel URLs take a few seconds to register. Wait and retry:
sleep 10 && agentibridge tunnel
Or check raw logs:
docker logs agentibridge-tunnel 2>&1 | grep trycloudflare
claude.ai says “could not connect” (HTTP 403 / 1020)
Cloudflare is blocking the connection. See Allowing claude.ai and MCP Clients Through Cloudflare above.
SSE connection drops
Cloudflare Tunnel handles long-lived connections well by default. If you experience drops:
- Ensure you’re using
https://(nothttp://) for the tunnel URL - For named tunnels, set
noTLSVerify: truein the tunnel config if the origin uses self-signed certs (not needed with the default Docker setup) - Check that Bot Fight Mode is not terminating the long-lived SSE connection
Container won’t start
The cloudflared container waits for agentibridge to be healthy:
# Check agentibridge health
docker inspect -f '' agentibridge
# Check cloudflared logs
docker logs agentibridge-tunnel
LLM requests failing with 403 when LLM backend is behind Cloudflare Access
Set CF_ACCESS_CLIENT_ID and CF_ACCESS_CLIENT_SECRET in your .env. See Fix 3 above.
Stopping the tunnel
# Stop quick tunnel
docker compose --profile tunnel stop cloudflared
# Stop named tunnel
docker compose --profile tunnel-named stop cloudflared-named
# Stop everything
docker compose --profile tunnel down
docker compose --profile tunnel-named down
How It Works
The docker-compose.yml includes two cloudflared services, each behind its own profile:
| Profile | Service | When to use |
|---|---|---|
tunnel | cloudflared | Quick tunnel — temporary *.trycloudflare.com URL, no Cloudflare account |
tunnel-named | cloudflared-named | Named tunnel — persistent hostname via Cloudflare Zero Trust |
docker compose up works normally without either — you only activate a tunnel when you explicitly use --profile.
The named-tunnel container detects its token automatically:
CLOUDFLARE_TUNNEL_TOKENset: Authenticates and routes traffic to your configured hostnameCLOUDFLARE_TUNNEL_TOKENunset: Thetunnel-namedprofile will fail to start — use--profile tunnelinstead for quick tunnels