A VLESS proxy that runs on Cloudflare Workers / Pages.
📦 Just want to deploy? Grab edgetunnel.zip from the
Releases page
and follow Cloudflare Pages (zip upload)
below.
This is a TypeScript refactor of cmliu/edgetunnel.
The upstream project is one 4619-line _worker.js with Chinese identifiers
and zero tests; this fork splits it into 37 typed modules with 171 unit
tests and 9 integration tests, while preserving runtime and KV-schema
compatibility so existing deployments upgrade seamlessly.
The deployable artefact is still a single _worker.js (~80 KB minified).
- VLESS over WebSocket / XHTTP / gRPC. Cloudflare's edge handles inbound TLS; this code parses VLESS frames and forwards traffic.
- Admin panel (
/admin) with KV-backed config, log viewer, traffic stats, runtime config validation. - Subscription generator (
/sub) for Clash, Sing-box, Surge, Quantumult X, Loon, mihomo, etc. Cached server-side for 5 minutes to reduce CPU on client refresh waves. - Upstream chaining: direct, ProxyIP, SOCKS5, HTTP CONNECT.
- Auto-migration of legacy Chinese-keyed KV configs.
See docs/PLATFORM_LIMITS.md for the full
explanation. Briefly:
- Shadowsocks and Trojan support — removed; VLESS covers the same use cases
- HTTPS upstream proxy mode — removed along with the hand-rolled TLS client
- UDP-based protocols (Hysteria2, TUIC) — Workers has no UDP API
- REALITY — Workers can't terminate inbound TLS
- Multi-tenant accounts — deferred until requested
If you need any of the above, run Xray-core or sing-box on a VPS instead. They're better tools for that environment.
You need a Cloudflare account and a domain (Cloudflare DNS, free is fine).
The most reliable path — the Workers dashboard's "paste-deploy" can mis-classify the project as static-assets-only and refuse to let you add environment variables. Pages doesn't have that problem.
-
Get
edgetunnel.zip. Either:- Download it from the Releases page (recommended — built, tested, and signed by CI on every tag), or
- Build it locally:
npm run build && zip edgetunnel.zip _worker.js.
Either way, the zip contains a single
_worker.js(~80 KB) at the root. Don't use the upstreamcmliu/edgetunnelarchive — its_worker.jsis the original pre-refactor 4619-line file, not what this fork's docs describe. -
Pages dashboard → Create → Pages tab → Upload assets → name the project → upload the zip → Deploy site.
-
Settings → Environment variables → Production → add
ADMIN= your admin password → Save. -
Go back to Deployments → Create new deployment → re-upload the same zip → Save and deploy. (This step is required for the env var to take effect on the running deployment.)
-
Settings → Bindings → KV namespace → add binding with variable name
KV→ save → re-deploy once more. -
Custom domains → bind a CNAME subdomain (note: don't use the apex; use a subdomain like
vless.your-domain.com). -
Visit
https://<your-subdomain>/adminand log in withADMIN.
- Fork this repo.
- Pages → Connect to Git → select your fork → set
ADMINenv var. - Add KV binding and custom domain as above.
Most reliable Workers path; avoids the dashboard's static-assets classification trap.
npm install
npx wrangler login
npx wrangler secret put ADMIN # paste your admin password
npx wrangler kv namespace create KV # note the id from the output
# Add the KV binding to wrangler.toml:
# [[kv_namespaces]]
# binding = "KV"
# id = "<id-from-the-create-output>"
npm run deployThen in the Cloudflare dashboard, bind a custom domain to the worker under Triggers → Custom Domain.
⚠️ Cloudflare's new dashboard sometimes classifies a paste-deployed Worker as "static assets only" and then refuses to let you add environment variables (error: "Variables cannot be added to a Worker that only has static assets"). If you hit this, delete the Worker and use one of the paths above.
- Workers & Pages → Create → Workers tab → Hello World template → Deploy. (Don't use other "create" paths — they may upload your code as static assets.)
- Open the Worker → Edit Code → replace the entire content with
the contents of
_worker.js→ Save and Deploy. - Settings → Variables: add
ADMIN= your admin password. - Settings → Bindings: add KV namespace named
KV. - Triggers → Custom Domain: bind a subdomain.
- Visit
https://<your-domain>/adminand log in.
After binding the custom domain and DNS has propagated:
/adminloads — confirms the Worker is running and theADMINenv var is plumbed in./admin/validation.jsonreturns{"ok": true, "count": 0}— confirms the KV binding is correct and there's no schema problem. Ifokis false, the admin panel will also show a yellow banner listing the issues.- Subscription URL — copy from the admin panel, paste into your client (Clash / Sing-box / Surge), confirm nodes show up.
- Real connection — connect through one of the nodes, hit
https://ifconfig.meor similar, confirm the IP is a Cloudflare address (proving traffic actually flowed through the worker). wrangler tail <project-name>(optional) — streamsconsole.logfrom the deployed worker. Useful for debugging issues you can't see from the client side.
If /admin shows a yellow banner ("/admin/validation.json for the full list of what's wrong with
your KV config; the validator coerces bad fields to defaults and keeps
serving, so the worker still works while you fix things.
| Name | Required | Example | Notes |
|---|---|---|---|
ADMIN |
yes | mypassword |
Admin panel password. The user UUID is derived from this. |
KEY |
no | mykey |
Quick-subscription path: visit /<KEY> to fast-fetch nodes. |
UUID |
no | 90cd4a77-... |
Force a fixed UUID. Must be UUIDv4. |
PROXYIP |
no | proxyip.example.net:443 |
Default reverse-proxy IP. |
URL |
no | https://example.com |
Disguise homepage URL (or 1101). |
GO2SOCKS5 |
no | *.example.com,*.foo.cn |
Hostnames forced through SOCKS5 (* = global). |
DEBUG |
no | 1 |
Enable console.log. |
OFF_LOG |
no | 1 |
Disable access logging. |
BEST_SUB |
no | 1 |
Act as preferred-subscription generator. |
The worker accepts upstream-proxy switches via URL path segments:
/proxyip=proxyip.example.net
/socks5=user:password@127.0.0.1:1080
/socks5://user:password@127.0.0.1:1080 (global SOCKS5)
/http=user:password@127.0.0.1:8080
/http://user:password@127.0.0.1:8080 (global HTTP CONNECT)
The /https=... and /https://... syntaxes from upstream are not
supported in this fork — see docs/PLATFORM_LIMITS.md.
The subscription token and node UUID are derived from md5x2(ADMIN + KEY).
To rotate them:
- Change
ADMINorKEY→ both rotate together. - Set
UUIDexplicitly to fix a UUID (must be valid UUIDv4).
Requires Node.js 20+.
npm install
npm run lint # tsc --noEmit
npm test # vitest run (~1s, 171 tests)
npm run test:integration # build + workerd-based end-to-end (~5s)
npm run build # esbuild → _worker.js
npm run analyze # per-module bundle size breakdown
npm run deploy # build + wrangler deployThe committed _worker.js must match a fresh build from src/. CI
enforces this with a drift check; if you edit src/, always rebuild
and commit the resulting bundle alongside.
See CONTRIBUTING.md for the full workflow.
CONTRIBUTING.md— how to make a PRREFACTORING.md— architecture, schema migration, rename mapdocs/PLATFORM_LIMITS.md— what won't work and why
This project is for educational and personal security-testing purposes.
Comply with the laws of your jurisdiction. The maintainers disclaim any
liability for misuse. Cloudflare's Terms of Service prohibit using
Workers as a "general proxy" — enforcement is inconsistent but the risk
of account suspension is real. Use a dedicated CF account, not your main
one. See docs/PLATFORM_LIMITS.md for details.
