Skip to content
Proxy · Use case

Run a headless browser through your own proxy server

A headless browser with a custom proxy is what you need when the exit IP is not yours to choose: a residential provider you already pay for, an address a partner has allow-listed, or a compliance rule about where traffic leaves from. Instead of launching and authenticating Chrome per proxy, pass the proxy URL with the request and the managed browser runs the page through it, credentials included.

The problem

Wiring an authenticated proxy into headless Chrome is fiddly

In Puppeteer the proxy is a launch flag, so it applies to the whole browser, and the username and password go in separately through page authentication on every page you open. Two proxies means two browsers. A second provider, a second country or a rotated credential means relaunching, and a mistake in any of those steps shows up as a timeout rather than a clear error.

Then the credential starts to travel. It ends up in environment files on every worker, in launch arguments that show in process lists, and sometimes in logs when a URL gets printed on failure. The proxy you pay for works, but the plumbing around it becomes its own small service to maintain.

With Microlink the proxy is a request option. proxy.url takes a standard URL with the credentials inside, and every sub-request made while resolving the page, from redirects to assets to dynamic fetches, goes through that server. The browser, the retries and the error codes stay on the API side, and a different proxy on the next call is just a different value.

How it works

How to use your own proxy with a headless browser API

Keep the proxy URL in an environment variable on your server and pass it per request. The proxy guide documents the format and the security rules in full.

1 · Route a request through your proxy
import createClient from 'microlink.io'

const microlink = createClient({
  apiKey: process.env.MICROLINK_API_KEY
})

const markdown = await microlink.markdown('https://example.com/article', {
  proxy: { url: process.env.PROXY_URL }
})

PROXY_URL holds a value such as https://username:password@hostname:port. The page, its redirects and every asset it loads leave through that server, and the call resolves to the Markdown of the page.

2 · Check the IP the target sees
import createClient from 'microlink.io'

const microlink = createClient({
  apiKey: process.env.MICROLINK_API_KEY
})

const text = await microlink.text('https://geolocation.microlink.io', {
  proxy: process.env.PROXY_URL
})

geolocation.microlink.io shows the IP and country the server sees, so the text should name your proxy’s exit, not a Microlink one. A bare string is shorthand for proxy.url.

3 · The same request with curl
curl -G https://pro.microlink.io \
  --data-urlencode 'url=https://example.com/article' \
  --data-urlencode "proxy.url=$PROXY_URL" \
  -d meta=false \
  -d screenshot=true \
  -H "x-api-key: $MICROLINK_API_KEY"

Both the proxy URL and the API key come from the shell environment, so neither is written into a script. Run it server-side only: the proxy URL is a credential.

Parameters used
  • proxy.url Your proxy as a WHATWG URL, https://username:password@hostname:port. Pro plans.
  • proxy Accepts an object or a bare string; a string is treated as proxy.url.
  • retry Server-side retries with exponential backoff when your proxy is briefly unreachable. Default 2.
  • timeout Maximum request time: 30 seconds on the free plan, 60 seconds on Pro.

If your proxy is flaky, the guide pairs it with retry: 3. For logged-in pages, the proxy only changes the exit IP; the session travels as forwarded request headers.

Why it works

Why bring your own proxy to a managed browser

You keep the exit you trust and drop the browser fleet around it. The API owns everything between the proxy and the output.

01 · Every sub-request
The whole page leaves through your server.
Redirects, images, scripts and the API calls the page makes while rendering all go through the proxy you pass, not only the first document request. Allow-lists and regional rules on the target see one consistent address.

The route shows in the x-fetch-mode response header, whose value ends in -proxy, as described in the proxy parameter reference.

02 · One option per call
Switch proxies without relaunching anything.
Each request carries its own proxy.url, so two providers, two countries or a rotated credential are two values, not two browser pools. The same option works on screenshots, PDFs, Markdown, metadata and extraction.

If you do not need a specific provider, you may not need this at all: Pro plans already include a managed proxy, explained in the rotating proxy alternative.

03 · Credentials stay server-side
A proxy URL is a secret. Treat it like one.
Never put it in client-side code, public HTML or an embed URL. Read it from an environment variable on the server, the same way you handle the API key.

When not to: for a country pin with no provider constraint, proxy.location is simpler. It is exclusive with proxy.url, so pick one per request.

FAQ

How do I use an authenticated proxy with a headless browser API?

Pass it as proxy.url in the form https://username:password@hostname:port on a Pro plan. The credentials travel inside the URL, so there is no separate authentication step, and the managed browser routes the whole page through that server.

Do redirects and assets also go through my own proxy?

Yes. All sub-requests made while resolving the target URL, including redirects, assets and dynamic fetches, go through the same proxy server.

Can I use my own proxy and pick a country at the same time?

Not in one request: proxy.url and proxy.location are exclusive. If you need a specific country from your own provider, use that provider’s country endpoint as the proxy.url.

Why do I get EINVALPROXY or EPROXY with my own proxy?

EINVALPROXY means the value could not be parsed as a URL; check the scheme, the port and that special characters in the password are URL-encoded. EPROXY means proxy was sent without a Pro plan, since custom proxies are a Pro feature.

Is it safe to send proxy credentials to a scraping API?

Send them only from your server, over the Pro endpoint with your API key, and read them from an environment variable. Never place them in frontend code or in embed URLs anyone can open. For browser apps, keep the call behind your own backend.
Related use cases

Solve the next problem with the same API

Rotating proxy alternative

Skip the proxy pool: the API escalates to residential IPs only when a site blocks you and remembers what works per domain.

Fetch geo-blocked websites

Route any request through one of 181 countries with a two-letter code, and verify the exit before you trust the page.

Screenshot blocked websites

When a site blocks headless browsers, a Pro key escalates the capture through proxy tiers automatically.

Ready to route through your own proxy?

Keep the provider you trust, drop the browser fleet. One option per request, on every product.