proxy-chain: Connect Tools to an Authenticated Upstream Proxy

Create a local bridge for an authenticated HTTP proxy, connect Playwright, check egress, and close both browser and forwarding resources.

16Yun Engineering TeamOct 28, 20263 min read

Some tools accept a proxy address but cannot conveniently express separate upstream credentials. proxy-chain can create a local forwarding endpoint and authenticate upstream on the tool's behalf.

This tutorial connects an authenticated upstream, a temporary local listener, and a browser, then cleans them up. It changes the integration shape, not the upstream's capabilities or the target website's access policy. Native proxy authentication remains simpler when the tool already supports it.

Prepare credentials and dependencies

Use Node.js 24 and your own working authenticated HTTP proxy:

mkdir upstream-bridge
cd upstream-bridge
npm init -y
npm install proxy-chain@3.0.1 playwright
npx playwright install chromium
export UPSTREAM_PROXY_URL='http://user:pass@proxy.16yun.cn:8888'

The proxy URL is a format example, not a usable service credential.

Characters such as @, colon, and slash require component encoding. A URL object can construct the address:

const url = new URL('http://proxy.16yun.cn:8888');
url.username = 'replace-with-user';
url.password = 'replace-with-password';
const upstream = url.href;

Avoid double encoding and never log the completed credential-bearing URL. Encoding is syntax handling, not encrypted storage.

Run the complete bridge

Create bridge.mts:

import { anonymizeProxy, closeAnonymizedProxy } from 'proxy-chain';
import { chromium } from 'playwright';
 
const upstream = process.env.UPSTREAM_PROXY_URL;
if (!upstream) throw new Error('请配置 UPSTREAM_PROXY_URL');
const parsed = new URL(upstream);
if (parsed.protocol !== 'http:' || !parsed.username || !parsed.password) {
    throw new Error('本例要求带用户名和密码的 HTTP 上游');
}
const localUrl = await anonymizeProxy(upstream);
let browser;
try {
    browser = await chromium.launch({ proxy: { server: localUrl } });
    const page = await browser.newPage();
    const response = await page.goto('https://httpbin.org/ip');
    if (!response || response.status() !== 200) {
        throw new Error('出口检查响应不符合预期');
    }
    const text = await page.locator('body').innerText();
    const data = JSON.parse(text);
    if (typeof data.origin !== 'string' || !data.origin.trim()) {
        throw new Error('出口检查缺少 origin');
    }
    console.log({ stage: 'egress_check', origin: data.origin });
} finally {
    try {
        await browser?.close();
    } finally {
        await closeAnonymizedProxy(localUrl, true);
    }
}

Run:

node bridge.mts

The helper returns an address the browser can use without directly supplying upstream credentials. Nested finally blocks ensure the local proxy still receives cleanup if browser closing fails. See the proxy-chain documentation.

Give the listener an owner

Close its browser consumer before closing the listener. Otherwise an in-flight request can lose its tunnel.

The true cleanup flag forces existing connections closed. That suits an exclusively owned temporary bridge. One task must not apply it to a listener shared with other tasks; a shared service needs its own lifecycle owner.

Not every helper call necessarily creates a listener. An unauthenticated input can pass through unchanged, which is why this example explicitly requires an authenticated HTTP upstream. Do not assume every returned address identifies a newly owned local server.

Interpret the egress check

The expected structure contains an origin field. The script establishes that this path can return the check endpoint's JSON; it does not establish target-specific authorization, market selection, or IP rotation.

After integrating the actual collector, inspect target identity and business fields. A correct egress address does not resolve login pages, challenges, or selector errors.

For diagnosis, compare a native browser proxy configuration with the bridge using the same target and account conditions. Otherwise differences may come from unrelated inputs.

Account for network namespaces

If the bridge runs in container A and the browser in container B, B's loopback address does not reach A. A returned loopback endpoint cannot simply be shared across containers.

For temporary use, keep bridge and consumer in the same network environment. A cross-container gateway needs an explicit service address, listener policy, and long-lived owner.

An HTTP proxy can tunnel HTTPS destinations using CONNECT. The proxy URL scheme describes the connection to the proxy; it does not imply that the destination must use HTTP or that target TLS is being decrypted.

Troubleshoot

407: check upstream credential syntax and identify which proxy requested authentication. It is not automatically a target-site block.

Connection refused: inspect listener lifetime, upstream reachability, and network namespace. A wrong password usually does not explain a refused local TCP connection.

Special-character credentials fail: set URL components explicitly and check for double encoding without exposing the full URL.

The process remains after browser exit: inspect listener cleanup and remaining consumers. Close the exclusively owned forwarder rather than terminating every Node process.

Correct egress but wrong market: check cookies, account settings, language, and the site's market control.

Another process needs long-term reuse: use a managed Server or gateway with explicit startup, authentication, and shutdown ownership. An in-process temporary helper is not a service-discovery system.

The bridge is most useful when its ownership and lifetime remain as clear as the authentication problem it solves.

Need an enterprise proxy plan?

We can tailor architecture to your target domains, concurrency, and reliability goals.