Cloudflare Tunnel API
Overview
Use this skill to set up Cloudflare Tunnel quickly with API-first commands and reproducible checks.
Default objective: expose an origin service (for example http://127.0.0.1:8317) as https://${HOST}.${DOMAIN}.
This skill must stay sanitized for sharing:
- Do not hardcode real tokens, account IDs, zone IDs, domains, or tunnel names.
- Use placeholders and environment variables only.
Token Type Guard (must read first)
CF_API_TOKENis a Cloudflare API token created from dashboard (My Profile->API Tokens).TUNNEL_TOKENis returned by tunnel create API (result.token) and is only forcloudflared tunnel run --token ....- Do not use
TUNNEL_TOKENinAuthorization: Bearer ...headers. - If
/user/tokens/verifydoes not returnsuccess: true, stop and replace token before doing any API calls.
Required Inputs
CF_API_TOKEN(management API token, not tunnel run token)ACCOUNT_IDZONE_IDDOMAIN(example:example.com)HOST(example:api)ORIGIN_URL(example:http://127.0.0.1:8317)TUNNEL_NAME(example:demo-tunnel)
If IDs are missing, read references/get-cloudflare-values.md.
Default Credential Location
Unless the user explicitly says otherwise, load Cloudflare management credentials from:
~/.ssh/cloudfare/cf.env
Expected keys in that file:
CF_API_TOKENACCOUNT_IDZONE_ID
Recommended loading pattern:
set -a
[ -f "$HOME/.ssh/cloudfare/cf.env" ] && . "$HOME/.ssh/cloudfare/cf.env"
set +a
Behavior rules:
- Prefer already-exported environment variables when present.
- If any of
CF_API_TOKEN,ACCOUNT_ID, orZONE_IDis still missing after loading~/.ssh/cloudfare/cf.env, stop and tell the user which key is absent. - Do not print token values back to the user.
Quick Verify
Verify token validity first:
set -a
[ -f "$HOME/.ssh/cloudfare/cf.env" ] && . "$HOME/.ssh/cloudfare/cf.env"
set +a
curl -sS "https://api.cloudflare.com/client/v4/user/tokens/verify" \
-H "Authorization: Bearer ${CF_API_TOKEN}" | jq
Expected: success: true.
Optional check if you suspect you copied a tunnel run token by mistake:
python3 - <<'PY'
import base64, json, os
t = os.environ.get("CF_API_TOKEN", "")
try:
j = json.loads(base64.b64decode(t + "==").decode())
print("Looks like TUNNEL_TOKEN payload:", j.keys())
except Exception:
print("Not tunnel-token payload format (this is normal for CF_API_TOKEN)")
PY
API Workflow (Remote-managed Tunnel)
1) Create tunnel
curl -sS "https://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/cfd_tunnel" \
-X POST \
-H "Authorization: Bearer ${CF_API_TOKEN}" \
-H "Content-Type: application/json" \
--data "{\"name\":\"${TUNNEL_NAME}\",\"config_src\":\"cloudflare\"}" | jq
Capture:
TUNNEL_IDfromresult.idTUNNEL_TOKENfromresult.token
2) Configure ingress (hostname -> origin)
curl -sS "https://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/cfd_tunnel/${TUNNEL_ID}/configurations" \
-X PUT \
-H "Authorization: Bearer ${CF_API_TOKEN}" \
-H "Content-Type: application/json" \
--data "{
\"config\": {
\"ingress\": [
{\"hostname\": \"${HOST}.${DOMAIN}\", \"service\": \"${ORIGIN_URL}\", \"originRequest\": {}},
{\"service\": \"http_status:404\"}
]
}
}" | jq
3) Create DNS record to tunnel
curl -sS "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records" \
-X POST \
-H "Authorization: Bearer ${CF_API_TOKEN}" \
-H "Content-Type: application/json" \
--data "{
\"type\": \"CNAME\",
\"name\": \"${HOST}.${DOMAIN}\",
\"content\": \"${TUNNEL_ID}.cfargotunnel.com\",
\"proxied\": true
}" | jq
4) Run connector on server (Docker)
docker rm -f cloudflared-${HOST} 2>/dev/null || true
docker run -d \
--name cloudflared-${HOST} \
--restart unless-stopped \
cloudflare/cloudflared:latest \
tunnel --no-autoupdate run --token "${TUNNEL_TOKEN}"
Validation Checklist
- DNS check:
curl -s "https://dns.google/resolve?name=${HOST}.${DOMAIN}&type=CNAME"
- Tunnel logs:
docker logs --tail 100 cloudflared-${HOST}
- Application check:
curl -I "https://${HOST}.${DOMAIN}"
Troubleshooting
403/blocked pageon custom domain:- Confirm hostname resolves to
<TUNNEL_ID>.cfargotunnel.com(CNAME), not direct origin IP. - Confirm CNAME is proxied in Cloudflare (
proxied: true).
- Confirm hostname resolves to
token invalid:- Ensure token is
CF_API_TOKEN(management token), notTUNNEL_TOKEN. - Recreate token and verify with
/user/tokens/verify.
- Ensure token is
connection refused to origin:- Check
ORIGIN_URLlocally on server. - Check firewall/security group and local bind address.
- Check
DNS record already exists:- Use DNS record list endpoint, then PATCH/PUT record instead of POST create.
Minimum Token Permissions
For this workflow, token should include:
- Account:
Cloudflare Tunnel(Edit) or equivalent tunnel write permission - Zone:
DNS(Edit)
References
- Setup guide: https://developers.cloudflare.com/tunnel/setup/
- Tunnel tokens: https://developers.cloudflare.com/tunnel/advanced/tunnel-tokens/
- Find IDs: https://developers.cloudflare.com/fundamentals/account/find-account-and-zone-ids/
- Create API token: https://developers.cloudflare.com/fundamentals/api/get-started/create-token/