Per-Context Proxy: Independent Network Per Browser
Configure independent proxy and geographic identity per BrowserContext. Run multiple regions in a single browser instance with automatic timezone, locale, and language alignment.
Prefer the maintained product doc?
This article has a matching page in the docs center. Use the docs for the canonical setup flow, current flags, and long-term reference.
Network ownership by context
When running multiple identities or regional workflows in a browser, a single proxy for the entire session is not enough. If every context shares the same IP address, traffic from different identities can be linked by that shared network path. Tracking systems observe IP patterns, and two accounts originating from the same address at similar times are easy to correlate.
Per-context proxy solves this by assigning a dedicated proxy to each BrowserContext. Each context routes through a different proxy server, receives its own public IP, and gets geographic metadata (timezone, locale, language) derived from that proxy's exit location. Combined with per-context fingerprint isolation, each context becomes a fully independent identity with no shared network or fingerprint signals.
This article covers how per-context proxy works in BotBrowser, how to configure it in both Puppeteer and Playwright, and how to optimize performance for multi-region deployments.
Privacy Impact
A single proxy creates a shared network identity across all contexts. Even with different fingerprint profiles, the shared IP becomes a correlation point. Consider these risks:
- IP-based correlation: Two accounts accessing the same service from the same IP can be linked, regardless of fingerprint differences
- Geographic inconsistency: A context configured with a German fingerprint profile but routed through a US proxy creates an obvious mismatch between stated location and network origin
- Timing analysis: Multiple identities sharing one proxy can be correlated by their traffic timing patterns, since all requests originate from the same network endpoint
Per-context proxy eliminates these risks. Each context has its own proxy, its own public IP, and its own geographic metadata. There is no shared network signal between contexts.
The context network model
BrowserContext-Level Network Isolation
In Chromium, a BrowserContext is an isolated browsing environment. Each context has its own cookie jar, localStorage, sessionStorage, IndexedDB, and cache. Standard Chromium provides this storage isolation out of the box.
BotBrowser extends this isolation to the network layer. When a BrowserContext is assigned its own proxy, all HTTP, HTTPS, and WebSocket traffic from that context routes through the specified proxy server. Other contexts in the same browser instance are unaffected. This is true network isolation at the context level, not a page-level interceptor that can miss early requests or WebSocket connections.
Automatic Geographic Detection
When a context connects through a proxy, BotBrowser detects the proxy's exit IP and automatically derives geographic settings for that specific context:
- Timezone: Derived from the proxy IP's geolocation (e.g.,
America/New_Yorkfor a US East proxy) - Locale: Matched to the proxy's country (e.g.,
en-US) - Languages: Set based on the proxy's region (e.g.,
en-US,en) - Geolocation: Coordinates approximated from the IP
This happens independently for each context. A US proxy context gets US geographic settings, while a German proxy context in the same browser gets German settings. No manual configuration of timezone, locale, or language is needed.
Integration with Per-Context Fingerprint
Per-context proxy works alongside BotBrowser's per-context fingerprint isolation (ENT Tier3). Each context can receive:
- A unique fingerprint profile via
--bot-profile - An independent proxy and public IP
- Matching geographic metadata derived from the proxy
- Isolated storage (cookies, localStorage, IndexedDB)
The combination means each context is a fully independent identity. No fingerprint signals, network paths, or storage are shared between contexts.
Assigning a route
Puppeteer: Multi-Region Setup
In Puppeteer, per-context proxy is configured through CDP (Chrome DevTools Protocol). You create a BrowserContext, assign proxy settings and a fingerprint profile via BotBrowser.setBrowserContextFlags, then create pages within that context.
const puppeteer = require('puppeteer-core');
const browser = await puppeteer.launch({
executablePath: process.env.BOTBROWSER_EXEC_PATH,
headless: true,
defaultViewport: null,
args: ['--bot-profile=path/to/default-profile.enc'],
});
const client = await browser.target().createCDPSession();
// US context with US proxy
const usCtx = await browser.createBrowserContext({
proxyServer: 'socks5://user:pass@us-proxy.example.com:1080',
});
await client.send('BotBrowser.setBrowserContextFlags', {
browserContextId: usCtx._contextId,
botbrowserFlags: [
'--bot-profile=path/to/us-profile.enc',
'--proxy-ip=203.0.113.1',
],
});
const usPage = await usCtx.newPage();
// UK context with UK proxy
const ukCtx = await browser.createBrowserContext({
proxyServer: 'socks5://user:pass@uk-proxy.example.com:1080',
});
await client.send('BotBrowser.setBrowserContextFlags', {
browserContextId: ukCtx._contextId,
botbrowserFlags: [
'--bot-profile=path/to/uk-profile.enc',
'--proxy-ip=198.51.100.1',
],
});
const ukPage = await ukCtx.newPage();
// DE context with German proxy
const deCtx = await browser.createBrowserContext({
proxyServer: 'socks5://user:pass@de-proxy.example.com:1080',
});
await client.send('BotBrowser.setBrowserContextFlags', {
browserContextId: deCtx._contextId,
botbrowserFlags: [
'--bot-profile=path/to/de-profile.enc',
'--proxy-ip=192.0.2.1',
],
});
const dePage = await deCtx.newPage();
// Each context navigates with its own proxy and geographic identity
await Promise.all([
usPage.goto('https://example.com'),
ukPage.goto('https://example.com'),
dePage.goto('https://example.com'),
]);
await browser.close();
Important: BotBrowser.setBrowserContextFlags must be called before creating any page in that context. The renderer process reads its flags at startup. If a page already exists, the flags will not take effect.
Puppeteer: Proxy via botbrowserFlags Only
You can also configure the proxy entirely through botbrowserFlags, without passing proxyServer to createBrowserContext:
const ctx = await browser.createBrowserContext();
await client.send('BotBrowser.setBrowserContextFlags', {
browserContextId: ctx._contextId,
botbrowserFlags: [
'--bot-profile=path/to/profile.enc',
'--proxy-server=socks5://user:pass@proxy.example.com:1080',
'--proxy-ip=203.0.113.1',
],
});
const page = await ctx.newPage();
This approach configures the proxy server, known exit address, and route policy in a single place.
Playwright: Per-Context Proxy
Playwright provides native per-context proxy support through browser.newContext():
const { chromium } = require('playwright-core');
const browser = await chromium.launch({
executablePath: 'path/to/botbrowser/chrome',
args: ['--bot-profile=path/to/profile.enc'],
headless: true,
});
// US context
const usContext = await browser.newContext({
proxy: { server: 'socks5://us-proxy:1080', username: 'user', password: 'pass' },
});
// Germany context
const deContext = await browser.newContext({
proxy: { server: 'socks5://de-proxy:1080', username: 'user', password: 'pass' },
});
// Each context automatically gets geographic settings matching its proxy
const usPage = await usContext.newPage();
const dePage = await deContext.newPage();
await usPage.goto('https://example.com');
await dePage.goto('https://example.com');
BotBrowser auto-derives timezone, locale, and language for each context based on its proxy's exit IP.
Skipping IP Detection with --proxy-ip
Each context's proxy triggers an IP detection request to determine geographic settings. If you already know the proxy's exit IP, you can skip this detection step with --proxy-ip:
await client.send('BotBrowser.setBrowserContextFlags', {
browserContextId: ctx._contextId,
botbrowserFlags: [
'--bot-profile=path/to/profile.enc',
'--proxy-server=socks5://user:pass@proxy.example.com:1080',
'--proxy-ip=203.0.113.1', // Skip IP lookup, use this IP for geo detection
],
});
This eliminates the per-context IP lookup latency, which is especially beneficial when creating many contexts in sequence.
Regional settings in the current release
Regional values are resolved independently for each setting. An explicit timezone, locale, language, or location remains authoritative for that context. Any setting left on auto follows the context's selected proxy route. This lets a deployment pin one required value while allowing the remaining regional values to stay aligned with the route.
Keep explicit values intentional. A copied timezone or language value can remain valid syntactically while no longer matching a changed proxy region. Store the proxy assignment and any explicit regional exceptions in the same deployment record, then create a fresh context when either changes.
For release validation, compare each context with its own route plan. Confirm the expected application locale, time presentation, and network region without assuming that success in one context proves the configuration of another.
Common Scenarios
Multi-Region Testing in One Browser
Run US, UK, and German contexts simultaneously within a single browser instance. Each context gets a different proxy, fingerprint profile, and geographic identity:
const regions = [
{
name: 'US',
proxy: 'socks5://user:pass@us.proxy.example.com:1080',
ip: '203.0.113.1',
profile: 'path/to/us-profile.enc',
},
{
name: 'UK',
proxy: 'socks5://user:pass@uk.proxy.example.com:1080',
ip: '198.51.100.1',
profile: 'path/to/uk-profile.enc',
},
{
name: 'DE',
proxy: 'socks5://user:pass@de.proxy.example.com:1080',
ip: '192.0.2.1',
profile: 'path/to/de-profile.enc',
},
];
const client = await browser.target().createCDPSession();
for (const region of regions) {
const ctx = await browser.createBrowserContext({
proxyServer: region.proxy,
});
await client.send('BotBrowser.setBrowserContextFlags', {
browserContextId: ctx._contextId,
botbrowserFlags: [
`--bot-profile=${region.profile}`,
`--proxy-ip=${region.ip}`,
],
});
const page = await ctx.newPage();
await page.goto('https://example.com');
// Verify geographic identity
const tz = await page.evaluate(() =>
Intl.DateTimeFormat().resolvedOptions().timeZone
);
const lang = await page.evaluate(() => navigator.language);
console.log(`${region.name}: timezone=${tz}, language=${lang}`);
}
Per-Context: Different Profile + Different Proxy
Each context can use a completely different fingerprint profile. A Windows profile with a US proxy and a macOS profile with a UK proxy, all within one browser:
// Windows identity with US proxy
const winCtx = await browser.createBrowserContext({
proxyServer: 'socks5://user:pass@us-proxy.example.com:1080',
});
await client.send('BotBrowser.setBrowserContextFlags', {
browserContextId: winCtx._contextId,
botbrowserFlags: [
'--bot-profile=path/to/windows-profile.enc',
'--proxy-ip=203.0.113.1',
],
});
// macOS identity with UK proxy
const macCtx = await browser.createBrowserContext({
proxyServer: 'socks5://user:pass@uk-proxy.example.com:1080',
});
await client.send('BotBrowser.setBrowserContextFlags', {
browserContextId: macCtx._contextId,
botbrowserFlags: [
'--bot-profile=path/to/macos-profile.enc',
'--proxy-ip=198.51.100.1',
],
});
Runtime Proxy Switching
For scenarios that require changing a context's proxy after creation (e.g., geo-rotation within a session), use BotBrowser.setBrowserContextProxy (ENT Tier3):
const ctx = await browser.createBrowserContext();
const page = await ctx.newPage();
const client = await browser.target().createCDPSession(); // must use browser-level session
// Start with US proxy
await client.send('BotBrowser.setBrowserContextProxy', {
browserContextId: ctx._contextId,
proxyServer: 'socks5://user:pass@us-proxy.example.com:1080',
proxyIp: '203.0.113.1',
});
await page.goto('https://example.com');
// Switch to UK proxy at runtime
await client.send('BotBrowser.setBrowserContextProxy', {
browserContextId: ctx._contextId,
proxyServer: 'socks5h://user:pass@uk-proxy.example.com:1080',
proxyIp: '198.51.100.1',
proxyBypassList: 'localhost;127.0.0.1',
proxyBypassRgx: 'cdn\\.example\\.com|/static/',
});
await page.goto('https://example.co.uk');
// Switch to Japan proxy
await client.send('BotBrowser.setBrowserContextProxy', {
browserContextId: ctx._contextId,
proxyServer: 'socks5://user:pass@jp-proxy.example.com:1080',
proxyIp: '192.0.2.1',
});
await page.goto('https://example.jp');
After each switch, BotBrowser re-detects geographic settings and applies them to the context. The geo update takes effect on the next main-frame navigation.
Performance Optimization
Use --proxy-ip to Reduce Lookup Overhead
When you know the exit IP for each proxy, always pass --proxy-ip. Without it, BotBrowser performs an IP detection request for each context on the first navigation. With 10+ contexts, these lookups add up:
// Without --proxy-ip: each context makes an IP detection request
// With --proxy-ip: geo detection is instant, no network request needed
const contexts = proxies.map(async (proxy) => {
const ctx = await browser.createBrowserContext({ proxyServer: proxy.server });
await client.send('BotBrowser.setBrowserContextFlags', {
browserContextId: ctx._contextId,
botbrowserFlags: [
'--bot-profile=path/to/profile.enc',
`--proxy-ip=${proxy.knownIp}`,
],
});
return ctx;
});
Shared session resources
Contexts in one BotBrowser session reuse common session resources while retaining their own cookies, storage, profile assignment, and proxy route. Measure the safe context count with the real page set and worker size because memory use depends on the workload.
Combine with Per-Context Fingerprint
When you assign both a proxy and a fingerprint profile per context, each context becomes a fully independent identity without the overhead of separate browser instances:
// Single browser instance, 3 complete identities
const identities = [
{ profile: 'profiles/win-us.enc', proxy: 'socks5://us:1080', ip: '203.0.113.1' },
{ profile: 'profiles/mac-uk.enc', proxy: 'socks5://uk:1080', ip: '198.51.100.1' },
{ profile: 'profiles/linux-de.enc', proxy: 'socks5://de:1080', ip: '192.0.2.1' },
];
for (const id of identities) {
const ctx = await browser.createBrowserContext({ proxyServer: id.proxy });
await client.send('BotBrowser.setBrowserContextFlags', {
browserContextId: ctx._contextId,
botbrowserFlags: [
`--bot-profile=${id.profile}`,
`--proxy-ip=${id.ip}`,
],
});
// Each context: unique fingerprint + unique proxy + unique geo = fully independent
}
Release validation
After setting up per-context proxies, verify each context independently:
async function verifyContext(context, label) {
const page = await context.newPage();
// Check public IP
await page.goto('https://httpbin.org/ip');
const ipData = await page.evaluate(() => document.body.textContent);
console.log(`[${label}] IP: ${ipData.trim()}`);
// Check timezone
const tz = await page.evaluate(() =>
Intl.DateTimeFormat().resolvedOptions().timeZone
);
console.log(`[${label}] Timezone: ${tz}`);
// Check language
const lang = await page.evaluate(() => navigator.language);
console.log(`[${label}] Language: ${lang}`);
// Check locale
const locale = await page.evaluate(() =>
Intl.NumberFormat().resolvedOptions().locale
);
console.log(`[${label}] Locale: ${locale}`);
await page.close();
}
await verifyContext(usCtx, 'US');
await verifyContext(ukCtx, 'UK');
await verifyContext(deCtx, 'DE');
Confirm that each context shows a different IP, timezone, and locale matching the proxy's geographic location.
Operational questions
What license tier is required for per-context proxy?
Per-context proxy with fingerprint isolation requires ENT Tier3. Runtime proxy switching via BotBrowser.setBrowserContextProxy also requires ENT Tier3.
Can I use Playwright for per-context proxy?
Yes. Playwright natively supports per-context proxy through browser.newContext({ proxy: ... }). BotBrowser auto-derives geographic settings for each Playwright context.
Must setBrowserContextFlags be called before creating a page?
Yes. The renderer process reads its flags at startup. If a page already exists in the context, the new flags will not apply. The correct order is: createBrowserContext -> setBrowserContextFlags -> newPage.
Can I change a context's proxy after creation?
Yes, via BotBrowser.setBrowserContextProxy (ENT Tier3). This allows runtime proxy switching without recreating the context. Geographic settings are re-derived on the next main-frame navigation.
What proxy protocols are supported?
All protocols supported by --proxy-server work per-context: socks5://, socks5h://, http://, https://. All support embedded authentication (user:pass@host:port).
How many contexts can run simultaneously? Each context consumes memory according to its pages and cached resources. Establish the supported count with a representative workload and leave capacity for navigation peaks.
Does BotBrowser auto-detect geography for each context? Yes. Each context with a different proxy gets independent geo-detection. BotBrowser detects the exit IP and configures timezone, locale, and language for that specific context.
What happens if I do not set --proxy-ip?
BotBrowser performs an automatic IP detection request on the first navigation for each context. This works correctly but adds a small latency overhead. Setting --proxy-ip eliminates this lookup.
Can each context use its own routing policy? Yes. Assign the documented proxy settings with the context configuration and validate that context against its own route plan.
Assigning route ownership
Every context route needs an owner. The owner should know which application uses the route, which profile families may be paired with it, which proxy inventory supplies it, and who approves a regional change. This does not require a large governance system. A short deployment record with those fields is enough to prevent an unowned proxy string from spreading across worker templates.
Keep credentials separate from the route description. Store secrets through the deployment's normal secret management, and let the context configuration reference them through the supported launch mechanism. Route ownership and credential rotation then remain separate changes that can be reviewed and rolled back independently.
Use stable names for proxy groups. A name based on region and purpose is easier to maintain than a hostname copied into every job. The inventory can change an endpoint while the deployment continues to request the same approved route family. When the route purpose changes, create a new assignment instead of silently reusing an old label.
The profile, proxy route, and explicit regional values form one release unit. Record them together. A context with a valid profile and a healthy proxy can still be misconfigured if the two describe different regions or if a copied language override belongs to an older assignment.
Managing explicit regional exceptions
An explicit value is useful when an application has a stable requirement that should not follow the proxy. A customer support portal may need a fixed interface language while its timezone and location follow regional routes. A date-sensitive application may require a selected timezone even when other values remain automatic.
Document the reason for each exception. Without a reason, a future operator cannot tell whether the value is required or merely left over from an earlier test. Remove stale exceptions instead of carrying them into every new context template.
Review exceptions whenever the proxy region or profile family changes. A language that fit one route may not fit another. A timezone copied from a desktop profile may not belong in a mobile profile. Because settings are resolved independently, one stale value can create drift even when every other value follows the intended route.
Prefer auto when route alignment is the actual requirement. It reduces duplicated configuration and lets the context use the documented source priority. Use explicit values when the application requirement is stronger than the route-derived choice, not simply to fill every field.
Staged rollout for route changes
Start with a representative deployment group. Use the intended browser release, profile family, worker platform, proxy inventory, and page set. A route that works from a developer machine does not establish that production credentials, access controls, or region assignments are correct.
Before the change, run the approved application workflow on the current route and record its outcome. After assigning the new route to a fresh context, repeat the same workflow. Compare application completion, selected region, expected language and time presentation, and normal browser exit status.
Keep the page set and job mix comparable during the rollout. If a different application version, profile family, and proxy region all change together, the result does not identify which change affected the workflow. Move one reviewed release unit at a time.
Expand by deployment group rather than changing every worker simultaneously. Keep the previous route assignment available until the new group is stable. If the application outcome no longer matches the release plan, restore the prior assignment and investigate from that known baseline.
Route changes should create new contexts. A context already carrying cookies, storage, and regional state from an earlier route is not a clean validation target. Starting fresh also makes the support record easier to interpret because the context has one profile and one route history.
Route health and application health
Proxy availability and application success are related but different observations. Infrastructure monitoring can show whether an approved route accepts connections. It does not prove that a context received the intended assignment or that the application completed normally.
Likewise, an application can fail while the route is healthy because credentials, profile settings, destination availability, or application data changed. Keep these checks separate. First confirm the assigned route and its availability, then review the approved application outcome.
For each context, retain only the support fields needed for the release: route family, profile family, regional exceptions, browser release, deployment group, time window, and outcome. Avoid collecting full destination histories merely to show that a proxy was active.
When a context fails, stop issuing new work to the affected deployment group if continued retries could create duplicate application actions. Confirm the assignment, route health, and last known good release unit. Restore the previous unit before introducing another change.
Capacity and lifecycle planning
The safe number of contexts depends on pages, media, extensions, profile data, and worker resources. Measure with the production page set and leave room for short navigation peaks. A capacity result from a simple blank page should not be used as the limit for a heavier application.
Close pages and contexts when their assigned work is complete. Long-lived unused contexts retain storage and resources, and they make route ownership harder to audit. A worker should be able to list its active assignments without including abandoned contexts from earlier jobs.
Use the same shutdown policy for normal completion and rollout changes. Let the application finish or stop according to its documented rules, close the context, and create a new one with the next assignment. This keeps the boundary between route revisions clear.
Maintaining the deployment
Review proxy assignments after browser, profile, application, or network changes. Retire routes that no longer have an active purpose or owner. Test rollback material while the corresponding credentials and inventory are still valid.
Use the per-context proxy documentation for maintained configuration details and the proxy geolocation alignment guide for regional behavior. Keeping operational templates linked to those guides avoids relying on an old article example after supported options change.
Keeping contexts aligned
Per-context proxy provides complete network isolation for each BrowserContext within a single browser instance. Each context routes through its own proxy, receives an independent public IP, and gets geographic metadata (timezone, locale, language) automatically derived from the proxy's exit location. Combined with per-context fingerprint profiles, every context operates as a fully independent identity. Explore all network features on the features page, or review pricing plans for per-context proxy support.
For proxy fundamentals and protocol details, see Proxy Configuration. For runtime proxy switching within existing contexts, see Dynamic Proxy Switching. For complete multi-identity isolation, see Multi-Account Browser Isolation. For DNS and WebRTC leak protection across all contexts, combine with DNS Leak Prevention and WebRTC Leak Prevention.
Related Articles
Take BotBrowser from research to production
The guides cover the model first, then move into cross-platform validation, isolated contexts, and scale-ready browser deployment.