cf-proxy
Deploy a free VLESS proxy node on Cloudflare Pages + edgetunnel, bypassing GFW censorship via WebSocket over TLS through Cloudflare's CDN.
Architecture
Client (Shadowrocket / v2rayN / Clash)
↓ VLESS over WebSocket over TLS (port 443)
Custom Domain (CNAME → *.pages.dev)
↓
Cloudflare CDN (global edge network)
↓
Cloudflare Pages Function (edgetunnel _worker.js)
↓ TCP outbound
Target Website
Key design decisions:
workers.devis blocked by GFW at the TLS SNI layer — a custom domain is mandatory- Cloudflare Workers custom domains require DNS hosted on CF — unsuitable for free domains
- Cloudflare Pages supports CNAME-based custom domains — the best free-tier approach
- edgetunnel (cmliu/edgetunnel, 30k+ stars) supports VLESS/Trojan/Shadowsocks
- Never create a CNAME at the zone apex — it destroys SOA/NS records; always use a subdomain
Pre-flight Check
Before starting, verify dependencies are available:
# Check Node.js
node --version || echo "MISSING: Node.js required (install via brew install node)"
# Check wrangler
npx wrangler --version 2>/dev/null || echo "MISSING: wrangler (will install via npx)"
# Check gh CLI (for downloading edgetunnel)
gh --version || echo "MISSING: gh CLI (install via brew install gh)"
If any dependency is missing, use AskUserQuestion to confirm whether to install it or if the user prefers to handle it themselves.
Setup Workflow
Phase 1: Collect User Inputs
Use AskUserQuestion to gather required information:
- Cloudflare API Token — must have Workers/Pages permissions. If the user doesn't have one, guide them to: Cloudflare Dashboard → My Profile → API Tokens → Create Token
- Cloudflare Account ID — found on the Workers & Pages overview page
- Project name — default:
edgetunnel-pages - Custom domain — ask if the user has a domain. If not, Phase 4 will register a free one
- UUID — offer to auto-generate (recommended) or let the user specify
- Admin password — offer to auto-generate (recommended) or let the user specify
Phase 2: Download edgetunnel
mkdir -p ~/edgetunnel && cd ~/edgetunnel
# Download _worker.js via GitHub API (git clone often times out in China)
gh api repos/cmliu/edgetunnel/contents/_worker.js --jq '.content' | base64 -d > _worker.js
# Verify download
[ -s _worker.js ] && echo "OK: $(wc -c < _worker.js) bytes" || echo "FAILED: empty file"
Phase 3: Generate Config & Deploy
- Generate credentials:
# UUID for VLESS authentication
UUID=$(uuidgen | tr '[:upper:]' '[:lower:]')
echo "UUID: $UUID"
# Admin password for management panel
ADMIN=$(openssl rand -base64 12)
echo "ADMIN: $ADMIN"
- Create wrangler.toml:
name = "edgetunnel-pages"
main = "_worker.js"
compatibility_date = "2025-11-04"
keep_vars = true
[vars]
UUID = "<generated-uuid>"
ADMIN = "<generated-password>"
- Deploy to Cloudflare Pages:
npx wrangler pages deploy . --project-name edgetunnel-pages
The output will contain a *.pages.dev URL — save it for DNS configuration.
Phase 4: Obtain a Free Domain (if needed)
If the user has no domain, register a free one from DNSExit (dnsexit.com):
- Provides free second-level domains (e.g.,
*.linkpc.net), valid for 2 years - Supports custom DNS records via API
After registration, add a CNAME record via DNSExit API:
curl -s "https://api.dnsexit.com/dns/" \
-H "apikey: <DNSEXIT_API_KEY>" \
-H "Content-Type: application/json" \
-d '{
"domain": "yourname.linkpc.net",
"update": [{"type":"CNAME","name":"vless","content":"your-project.pages.dev","ttl":300}]
}'
Critical: use a subdomain (e.g., vless.yourname.linkpc.net), never the root domain. A root CNAME will destroy the zone's SOA/NS records and make the entire domain unreachable.
Phase 5: Bind Custom Domain to Pages
Via Cloudflare API:
curl -s -X POST \
"https://api.cloudflare.com/client/v4/accounts/<ACCOUNT_ID>/pages/projects/<PROJECT_NAME>/domains" \
-H "Authorization: Bearer <API_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"name":"vless.yourname.linkpc.net"}'
Or via Dashboard: Workers & Pages → select project → Custom domains → Add.
Wait for DNS propagation (typically 1-5 minutes, can take up to 30 minutes):
# Verify DNS resolution
dig vless.yourname.linkpc.net CNAME +short
Phase 6: Verify & Configure Client
- Verify the node is reachable:
curl -v --max-time 10 "https://vless.yourname.linkpc.net"
# Should return a valid HTTP response (not timeout)
- Access management panel to get connection details:
https://vless.yourname.linkpc.net/<ADMIN_PASSWORD>
- Client configuration (Shadowrocket / v2rayN / Clash):
- Protocol: VLESS
- Address:
vless.yourname.linkpc.net - Port: 443
- UUID: the generated UUID
- Transport: WebSocket
- TLS: enabled
- Host/SNI:
vless.yourname.linkpc.net - Path:
/?ed=2048
Phase 7: Optional — Bind KV Storage
KV provides persistent configuration storage:
npx wrangler kv namespace create KV
# Note the returned namespace ID, then add to wrangler.toml:
# [[kv_namespaces]]
# binding = "KV"
# id = "<namespace_id>"
Cloudflare Free Tier Limits
| Resource | Free Quota | Impact on Proxy |
|---|---|---|
| Requests | 100,000/day | WebSocket connection = 1 request; messages are free. Plenty for daily browsing |
| CPU time | 10 ms/request | Only computation, not I/O wait. Proxy is I/O-bound, typically <3ms |
| Bandwidth | Unlimited | No egress fees — the biggest advantage |
| Memory | 128 MB/isolate | Sufficient |
| Outbound connections | 6/request | Single VLESS connection needs only 1 outbound |
| KV reads | 100,000/day | Sufficient |
| KV writes | 1,000/day | Sufficient |
| Pages builds | 500/month | No impact unless redeploying frequently |
Known Limitations
- No UDP support — Workers handle HTTP/WebSocket only; cannot proxy UDP traffic (games, VoIP)
- Speed depends on CDN routing — typically 5-50 Mbps depending on client-to-edge path quality
- 100K daily request cap — heavy use may hit this limit; proxy stops with error 1027
- GFW may block custom domain SNI — domain rotation may be needed
- Cloudflare ToS risk — low-profile personal use is generally fine; large-scale/commercial use risks account termination
- Not suitable for low-latency use cases — CDN relay adds latency vs direct connections
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Shadowrocket connection timeout | workers.dev blocked by GFW | Must use custom domain |
| curl returns NXDOMAIN | DNS not propagated or zone corrupted | Check DNS records, wait for propagation |
| Error 1027 | Daily request limit exceeded | Wait for UTC midnight reset, or upgrade to paid plan |
| Error 1102 | CPU time or memory exceeded | Check worker code; rarely triggered for proxy workloads |
| WebSocket disconnects immediately | UUID mismatch | Verify client UUID matches worker config |
| Admin panel 404 | ADMIN var not set | Set ADMIN in wrangler.toml [vars] |
| Root domain NXDOMAIN after CNAME | Zone apex CNAME destroyed SOA/NS | Delete root CNAME, use subdomain instead, rebuild zone |
Upgrading to Paid Plan
Workers Paid plan ($5/month):
- Requests: 10 million/month (no daily cap)
- CPU time: 30s/request (default), up to 5 min
- All other limits significantly increased
References
- edgetunnel: https://github.com/cmliu/edgetunnel
- Cloudflare Workers limits: https://developers.cloudflare.com/workers/platform/limits/
- Cloudflare Pages limits: https://developers.cloudflare.com/pages/platform/limits/
- Cloudflare Workers pricing: https://developers.cloudflare.com/workers/platform/pricing/