# Cloudflare Tunnel from Chinese Servers — Testing Results

## Quick Tunnel (trycloudflare.com) — NOT VIABLE

Tested on Tencent Cloud Light Server (Shanghai, Ubuntu 24.04).

### Command

```bash
cloudflared tunnel --url http://localhost:5000 --no-autoupdate --no-prechecks --protocol http2
```

### Results

| Protocol | Tunnel registered? | Pre-checks w/o flag | Request routing |
|----------|-------------------|---------------------|-----------------|
| QUIC     | ✅ Yes            | Hangs after DNS     | 404 from CF edge |
| HTTP2    | ✅ Yes            | Hangs after DNS     | 404 from CF edge |

- Tunnel connection registers at Cloudflare edge (location: sjc01/sjc05/sjc06/sjc07)
- curl from same server to trycloudflare URL returns **404** with `server: cloudflare` header
- Requests do NOT appear in cloudflared logs — never forwarded to local service
- Tested with: Flask app (port 5000) and Python http.server (port 9999) — same result
- Waited 30+ seconds for propagation — no change

### `--no-prechecks` is MANDATORY

Without this flag, connectivity pre-checks output shows:
```
DNS Resolution    region1.v2.argotunnel.com  PASS
DNS Resolution    region2.v2.argotunnel.com  PASS
```
...then **hangs indefinitely**. The tunnel never reaches the "Requesting new quick Tunnel" stage. Always use `--no-autoupdate --no-prechecks`.

## Named Tunnel — VIABLE (API or dashboard)

### Option A: User creates in Zero Trust dashboard

1. User: one.dash.cloudflare.com → Zero Trust → Networks → Tunnels → Create
2. User: copy the connector token (starts with `eyJ`)
3. Agent: `cloudflared tunnel run --token <token>`

### Option B: API token

`cfut_` prefix tokens are Cloudflare **API tokens**, not tunnel tokens. `cloudflared tunnel run --token` rejects them.

API tokens can create/manage tunnels but need:
- Account-level `Cloudflare Tunnel:Edit` permission
- The domain zone must be in the same Cloudflare account

### DNS Migration Check

```bash
dig NS domain.com
```

⚠️ **PITFALL**: Chinese servers may have stale DNS cache. If `dig NS` returns DNSPod/AliDNS but the user confirms migration was done, verify with a public resolver:

```bash
dig +short NS domain.com @8.8.8.8
dig +short NS domain.com @1.1.1.1
```

If 8.8.8.8/1.1.1.1 return Cloudflare NS but the local server returns DNSPod, it's a **local DNS cache issue** — the migration IS complete. Flush: `sudo resolvectl flush-caches` or `sudo systemd-resolve --flush-caches`. Do NOT tell the user "DNS is still on DNSPod" — this triggers frustration because they already did the migration. Instead, report "DNS confirmed on Cloudflare globally, local cache stale — flushed."

### API Token Permission Requirements

When the user provides a Cloudflare API token for tunnel creation:

| Attempt | Result | Why |
|---------|--------|-----|
| Token verification | ✅ Valid | Token exists and is active |
| `GET /zones?name=icoach.chat` | Empty result | Token lacks Zone:Read |
| `GET /accounts` | Empty result | Token lacks Account:Read |
| `POST /certificates` (origin cert) | 400 "zone not in your account" | Token can't access this specific zone |
| `cloudflared tunnel run --token` | "not valid" | `cfut_` prefix = API token, not tunnel token |

**Correct token permissions to create:**
- Zone: `DNS:Edit` + `Cloudflare Tunnel:Edit`
- Zone Resources: `Include → Specific zone → icoach.chat`

The user creates this at: dash.cloudflare.com/profile/api-tokens → Create Custom Token

### Option C: Full API-based named tunnel (no browser login needed)

When `cloudflared tunnel login` browser callback is blocked by GFW, create the tunnel entirely via API. Prerequisites:
- Cloudflare API token with **Tunnel:Edit** (Account Resources) + **DNS:Edit** (Zone Resources → icoach.chat)
- Account ID and Zone ID from Cloudflare dashboard

**Step 1: Create tunnel**
```bash
curl -s -X POST -H "Authorization: Bearer $CF_TOKEN" -H "Content-Type: application/json" \
  "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/cfd_tunnel" \
  -d '{"name":"icoach-tunnel","config_src":"cloudflare"}'
# Response includes tunnel ID and credentials_file (save these)
```

**Step 2: Get tunnel token**
```bash
curl -s -H "Authorization: Bearer $CF_TOKEN" \
  "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/cfd_tunnel/$TUNNEL_ID/token"
# Save the token; use with: cloudflared tunnel run --token <token>
```

**Step 3: Configure ingress (routes traffic to local service)**
```bash
curl -s -X PUT -H "Authorization: Bearer $CF_TOKEN" -H "Content-Type: application/json" \
  "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/cfd_tunnel/$TUNNEL_ID/configurations" \
  -d '{"config":{"ingress":[{"hostname":"icoach.chat","service":"http://localhost:5000"},{"service":"http_status:404"}]}}'
```

**Step 4: Run the tunnel**
```bash
cloudflared tunnel --no-autoupdate --no-prechecks --protocol http2 run --token "$TUNNEL_TOKEN" 2>&1 | tee /tmp/cf-named.log
```

**Step 5: DNS CNAME (user must do manually if token lacks zone DNS)**
- Delete existing A record for `icoach.chat` first (conflict: CNAME can't coexist with A at apex)
- Add CNAME: `@` → `$TUNNEL_ID.cfargotunnel.com`, proxied (orange cloud)

### DNS CNAME Conflict — MUST delete A record first

When adding CNAME at zone apex (`@`), Cloudflare rejects with: "An A, AAAA, or CNAME record with that host already exists." The existing A record (pointing to server IP) must be deleted first. User must do this manually if API token lacks zone DNS permissions.

### Error 1016 "Origin DNS Error" Troubleshooting

After DNS is set up, HTTP 530 error 1016 means Cloudflare can't find the origin. Check:
1. DNS record type: Must be **CNAME**, not A — verify in Cloudflare dashboard
2. Tunnel status: `curl -H "Authorization: Bearer $CF_TOKEN" "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/cfd_tunnel/$TUNNEL_ID"` → should show `"status": "healthy"`
3. Config loaded: Log should show `"Updated to new configuration"` with correct ingress
4. Propagation: Can take 1-2 minutes after DNS change
5. If all above pass but 1016 persists: restart tunnel (`pkill -f "$TUNNEL_ID"` then re-run)

## Test Commands (for future sessions)


## ⚠️ Tunnel Token Expiration

**Tunnel tokens expire.** When a named tunnel that was working suddenly fails with:

```
Provided Tunnel token is not valid.
See 'cloudflared tunnel run --help'.
```

The token has expired or been rotated. Solution:

1. User goes to: one.dash.cloudflare.com → Zero Trust → Networks → Tunnels → [tunnel-name] → Configure
2. Copy the **Tunnel token** (starts with `eyJ`)
3. Agent saves to `/tmp/tunnel-token.b64` and restarts the tunnel

**Do NOT try to decode, repair, or use API to regenerate the token** — the dashboard is the only reliable source.


## Adding a New Subdomain

To expose another local service through the same tunnel (e.g., `rss.gdcjgk.net` → `localhost:8001`):

**Step 1: DNS CNAME** — Cloudflare dashboard → DNS → Add: `rss` CNAME → `TUNNEL_ID.cfargotunnel.com` (proxied)

**Step 2: Public Hostname** — Zero Trust → Networks → Tunnels → [tunnel-name] → Public Hostname → Add:
| Field | Value |
|-------|-------|
| Subdomain | `rss` |
| Domain | `gdcjgk.net` |
| Type | `HTTP` |
| URL | `localhost:8001` |

**Step 3: Verify** — `curl -s -o /dev/null -w "%{http_code}" https://rss.gdcjgk.net/`

Works instantly — no tunnel restart needed. DNS + Zero Trust config are pushed to the edge when you save.


## Test Commands (for future sessions)

```bash
# Quick tunnel foreground test (kill with Ctrl+C or timeout)
timeout 15 cloudflared tunnel --url http://localhost:5000 --no-autoupdate --no-prechecks 2>&1

# Background with log capture
cloudflared tunnel --url http://localhost:5000 --no-autoupdate --no-prechecks --protocol http2 2>&1 | tee /tmp/cf-test.log &

# Test routing (replace URL from log)
URL=$(grep -o 'https://[^ ]*trycloudflare\.com' /tmp/cf-test.log | head -1)
curl -s -w "\nHTTP:%{http_code}" "$URL/wx-callback?msg_signature=test&timestamp=1&nonce=1&echostr=test"

# Verify DNS is actually on Cloudflare
dig +short NS domain.com
```
