proxy-chain

A programmable Node.js proxy server with SSL/TLS, SOCKS4/5, and upstream proxy chaining for tools like Puppeteer and Crawlee.

Library
npm
v3.0.0
1,021stars
Apache License 2.0

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
82/100Excellent
Development Activity92
Maintenance76
Community72
Maturity60
Momentum28

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
82/100Excellent
Architecture85
Code Quality88
Innovation78
Learning Curve75

proxy-chain is a Node.js library for running a fully programmable HTTP/HTTPS proxy server, comparable in scope to Squid but configured entirely in code. Its Server class supports SSL/TLS termination, HTTP CONNECT tunneling for arbitrary protocols, SOCKS4/5 chaining to upstream proxies, and per-request Basic authentication decided dynamically by a user-supplied prepareRequestFunction.

It exists to solve a specific, recurring problem: headless browsers such as Chrome (and by extension Puppeteer and Playwright) cannot connect to an upstream proxy URL that embeds a username and password. proxy-chain spins up a local, password-free proxy that transparently forwards to the authenticated upstream, so tools that only understand bare http://host:port proxy URLs can still ride on authenticated infrastructure. It is maintained by Apify to power Apify Proxy and is a dependency of Crawlee, one of the most widely used Node.js web-scraping frameworks.

What You Get

  • A Server class that starts an HTTP or HTTPS proxy on a given port with a single await server.listen() call
  • A prepareRequestFunction hook for dynamic per-request Basic authentication and upstream proxy selection (HTTP, HTTPS, or SOCKS4/5)
  • anonymizeProxy() / closeAnonymizedProxy() helpers that wrap a password-protected upstream proxy in a local, credential-free proxy for tools like Puppeteer that can’t pass a proxy URL with embedded credentials
  • createTunnel() / closeTunnel() helpers for tunneling arbitrary TCP traffic through an HTTP/HTTPS proxy to a target host and port
  • Custom HTTP response and custom CONNECT-server overrides (customResponseFunction, customConnectServer) for returning canned responses or rerouting tunnels without touching a target server
  • Per-connection traffic statistics (getConnectionStats) and events (connectionClosed, requestFailed, tlsError, tunnelConnectResponded, tunnelConnectFailed) for monitoring and observability
  • A redactUrl() utility for safely logging proxy URLs without leaking embedded passwords
  • Non-standard 590-599 HTTP status codes that distinguish upstream failure modes (DNS failure, connection refused, auth failed, etc.) beyond the generic 502 Bad Gateway

Common Use Cases

  • Giving Puppeteer or Playwright a proxy URL it can actually use when the real upstream proxy requires a username and password
  • Building a local proxy layer in front of a pool of authenticated upstream/residential proxies (as Apify Proxy and Crawlee do)
  • Tunneling non-HTTP protocols like FTP or raw TCP services through an HTTP CONNECT proxy to reach a service behind a static IP
  • Serving canned/mocked HTTP responses to specific requests without ever contacting the real target server
  • Measuring per-connection bandwidth and diagnosing upstream proxy failures using the distinct 590-599 status codes

Under The Hood

Architecture The single entry point, src/index.ts, re-exports only server.ts, request_error.ts, anonymize_proxy.ts, tcp_tunnel_tools.ts, and the redact_url utility, so everything else under src/ is a private implementation detail free to change. Server extends EventEmitter, owns the underlying http/https server, and its onRequest/onConnect handlers route each request to one dedicated transport module per request/upstream combination: forward.ts for a plain HTTP request with no or an HTTP/HTTPS upstream, forward_socks.ts when the upstream is SOCKS, direct.ts/chain.ts/chain_socks.ts for CONNECT tunnels with no upstream, an HTTP/HTTPS upstream, or a SOCKS upstream respectively, and custom_response.ts/custom_connect.ts for user-supplied overrides. Each transport module exports its own HandlerOpts type and single entry function that server.ts calls after casting at the call site. Every socket is stamped with a proxyChainId in registerConnection(), which becomes the correlation key for the connectionClosed event, getConnectionStats(), and manual closeConnection(). Module-level registries (anonymizedProxyUrlToServer, runningServers) hold process-global state for the helper functions layered on top of Server.

Tech Stack The package targets Node.js >=20.11 and ships as ESM-only TypeScript (module: NodeNext, every relative import ending in .js), built with tsc and linted via @apify/eslint-config’s flat ESLint 9 config. Tests run on Vitest across two named projects, unit and e2e, with @vitest/coverage-v8 for coverage. Runtime dependencies are deliberately minimal: socks and socks-proxy-agent implement the SOCKS4/5 protocol, and tslib supplies TypeScript helpers — the proxy itself is built directly on Node’s built-in http/https/net modules rather than any web framework. CI workflows exercise the Node 20/22/24/26 matrix, and a documented Bun-compatible test suite plus Dockerfile give a second reference runtime and environment.

Code Quality Tests are organized as two Vitest projects distinguished by directory rather than filename suffix, with e2e tests defaulting to a strict 2-second timeout so hangs fail fast, shared port-range allocation to avoid collisions across files, and reusable helpers in test/utils/ for spinning up target servers and asserting HTTP behavior. Error handling is explicit and typed: a dedicated RequestError class carries a statusCode and headers, is distinguished from generic errors in Server.failRequest(), and normalizeHandlerError() maps specific known failure messages into typed 590-599 RequestErrors instead of leaking raw stack traces. Type-checking and linting run as separate CI-gated steps from build and test, and the project documents its own architecture, testing, and style conventions for contributors directly in the repo.

API Design The public surface is intentionally small — a single Server class plus a handful of standalone helpers (anonymizeProxy, createTunnel, redactUrl, listenConnectAnonymizedProxy) — so getting started is new Server({ port }); await server.listen(); with no required configuration. The one extension point, prepareRequestFunction, is documented in the README with a fully annotated example covering every option field, keeping day-to-day usage minimal while still exposing power-user hooks such as custom responses, custom CONNECT servers, custom DNS lookup, and custom HTTP agents for upstream connection pooling. The non-standard 590-599 status codes are a deliberate, well-documented choice that turns an otherwise opaque 502 Bad Gateway into an actionable diagnostic distinguishing DNS failure, refused connections, and bad upstream credentials — an unusual but genuinely useful piece of API design for anyone operating real proxy infrastructure.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search