proxy-chain puts a password-free local proxy in front of an authenticated upstream
TypeScript/Node.js implementation of a proxy server (think Squid) with support for SSL, HTTP/HTTPS, SOCKS5, authentication, and upstream proxy chaining.
At a glance
- What is it?
- proxy-chain is a Node.js library that runs a programmable HTTP proxy in the style of Squid, with Basic authentication, upstream proxy chaining, SOCKS4/5 forwarding and HTTP CONNECT tunnelling configured from code. Its main practical use is letting headless Chrome and Puppeteer reach a proxy that requires credentials, which the browser cannot express on its own.
- Who is it for?
- proxy-chain fits Node.js scraping and automation code that must authenticate to an upstream proxy, especially when the client cannot carry proxy credentials itself, and it does not fit a team that wants a full-featured standalone proxy daemon.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 2 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem is a browser that cannot put a password in a proxy URL
proxy-chain is a TypeScript implementation of a proxy server for Node.js, described as a programmable proxy server in the spirit of Squid. It supports SSL/TLS, authentication, upstream proxy chaining, the SOCKS4/5 protocol, custom HTTP responses and traffic statistics.
The motivating case is concrete. Headless Chrome does not support proxy URLs that carry a password, such as http://username:[email protected]:8080. Any automation stack that needs an authenticated upstream proxy therefore hits a wall: the browser can be pointed at a proxy, but not at one that asks for credentials. The answer the package gives is to sit in between. You start a local proxy with no password, point the browser at that, and the library forwards the request upstream, adding the credentials on the way.
That also covers the broader case of any client that needs proxy credentials injected on its behalf, or that needs its requests routed through several hops. The package is developed by Apify to support their Apify Proxy product, and it is also used by Crawlee.
A minimal proxy is one Server object and a listen call
The simplest useful configuration creates a Server with a port and starts listening. The example below is the whole thing:
import { Server } from 'proxy-chain';
const server = new Server({ port: 8000 });
await server.listen();
console.log(`Proxy server is listening on port ${server.port}`);The port is 8000 by default, and the host option controls where the server binds. The documentation notes that if the host is not specified the server listens on an unspecified IP address, 0.0.0.0 in IPv4 and :: in IPv6, and that the host option can be used to limit access to the proxy server. That default deserves attention, because a proxy without credentials listening on every interface is a resource that anyone who can reach the port can use.
The verbose option turns on detailed logging, and once the server is running it emits events for connection lifecycle and failure, including a connectionClosed event carrying the connection id and traffic statistics, and a requestFailed event carrying the request and the error.
prepareRequestFunction is where authentication and chaining are decided per request
The configuration of authentication and upstream chaining is defined in code and can be fully dynamic, and the whole mechanism is a single option, prepareRequestFunction. The function receives a request object with the parsed username and password from the Proxy-Authorization header, the target hostname and port, an isHttp flag that distinguishes a plain request from a CONNECT tunnel, and a connection id that can be used to look up traffic statistics. It returns an object describing what the server should do next.
The interesting fields are upstreamProxyUrl, which points the request at another HTTP, HTTPS or SOCKS proxy, and requestAuthentication, which decides whether the client is answered with an HTTP 407 response carrying the Proxy-Authenticate header. If requestAuthentication is true, the upstreamProxyUrl field is ignored, and an optional failMsg replaces the default Proxy credentials required message. There is also an ignoreUpstreamProxyCertificate flag, which applies to an HTTPS upstream and makes the request ignore certificate errors, useful when that upstream uses a self-signed certificate. A customTag field passes an arbitrary value back through the tunnelConnectResponded or tunnelConnectFailed events, which is how a caller associates external state with an internal connection.
The credentials in the upstream URL must be URI-encoded, and the username and password in that URL are what the upstream sees, not the ones the client sent.
Pointing at an authenticated upstream with a two-line chain
The chaining part of the configuration is small and worth reading closely, because it is where the credentials are injected. In the documentation example, the upstream is given as an HTTP URL with embedded credentials, and the alternative is a SOCKS URL:
prepareRequestFunction: ({ request, username, password, hostname, port, isHttp, connectionId }) => {
return {
requestAuthentication: username !== 'bob' || password !== 'TopSecret',
upstreamProxyUrl: `http://username:[email protected]:3128`,
// Or use SOCKS4/5 proxy, e.g.
// upstreamProxyUrl: `socks://username:[email protected]:1080`,
ignoreUpstreamProxyCertificate: true,
failMsg: 'Bad username or password, please try again.',
customTag: { userId: '123' },
};
},Setting upstreamProxyUrl to null puts the server in direct mode, where the connection goes straight to the target server. The authentication check in the sample is a plain string comparison against bob and TopSecret, which is fine as an illustration and obviously not something to ship. The library leaves the credential decision to the caller, which is the point: the function runs per request, so different clients, different targets or different tenancy can be routed to different upstream proxies from the same listener.
HTTP CONNECT carries arbitrary protocols, including over SOCKS
The forwarding support covers two shapes of traffic. Plain HTTP and SOCKS forwarding handles ordinary requests, and HTTP CONNECT tunnelling forwards arbitrary protocols such as HTTPS or FTP. The tunnel is what makes an HTTPS target work through the proxy: the client asks to CONNECT, the server opens the tunnel, and the TLS handshake then happens end to end between client and target rather than being terminated at the proxy.
The same CONNECT tunnelling also supports the SOCKS protocol, so a SOCKS upstream can carry the tunnel for a non-SOCKS client. The tunnel events, tunnelConnectResponded and tunnelConnectFailed, are where the customTag from the prepare function comes back out, and the connectionClosed event reports the traffic statistics for the connection once it is done.
One boundary is worth stating clearly. The library supports only the Basic Proxy-Authorization scheme, so an upstream that expects NTLM, Negotiate or a digest exchange is out of scope. The README does not describe a way to plug in a different authentication handler.
TLS on the listener is the same server with a certificate
Running the proxy over TLS is a variation on the same object rather than a separate implementation. The documentation describes an HTTPS proxy server with a self-signed certificate, and notes that it works identically to the HTTP version but with TLS encryption. The example reads a key and a certificate from disk with node:fs and node:path, and the code carries a comment reminding you to replace them with your own:
import { Server } from 'proxy-chain';
import fs from 'node:fs';
import path from 'node:path';
(async () => {
// TODO: update these lines to use your own key and cert
const sslKey = fs.readFileSync(path.join(import.meta.dirname, 'ssl.key'));
const sslCrt = fs.readFileSync(path.join(import.meta.dirname, 'ssl.crt'));What differs from the plain HTTP setup is the event listening needed for the encrypted case. The rest of the configuration, including prepareRequestFunction and the upstream chaining, is shared with the HTTP server.
Scaffolding, licensing and the shape of the repository
The package is published to npm as proxy-chain, at version 3.0.1, and is an ES module: the manifest sets type to module, the entry point is dist/index.js, and the export map publishes types from dist/index.d.ts. The build is a plain TypeScript compile with tsc, and the test setup is Vitest with separate unit and e2e projects, plus scripts that run the suite under Bun, including an e2e variant that uses NODE_OPTIONS=--insecure-http-parser for the TCP tunnel test.
The repository holds src, test, examples and docs directories, with an examples/apify_proxy_tunnel.js file that shows the package used against Apify Proxy. There is also a RELEASING.md and a CHANGELOG.md. The project is maintained by Apify Technologies under the Apache-2.0 licence, and the last push was on 2026-10-07, following v3.0.1 on 2026-09-08 and v3.0.0 on 2026-05-19.
Compared with running a real Squid instance, the difference is scope rather than quality. proxy-chain is a library you embed in a Node.js process, and the point of that choice is exactly the case Squid does not cover well: injecting credentials that the client cannot send.
Editorial conclusion
proxy-chain fits Node.js scraping and automation code that must authenticate to an upstream proxy, especially when the client cannot carry proxy credentials itself, and it does not fit a team that wants a full-featured standalone proxy daemon. Before adopting it, confirm your upstream proxy accepts the Basic Proxy-Authorization scheme the library supports, since other authentication mechanisms are out of scope, and decide how the local listener is exposed, since the default bind is an unspecified address and the host option exists to narrow it. The package is Apache-2.0 licensed and was last pushed to on 2026-10-07.
Frequently asked questions
What is proxy-chain used for?
It runs a programmable HTTP proxy server in Node.js, and its main use is letting headless Chrome and Puppeteer connect to an upstream proxy that requires credentials, which the browser cannot express in a proxy URL. It also supports upstream proxy chaining, SOCKS4/5 forwarding and HTTP CONNECT tunnelling.
How do I create a basic proxy server with proxy-chain?
Create a Server with a port, default 8000, and call listen on it. The server binds to an unspecified IP address unless you set the host option, and the port and host can be used to limit access to the proxy.
How does proxy-chain handle authentication?
The prepareRequestFunction option receives the parsed username and password from the Proxy-Authorization header and decides whether to answer the client with an HTTP 407 response. Only the Basic Proxy-Authorization scheme is supported.
Can proxy-chain forward to an upstream SOCKS proxy?
Yes. The upstreamProxyUrl returned from prepareRequestFunction can point at an HTTP, HTTPS or SOCKS proxy, for example socks://username:[email protected]:1080. Setting it to null makes the server forward directly to the target server instead.
What licence is proxy-chain released under?
The package is published under the Apache-2.0 licence, with the licence text in the LICENSE file in the repository. It is developed by Apify Technologies and published to npm as proxy-chain.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/apify-proxy-chain)Was this useful?