Back to Blog
Deployment

Scaling Browser Contexts: Run 100+ Fingerprint Identities on a Single Machine

How to run over 100 concurrent browser contexts with independent fingerprints using Per-Context Fingerprint architecture. Includes benchmark data, Puppeteer examples, and production optimization tips.

Introduction

Large-scale browser automation comes with a fundamental resource problem. Every fingerprint identity traditionally requires its own browser process, and each Chromium process brings a GPU process, a network process, utility processes, and renderer processes. At 50 concurrent identities, that means 50 GPU processes, 50 network processes, and hundreds of total OS processes competing for memory, CPU, and file descriptors.

This works fine at small scale. At 10 identities, a modern server handles the load without much thought. But at 50, 100, or 200 concurrent identities, the multi-instance approach hits hard limits: memory exhaustion, process table pressure, and slow startup times that delay the entire pipeline.

BotBrowser's Per-Context Fingerprint architecture solves this by running multiple fingerprint identities within a single browser instance. One browser process, one GPU process, one network process, serving dozens or hundreds of independent contexts. Each context gets its own fingerprint, proxy, timezone, locale, and storage, but the expensive infrastructure processes are shared.

This article covers the architecture, benchmark data, configuration examples, and production optimization techniques for running 100+ browser contexts on a single machine.

Privacy Impact: Why Multiple Independent Identities Matter

When running multiple browser sessions, each session must present a fully independent identity. If two sessions share any fingerprint signal, they can be correlated. Canvas hashes, WebGL renderer strings, audio fingerprints, screen dimensions, and navigator properties all contribute to tracking. A single shared signal across sessions creates a linkage point.

True identity independence requires:

  • Unique fingerprint signals per session: Different Canvas output, WebGL parameters, audio characteristics, and navigator properties
  • Independent network paths: Each session routes through a different proxy IP
  • Consistent geographic metadata: Timezone, locale, and language aligned with each session's network identity
  • Isolated storage: Separate cookies, localStorage, and IndexedDB per session
  • No cross-session leakage: One session cannot detect or influence another

At scale, maintaining this independence becomes both a privacy requirement and a technical challenge. The architecture you choose directly affects whether isolation holds under load.

Technical Background

The Multi-Instance Approach

The traditional way to run N fingerprint identities is to launch N separate browser processes, each with its own profile:

# Instance 1
chrome --bot-profile=/profiles/profile-1.enc --user-data-dir=/tmp/session-1
# Instance 2
chrome --bot-profile=/profiles/profile-2.enc --user-data-dir=/tmp/session-2
# ...
# Instance 50
chrome --bot-profile=/profiles/profile-50.enc --user-data-dir=/tmp/session-50

Each instance spawns its own set of processes:

Process TypePer Instance50 Instances
Browser process150
GPU process150
Network process150
Utility processes1-350-150
Renderer processes1+50+
Total4-6200-300+

Each browser process loads shared libraries, initializes V8, sets up IPC channels, and spawns its GPU and network processes independently. The GPU process duplicates shader caches and command buffers. The network process duplicates connection pools and DNS caches. None of this can be shared across instances.

The Per-Context Fingerprint Approach

Per-Context Fingerprint (ENT Tier 3) takes a different path. A single browser instance creates multiple BrowserContexts, and each context is assigned its own complete fingerprint bundle through the BotBrowser.setBrowserContextFlags CDP command.

The browser's shared processes become fingerprint-aware:

Shared ProcessPer-Context Behavior
GPU processCanvas/WebGL/WebGPU noise applied per context
Network processProxy routing and IP detection per context
Audio serviceAudioContext noise seed per context
Browser processTimezone, locale, screen metrics per context

Each context operates with independent:

  • Profile file (via --bot-profile)
  • User-Agent and Client Hints
  • Device model and platform
  • Screen resolution and color depth
  • Timezone, locale, and languages
  • Canvas/WebGL/Audio noise seeds
  • Proxy configuration and public IP
Multi-Instance vs Per-Context Architecture Multi-Instance (Traditional) Instance 1 Browser Process GPU Process Network Process Utility Processes Renderer Profile A Instance 2 Browser Process GPU Process Network Process Utility Processes Renderer Profile B Each instance duplicates all processes 492 processes at 50 profiles Per-Context (BotBrowser) Single Browser Instance Shared: Browser + GPU + Network + Utility (1 set) Initialized once, reused by all contexts Context A Profile A Context B Profile B Context N Profile N Each context: own fingerprint, proxy, locale, storage 210 processes at 50 profiles At 50 Concurrent Profiles Memory: 40,218 MB vs 28,553 MB (29% saved) Processes: 492 vs 210 (57% fewer) Creation Time: 57.9s vs 28.9s (2.0x faster) Fingerprint Isolation: 10/10 unique hashes in both approaches

The key insight: renderer processes scale with page count in both approaches. The savings come from sharing the GPU, network, browser, and utility processes across all contexts. These shared processes are initialized once and reused, eliminating the duplication overhead.

Benchmark Data

All benchmarks were run on macOS (Apple M4 Max, 16 cores, 64 GB RAM) in headless mode. For full methodology and reproduction scripts, see BENCHMARK.md.

Resource Usage at Scale

ScaleMulti-Instance MemoryPer-Context MemorySavingsMI ProcessesPC ProcessesMI Create TimePC Create TimeSpeedup
116,055 MB14,022 MB13%1401361,667ms627ms2.7x
1023,345 MB19,586 MB16%21215011,434ms4,854ms2.4x
2530,133 MB23,781 MB21%32017428,205ms14,415ms2.0x
5040,218 MB28,553 MB29%49221057,891ms28,946ms2.0x

Per-Context memory savings increase with scale because the shared browser, GPU, and network processes are amortized across more contexts.

Canvas Fingerprint Isolation

Each context receives a unique noise seed, producing distinct canvas fingerprints. This was verified across all scale levels:

ArchitectureScaleUnique HashesStatus
Multi-Instance10/25/5010/10PASS
Per-Context10/25/5010/10PASS

Per-Context provides the same fingerprint isolation as running separate browser instances.

Performance Overhead

BotBrowser's fingerprint protection adds near-zero overhead to browser performance:

BenchmarkStock ChromeBotBrowserDifference
Speedometer 3.0 (headless)42.8 (+-0.31)42.7 (+-0.25)-0.2%
Speedometer 3.0 (headed)41.8 (+-0.21)42.1 (+-0.17)+0.7%

Canvas, WebGL, Navigator, Screen, and Font APIs all show identical latency with or without a fingerprint profile loaded.

Context Lifecycle Performance

Continuous create/destroy cycle test (200 iterations):

MetricValue
Context creation (median)278ms
Context creation (p95)369ms
Context destruction (median)7.9ms
Context destruction (p95)16ms
Memory trend (200 cycles)Stable, no persistent growth

Context creation is lightweight and destruction is near-instant. Memory remains stable over 200 create/destroy cycles with no persistent leaks observed.

Configuration and Usage

Puppeteer: Multiple Contexts with Per-Context Fingerprints

The core workflow is: create a browser context, assign fingerprint flags via CDP, then create pages within that context.

const puppeteer = require('puppeteer-core');

async function main() {
  const browser = await puppeteer.launch({
    executablePath: '/path/to/botbrowser/chrome',
    args: [
      '--bot-profile=/profiles/base-profile.enc',
      '--no-sandbox',
    ],
    headless: true,
    defaultViewport: null,
  });

  // Browser-level CDP session (required for BotBrowser.* commands)
  const client = await browser.target().createCDPSession();

  // Profile list for different identities
  const profiles = [
    {
      profile: '/profiles/windows-us.enc',
      timezone: 'America/New_York',
      locale: 'en-US',
      languages: 'en-US,en',
    },
    {
      profile: '/profiles/macos-uk.enc',
      timezone: 'Europe/London',
      locale: 'en-GB',
      languages: 'en-GB,en',
    },
    {
      profile: '/profiles/android-jp.enc',
      timezone: 'Asia/Tokyo',
      locale: 'ja-JP',
      languages: 'ja-JP,en-US',
    },
  ];

  const contexts = [];

  for (const p of profiles) {
    // 1. Create browser context
    const context = await browser.createBrowserContext();

    // 2. Set per-context fingerprint flags BEFORE creating any page
    await client.send('BotBrowser.setBrowserContextFlags', {
      browserContextId: context._contextId,
      botbrowserFlags: [
        `--bot-profile=${p.profile}`,
        `--bot-config-timezone=${p.timezone}`,
        `--bot-config-locale=${p.locale}`,
        `--bot-config-languages=${p.languages}`,
      ],
    });

    // 3. NOW create the page
    const page = await context.newPage();
    contexts.push({ context, page, config: p });
  }

  // All contexts run simultaneously with independent fingerprints
  await Promise.all(
    contexts.map(({ page }) => page.goto('https://example.com'))
  );

  // Clean up
  for (const { context } of contexts) {
    await context.close();
  }

  await browser.close();
}

main();

CDP Command: BotBrowser.setBrowserContextFlags

The BotBrowser.setBrowserContextFlags command assigns fingerprint configuration to a specific BrowserContext. It must be called on a browser-level CDP session and before any page is created in that context.

await client.send('BotBrowser.setBrowserContextFlags', {
  browserContextId: context._contextId,
  botbrowserFlags: [
    '--bot-profile=/path/to/profile.enc',
    '--bot-config-timezone=America/Chicago',
    '--bot-config-languages=en-US',
    '--bot-config-locale=en-US',
    '--proxy-server=socks5://user:pass@proxy.example.com:1080',
    '--proxy-ip=203.0.113.1',
  ],
});

Alternatively, pass flags when creating the context via Target.createBrowserContext:

const { browserContextId } = await client.send('Target.createBrowserContext', {
  botbrowserFlags: [
    '--bot-profile=/path/to/profile.enc',
    '--bot-config-timezone=Europe/Berlin',
    '--bot-config-languages=de-DE,en-US',
  ],
});

Important: Call Order

The correct sequence is critical:

  1. createBrowserContext - Create the context
  2. BotBrowser.setBrowserContextFlags - Assign fingerprint and proxy flags
  3. newPage - Create pages within the configured context

If a page is created before setBrowserContextFlags, the renderer process has already started and the flags will not take effect for that renderer.

Memory Management Tips

When running many contexts, memory management becomes important:

// Close contexts and pages when done
await page.close();
await context.close();

// Force garbage collection between batches (if --expose-gc is enabled)
if (global.gc) global.gc();

Practical guidelines:

  • Close contexts as soon as their work is complete. Each open context with a page consumes renderer memory.
  • Monitor memory usage with process.memoryUsage() and OS-level tools. Set alerts at 80% of available RAM.
  • Use batching: if you need 200 identities, run them in batches of 50, closing each batch before starting the next.
  • Each context with one page typically uses 200-500 MB depending on page complexity. Plan server memory accordingly.

Production Optimization Flags

These flags help with high-density deployments:

chrome \
  --bot-profile=/profiles/base.enc \
  --headless \
  --no-sandbox \
  --disable-dev-shm-usage \
  --disable-gpu \
  --disable-software-rasterizer \
  --disable-extensions \
  --disable-background-networking \
  --disable-default-apps \
  --disable-sync \
  --disable-translate \
  --no-first-run \
  --no-zygote \
  --single-process

For Docker deployments, ensure sufficient shared memory:

docker run --shm-size=4g ...

Or use --disable-dev-shm-usage to write shared memory to /tmp instead.

March 2026 Improvement: High-Concurrency Stability

The March 2026 release (Chromium 146.0.7680.165) includes a significant improvement for high-concurrency workloads: 100+ concurrent browser contexts now run without crashes or memory corruption.

Previous versions could encounter stability issues when running very large numbers of contexts simultaneously. The underlying causes included race conditions in shared process resource allocation and memory management under extreme concurrency. These have been resolved.

Additionally, per-context fingerprint initialization latency has been reduced, improving throughput for workloads that create and destroy contexts frequently.

This means production deployments can now confidently target 100+ concurrent contexts on appropriately sized hardware without worrying about process crashes or data corruption between contexts.

Per-Context Proxy Integration

Per-Context Fingerprint works naturally with per-context proxy configuration. Each context can route through its own proxy, and BotBrowser auto-derives geographic metadata (timezone, locale, language) from the proxy IP.

// Context with proxy configured via botbrowserFlags
const ctx = await browser.createBrowserContext();
await client.send('BotBrowser.setBrowserContextFlags', {
  browserContextId: ctx._contextId,
  botbrowserFlags: [
    '--bot-profile=/profiles/profile.enc',
    '--proxy-server=socks5://user:pass@us-proxy.example.com:1080',
    '--proxy-ip=203.0.113.1',
    '--proxy-bypass-list=localhost;127.0.0.1',
  ],
});
const page = await ctx.newPage();

When --proxy-ip is provided, BotBrowser skips the IP lookup step and derives geographic settings directly from the known IP. This eliminates network round-trips during context creation, which is particularly valuable at scale.

Supported proxy flags per context: --proxy-server, --proxy-ip, --proxy-bypass-list, --proxy-bypass-rgx.

For runtime proxy switching without restarting a context, see the Dynamic Proxy Switching guide.

Scaling Guidelines

Hardware Sizing

Based on the benchmark data, approximate memory requirements per context:

Page ComplexityMemory per Context50 Contexts100 Contexts
Minimal (about:blank)~100 MB~5 GB + shared~10 GB + shared
Typical web page200-400 MB~10-20 GB + shared~20-40 GB + shared
Heavy SPA400-800 MB~20-40 GB + shared~40-80 GB + shared

The "shared" overhead (browser, GPU, network, utility processes) is roughly 2-4 GB regardless of context count.

Batching Strategy

For workloads requiring more identities than a single machine can hold simultaneously:

const BATCH_SIZE = 50;
const profiles = loadAllProfiles(); // e.g., 500 profiles

for (let i = 0; i < profiles.length; i += BATCH_SIZE) {
  const batch = profiles.slice(i, i + BATCH_SIZE);

  // Create contexts for this batch
  const contexts = await Promise.all(
    batch.map((profile) => createContextWithProfile(client, browser, profile))
  );

  // Run workload
  await Promise.all(
    contexts.map(({ page }) => runWorkload(page))
  );

  // Clean up before next batch
  await Promise.all(
    contexts.map(({ context }) => context.close())
  );
}

Monitoring

Track these metrics in production:

  • Process count: Should remain relatively stable. A growing process count indicates contexts are not being properly closed.
  • RSS memory per context: Monitor for memory leaks in long-running contexts.
  • Context creation time: Should remain under 500ms. Increasing times suggest resource pressure.
  • Context destruction time: Should remain under 20ms. Slow destruction may indicate pending operations.

FAQ

What tier is Per-Context Fingerprint?

Per-Context Fingerprint is an ENT Tier 3 feature. It requires an enterprise license.

Does Per-Context work with Playwright?

Yes. Use browser.newBrowserCDPSession() in Playwright to get a browser-level CDP session, then call BotBrowser.setBrowserContextFlags the same way as with Puppeteer. Playwright's native browser.newContext() with proxy settings also works for the network layer.

Can I mix different platform profiles in the same browser instance?

Yes. Each context can load a completely different profile. You can run a Windows profile in Context A, a macOS profile in Context B, and an Android profile in Context C, all within the same browser instance.

Is fingerprint isolation between contexts as strong as between separate instances?

The fingerprint isolation is equivalent. Each context produces unique Canvas hashes, WebGL output, audio fingerprints, and navigator properties. The benchmark data confirms 10/10 unique hashes across all scale levels for both approaches.

What happens if I create a page before calling setBrowserContextFlags?

The renderer process starts with the browser's base profile. The per-context flags will not apply to that renderer. Always call setBrowserContextFlags before newPage.

How many contexts can I run on a single machine?

This depends on your hardware and the complexity of pages being loaded. On a 64 GB server, 50-100 contexts with typical web pages is realistic. The March 2026 update ensures stability at 100+ contexts without crashes.

Does Worker inheritance work with Per-Context?

Yes. Dedicated Workers, Shared Workers, and Service Workers created within a context automatically inherit that context's fingerprint configuration. No additional setup is needed.

Can I switch a context's proxy at runtime?

Yes, using BotBrowser.setBrowserContextProxy (ENT Tier 3). This allows proxy changes without destroying and recreating the context.

Summary

Per-Context Fingerprint changes the economics of large-scale browser automation. Instead of paying the full process overhead for each fingerprint identity, you share the expensive infrastructure processes across all contexts while maintaining complete fingerprint isolation.

The numbers at 50 concurrent profiles:

  • 29% less memory (28,553 MB vs 40,218 MB)
  • 57% fewer processes (210 vs 492)
  • 2x faster creation (28.9s vs 57.9s)
  • 100% fingerprint isolation verified (10/10 unique hashes at all scale levels)

With the March 2026 stability improvements, production deployments can target 100+ concurrent contexts on appropriately sized hardware. Combined with per-context proxy configuration, each context presents a fully independent identity: unique fingerprint, unique IP, consistent geographic metadata, and isolated storage.

For implementation details, see the Per-Context Fingerprint documentation and benchmark reproduction scripts.

#scaling#per-context#deployment#performance#production#fingerprint isolation#browser contexts