warproxy
v0.2.0
Published
Local HTTP + SOCKS5 proxy (TCP and UDP: BitTorrent, DHT, DNS...) that routes everything through Cloudflare WARP. One command, no admin rights, Windows / Linux / macOS.
Maintainers
Readme
warproxy
A local HTTP + SOCKS5 proxy on one port that sends everything — TCP and UDP (BitTorrent trackers, DHT, µTP, DNS, games…) — through the Cloudflare WARP tunnel.
- One command:
warproxy start. It registers a free WARP account, fetches the engine and starts. - No admin rights, no TUN device, no system-wide VPN. Only the apps you point at the proxy use WARP.
- Windows, Linux, macOS (x64 and arm64; Alpine/musl too).
- Default address
127.0.0.1:25344, a port almost nothing else uses.
your app ──HTTP / SOCKS5──▶ 127.0.0.1:25344 ──userspace WireGuard──▶ Cloudflare WARP ──▶ Internet
(TCP + UDP) warproxy (sing-box engine)Install
Requires Node.js 20 or newer.
npm install -g warproxy
warproxy startThe first start downloads the engine (sing-box, ~30 MB, checksum-verified) and registers a free WARP account. Both are cached in the data directory, so later starts take under a second.
Run it in the background instead:
warproxy start --detach
warproxy status
warproxy test # checks TCP, HTTP proxy and UDP through the tunnel
warproxy stopPoint your software at it
| Software | Setting |
| --- | --- |
| curl | curl -x socks5h://127.0.0.1:25344 https://www.cloudflare.com/cdn-cgi/trace (look for warp=on) |
| Shell tools | export ALL_PROXY=socks5h://127.0.0.1:25344 or HTTPS_PROXY=http://127.0.0.1:25344 |
| git | git config --global http.proxy socks5h://127.0.0.1:25344 |
| Firefox | Settings → Network → Manual proxy → SOCKS Host 127.0.0.1, port 25344, SOCKS v5, tick Proxy DNS when using SOCKS v5 |
| Chrome / Edge | start with --proxy-server="socks5://127.0.0.1:25344" |
| qBittorrent | Options → Connection → Proxy Server: type SOCKS5, host 127.0.0.1, port 25344; tick Use proxy for peer connections and hostname lookups |
Use socks5h:// (with the h) wherever the tool offers the choice: domain names are then resolved through WARP instead of by your local resolver.
What goes through, and what doesn't
| Traffic | Supported |
| --- | --- |
| HTTP, HTTPS, any TCP (HTTP CONNECT, SOCKS4/4a/5) | Yes |
| UDP (DNS, DHT, UDP trackers, µTP, QUIC, games…) | Yes, via SOCKS5 UDP ASSOCIATE, full-cone NAT |
| UDP through the HTTP proxy | No — HTTP has no UDP; use the SOCKS5 side |
| Apps that ignore proxies, or whose proxy support is TCP-only | Not proxied (browsers, for instance, don't send QUIC over SOCKS) |
| ICMP / ping | No (not proxyable) |
| Incoming connections / port forwarding | No. WARP gives you no public port, so torrent clients can only connect out: fewer peers, little seeding |
Be clear-eyed about what WARP is: destinations see a shared Cloudflare IP instead of yours, and your traffic to the Internet is encrypted from your machine to Cloudflare. It is not an anonymity tool — Cloudflare sees your traffic metadata, some sites challenge or block WARP addresses, and you remain bound by Cloudflare's terms and by the law for what you transfer.
Commands
| Command | |
| --- | --- |
| warproxy start | Start in the foreground (Ctrl+C stops). Add -d / --detach for the background |
| warproxy stop | Stop the background instance |
| warproxy restart | Stop, then start with the options you give |
| warproxy status | Is it running? (exit code 0 / 1, scriptable) |
| warproxy test | End-to-end check: SOCKS5 TCP with proxy-side DNS, HTTP CONNECT, SOCKS5 UDP relay |
| warproxy register [--force] | Create or replace the WARP account |
| warproxy config | Print the engine configuration (secrets redacted) |
| warproxy engine [info\|install\|path] | Inspect or reinstall the engine |
| warproxy update [--dry-run] | Install the latest release (--dry-run only reports it) |
Options for start / restart:
| Option | Meaning |
| --- | --- |
| -p, --port <n> | Port (default 25344, env WARPROXY_PORT) |
| -l, --listen <ip> | Bind address (default 127.0.0.1) |
| --auth <user:pass> | Require credentials (env WARPROXY_AUTH, or --user + --pass) |
| --endpoint-port <n> | WARP UDP port: 2408 (default), or 500 / 1701 / 4500 |
| --endpoint <host> | WARP endpoint host (default engage.cloudflareclient.com) |
| --log-level <lvl> | trace debug info warn (default) error |
| --engine <path> | Use your own sing-box ≥ 1.12 (env WARPROXY_ENGINE) |
| --no-check | Skip the tunnel check after startup |
| --no-update-check | Don't look for a newer release at launch (env WARPROXY_NO_UPDATE_CHECK=1) |
Updates
When you run warproxy start or restart, it looks at the latest GitHub release of this project and, if there is a newer one, prints a single line:
warproxy: update available: 0.1.0 -> 0.2.0 (run: warproxy update)Then warproxy update installs it (npm install -g warproxy@<version>); restart a running proxy afterwards with warproxy restart -d. Nothing is ever installed without you asking.
- The lookup runs in parallel with the startup, is limited to once a day (once an hour after a failure) and never prints an error: offline, rate-limited or behind a firewall, you simply see nothing.
- It is one anonymous request to
api.github.com(GitHub sees your IP and awarproxy/<version>User-Agent, nothing else). - Turn it off with
--no-update-checkorWARPROXY_NO_UPDATE_CHECK=1. It is also skipped when theCIenvironment variable is set. - If
updatereports that a release "is not on npm yet", it was published on GitHub a moment ago and npm is still receiving it (about a minute): try again shortly.
Security notes
- It listens on loopback only by default. Note that any local program or user of the machine can then use the proxy; on a shared machine add
--auth. --listen 0.0.0.0exposes the proxy to your network — only do that together with--auth.warproxywarns when you don't.account.json(WireGuard private key, API token) andrun/config.jsonlive in the data directory with owner-only permissions on Linux/macOS (the per-user profile on Windows).- The engine is a pinned sing-box release: the downloaded bytes are checked against a SHA-256 stored in this package, and only those exact bytes are extracted and installed (a custom
--enginebinary is, by design, run as-is). There are no install scripts and no runtime dependencies. - On shared Unix machines, give the password through the
WARPROXY_AUTHenvironment variable rather than--auth: while a foreground instance runs, command-line arguments are visible to other users inps.
Troubleshooting
- "Tunnel check failed" — your network probably blocks UDP 2408. Try
warproxy restart -d --endpoint-port 4500(or500,1701). Some corporate networks block WARP entirely. - Windows Firewall prompt for
warproxy-engine.exe— the proxy itself only listens on loopback, so declining doesn't expose anything; if the tunnel then fails to connect, allow it. - Port already in use — pick another:
warproxy start --port 31337. - GitHub is blocked — host the engine archive on a mirror and set
WARPROXY_ENGINE_BASE_URL(the SHA-256 is still enforced), or pass--engine. - Logs —
warproxy start --log-level infoin the foreground; with--detach, seerun/engine.login the data directory.
Environment variables: WARPROXY_HOME (data directory), WARPROXY_PORT, WARPROXY_AUTH, WARPROXY_ENGINE, WARPROXY_ENGINE_BASE_URL, WARPROXY_NO_UPDATE_CHECK=1, WARPROXY_DEBUG=1 (stack traces).
Data directory: %LOCALAPPDATA%\warproxy (Windows), ~/Library/Application Support/warproxy (macOS), ~/.local/share/warproxy (Linux). To uninstall completely: warproxy stop, npm rm -g warproxy, delete that directory.
How it works
warproxy is a thin, dependency-free Node.js wrapper. It registers a WARP device with Cloudflare's consumer API (the same one the mobile apps and tools like wgcf use), writes a sing-box configuration — a mixed inbound (HTTP + SOCKS) wired to a userspace WireGuard endpoint — and supervises the engine process. Domain names are resolved inside the tunnel (1.1.1.1 through WARP); only the WARP endpoint's own hostname uses your local resolver.
The registration API is unofficial and Cloudflare may change it; if registration starts failing, please open an issue.
Development
npm test # unit tests, offline
WARPROXY_E2E=1 npm test # + live test against real Cloudflare WARP
npm run update-engine [version] # pin a newer sing-box release (rewrites src/engine-manifest.json)
npm run check-engine # make sure that engine accepts our generated configurationReleasing
Publishing a GitHub release publishes the package to npm (release.yml), and the same release is what users are told about by the update check:
npm version minor # bumps package.json and creates the git tag vX.Y.Z
git push --follow-tags
gh release create vX.Y.Z --generate-notes # triggers the workflow: tests, then npm publishThe workflow refuses a release whose tag differs from package.json, runs the tests and npm run check-engine, then publishes (a GitHub pre-release goes to the npm tag next). It uses npm trusted publishing, so no token is stored in the repository. One-time setup on npmjs.com: package warproxy → Settings → Trusted Publisher → GitHub Actions → user ajoly21, repository warproxy, workflow release.yml. Actions → Release → Run workflow does a dry run of everything without publishing.
The repository must be public for users to see releases (the API answers 404 for a private repository, and the check then stays silent); npm build provenance also needs a public repository.
License
MIT for this package. The sing-box engine is downloaded at runtime from its official releases and is licensed separately (GPL-3.0-or-later); it is not bundled. Cloudflare and WARP are trademarks of Cloudflare, Inc.; this project is not affiliated with or endorsed by Cloudflare.
