Skip to main content

Camoufox Connector

PyPI version Python 3.9+ License: MIT

WebSocket bridge for multi-language Playwright access to Camoufox anti-detect browser

Connect to Camoufox from any programming language that has Playwright bindings - Node.js, Go, Java, .NET, Python, and more.

Architecture

flowchart TB
    subgraph clients["Client Applications"]
        NodeJS["Node.js<br/>Playwright"]
        Go["Go<br/>Playwright"]
        Java["Java<br/>Playwright"]
        Python["Python<br/>Playwright"]
        Other["Other Languages<br/>Playwright"]
    end
    
    subgraph connector["Camoufox Connector"]
        API["HTTP API<br/>:8080<br/>GET /next"]
        LB["Round-Robin<br/>Load Balancer"]
        
        subgraph pool["Browser Pool"]
            B1["Camoufox 1<br/>WS :9222<br/>Fingerprint A"]
            B2["Camoufox 2<br/>WS :9223<br/>Fingerprint B"]
            B3["Camoufox N<br/>WS :922X<br/>Fingerprint N"]
        end
        note1["Note: WebSocket ports<br/>are dynamically assigned"]
    end
    
    NodeJS -->|"WebSocket"| API
    Go -->|"WebSocket"| API
    Java -->|"WebSocket"| API
    Python -->|"WebSocket"| API
    Other -->|"WebSocket"| API
    
    API -->|"Round-Robin"| LB
    LB -->|"Distribute"| B1
    LB -->|"Distribute"| B2
    LB -->|"Distribute"| B3
    
    style clients fill:#e1f5ff
    style connector fill:#fff4e1
    style pool fill:#e8f5e9
    style API fill:#ffebee
    style LB fill:#f3e5f5

How it works:

  1. Clients (Node.js, Go, Python, etc.) connect via Playwright
  2. HTTP API provides endpoints via GET /next (round-robin)
  3. Load Balancer distributes connections across browser instances
  4. Browser Pool maintains multiple Camoufox instances with unique fingerprints
  5. Each client gets a WebSocket endpoint to connect directly to a browser
  6. WebSocket ports are dynamically assigned by camoufox (use host network mode in Docker for pool mode)

Sponsored by Scrappey

Tired of getting blocked while scraping the web?

Rotating proxies, Anti-Bot technology and headless browsers to CAPTCHAs. It's never been this easy using our simple-to-use API.

👉 Try Scrappey for free


Why Camoufox Connector?

Camoufox is a powerful anti-detect browser based on Firefox, but its Python-only interface limits accessibility. Camoufox Connector solves this by:

  • Exposing WebSocket endpoints that any Playwright client can connect to
  • Managing browser pools for high-volume scraping with fingerprint rotation
  • Providing health monitoring via HTTP API
  • Simplifying deployment with Docker support

Features

  • Multi-language support - Connect from Node.js, Go, Python, Java, .NET, or any Playwright-compatible language
  • Single & Pool modes - One persistent browser or multiple rotating browsers
  • Round-robin load balancing - Distribute connections across browser instances
  • Request priority - Reserve browsers with a priority; higher-priority requests jump the queue and can (optionally) preempt lower-priority ones
  • Proxy pool - Assign a different proxy per instance, with runtime blacklist + rotation
  • Fingerprint rotation - Each browser instance has a unique fingerprint
  • Health check API - Monitor browser health and statistics
  • Docker ready - Production-ready containerization
  • High performance - Async architecture optimized for concurrent connections

Quick Start

Installation

# Install from PyPI
pip install camoufox-connector

Or install from source:

# Clone the repository
git clone https://github.com/pim97/camoufox-connector.git
cd camoufox-connector

# Install with pip
pip install -e .

# Or install from PyPI (when published)
pip install camoufox-connector

Start the Server

# Single browser mode (default)
camoufox-connector

# Pool mode with 5 browsers
camoufox-connector --mode pool --pool-size 5

# With proxy
camoufox-connector --proxy http://user:pass@host:port

Language Examples

Full working examples are available for many programming languages:

Language Directory Playwright Support
Node.js examples/nodejs/ Full
TypeScript examples/typescript/ Full
Python examples/python/ Full
Go examples/go/ Full
Java examples/java/ Full
Kotlin examples/kotlin/ Full
C# (.NET) examples/csharp/ Full
Ruby examples/ruby/ API only
PHP examples/php/ API only
Rust examples/rust/ API only
cURL/Shell examples/curl/ API only

Connect from Node.js

import { firefox } from 'playwright';

// Get endpoint from the connector API
const response = await fetch('http://localhost:8080/next');
const { endpoint } = await response.json();

// Connect to Camoufox
const browser = await firefox.connect(endpoint);
const page = await browser.newPage();

await page.goto('https://example.com');
console.log(await page.title());

await browser.close();

Connect from Go

package main

import (
    "github.com/playwright-community/playwright-go"
)

func main() {
    pw, _ := playwright.Run()
    defer pw.Stop()
    
    // Get endpoint from connector API
    // endpoint := getEndpointFromAPI()
    endpoint := "ws://localhost:9222/abc123"
    
    browser, _ := pw.Firefox.Connect(endpoint)
    page, _ := browser.NewPage()
    
    page.Goto("https://example.com")
    title, _ := page.Title()
    println(title)
    
    browser.Close()
}

Connect from Python

import httpx
from playwright.async_api import async_playwright

async def main():
    # Get endpoint from connector API
    async with httpx.AsyncClient() as client:
        response = await client.get("http://localhost:8080/next")
        endpoint = response.json()["endpoint"]
    
    async with async_playwright() as p:
        browser = await p.firefox.connect(endpoint)
        page = await browser.new_page()
        
        await page.goto("https://example.com")
        print(await page.title())
        
        await browser.close()

Operating Modes

Single Mode (Default)

One browser instance with a consistent fingerprint. Ideal for:

  • Maintaining logged-in sessions
  • Sequential scraping tasks
  • Development and testing
camoufox-connector --mode single

Pool Mode

Multiple browser instances with different fingerprints, distributed via round-robin. Ideal for:

  • High-volume scraping
  • Avoiding detection through fingerprint rotation
  • Parallel processing
camoufox-connector --mode pool --pool-size 5

Note: Since each browser instance maintains its own fingerprint, use pool mode when you need fingerprint rotation between requests. Use single mode when you need session persistence.

Proxy Pool

Instead of a single proxy shared by every browser, you can supply a list of proxies. Each instance is assigned one proxy (round-robin), so a 5-browser pool can egress from 5 different IPs.

# One proxy per instance
camoufox-connector --mode pool --pool-size 3 \
  --proxies "http://user:pass@host1:port,http://user:pass@host2:port,http://user:pass@host3:port"
# Or via env (comma or newline separated); or a JSON list in --config
export CAMOUFOX_PROXIES="http://host1:port,http://host2:port"

--proxies takes precedence over --proxy. If a proxy starts failing, rotate an instance onto a different one from the pool — and optionally blacklist the bad proxy so it isn't reused:

# Relaunch instance 0 with a new proxy, blacklisting its current one
curl -X POST "http://localhost:8080/restart/0?blacklist=true"

/stats reports proxy assignments and the blacklist (credentials are masked).

Request Priority (leases)

The /acquire + /release API lets you reserve a browser with a priority. When the pool is at capacity, acquirers wait in a priority-ordered queue, so a higher-priority request is served before lower-priority ones as soon as a browser frees up. This is separate from /next (which is unchanged and never blocks).

# Reserve a browser at priority 5, waiting up to 30s for capacity
curl "http://localhost:8080/acquire?priority=5&timeout=30"
# -> {"endpoint":"ws://...","lease_id":"ab12...","instance":2,"priority":5}

# ... connect Playwright to endpoint, do your work, then:
curl -X POST "http://localhost:8080/release/ab12..."

Each instance serves --max-concurrency-per-instance leases at once (default 1, i.e. one lease = one exclusive browser).

Preemption (opt-in)

With --preemption, a higher-priority /acquire can reclaim a browser from a strictly lower-priority lease when the pool is full. The lower-priority holder is flagged preempted (visible via GET /lease/{lease_id}) and given a grace period (--preempt-grace, default 10s) to /release voluntarily; if it doesn't, the browser is restarted so the high-priority request gets a clean instance.

camoufox-connector --mode pool --pool-size 5 --preemption --preempt-grace 15

How this maps to the architecture: the connector hands out browser endpoints — clients connect to the browser directly, so it can't transparently pause and resume an opaque in-flight session. Preemption is therefore cooperative (yield within the grace window) with a hard fallback (restart). A well-behaved high-value client should poll GET /lease/{id} and release promptly when preempted is true.

HTTP API

The connector exposes an HTTP API for health monitoring and browser management.

Endpoint Method Description
/ GET Server info and version
/health GET Health check (returns 200/503)
/next GET Get next browser endpoint (round-robin)
/acquire GET/POST Acquire a priority lease on a browser (?priority=&timeout=)
/release/{lease_id} POST Release a previously acquired lease
/lease/{lease_id} GET Inspect a lease (incl. whether it's been asked to yield)
/endpoints GET List all available endpoints
/stats GET Pool statistics, lease + proxy state
/restart/{n} POST Restart browser instance N (?rotate_proxy=&blacklist=)

Example API Responses

GET /next

{
  "endpoint": "ws://localhost:9222/abc123def456"
}

GET /health

{
  "status": "healthy",
  "mode": "pool",
  "instances": [
    {"index": 0, "healthy": true, "endpoint": "ws://..."},
    {"index": 1, "healthy": true, "endpoint": "ws://..."},
    {"index": 2, "healthy": true, "endpoint": "ws://..."}
  ]
}

GET /stats

{
  "mode": "pool",
  "total_instances": 3,
  "healthy_instances": 3,
  "active_connections": 5,
  "total_connections": 142,
  "instances": [
    {"index": 0, "uptime": 3600.5, "connections": 2, "total_connections": 48},
    {"index": 1, "uptime": 3600.3, "connections": 2, "total_connections": 47},
    {"index": 2, "uptime": 3600.1, "connections": 1, "total_connections": 47}
  ]
}

Configuration

Command Line Options

Usage: camoufox-connector [OPTIONS]

Options:
  --mode {single,pool}   Operating mode (default: single)
  --pool-size N          Number of browser instances in pool mode (default: 3)
  --api-port PORT        HTTP API port (default: 8080)
  --api-host HOST        HTTP API host (default: 0.0.0.0)
  --ws-port-start PORT   Starting port for WebSocket endpoints (default: 9222)
  --headless             Run browsers in headless mode (default)
  --no-headless          Run browsers in headed mode
  --geoip                Enable GeoIP spoofing (default)
  --no-geoip             Disable GeoIP spoofing
  --humanize             Enable humanization (default)
  --no-humanize          Disable humanization
  --block-images         Block image loading
  --proxy URL            Single proxy URL (http://user:pass@host:port)
  --proxies URLS         Comma-separated proxy pool (one per instance)
  --max-concurrency-per-instance N   Max simultaneous /acquire leases per browser (default 1)
  --preemption           Let higher-priority /acquire requests preempt lower ones
  --preempt-grace SECONDS            Grace before a preempted lease is reclaimed (default 10)
  --config FILE          Load configuration from JSON file
  --debug                Enable debug logging

Environment Variables

All options can be set via CAMOUFOX_ prefixed environment variables:

export CAMOUFOX_MODE=pool
export CAMOUFOX_POOL_SIZE=5
export CAMOUFOX_HEADLESS=true
export CAMOUFOX_PROXY=http://user:pass@host:port

camoufox-connector

JSON Configuration

{
  "mode": "pool",
  "pool_size": 5,
  "headless": true,
  "geoip": true,
  "humanize": true,
  "proxy": "http://user:pass@host:port"
}
camoufox-connector --config config.json

Docker

Quick Start with Docker

# Build the image
docker build -t camoufox-connector .

# Run in single mode
docker run -p 8080:8080 -p 9222:9222 \
  --shm-size=2gb \
  -v camoufox-cache:/root/.cache/camoufox \
  camoufox-connector

# Run in pool mode (Linux: use host network for dynamic ports)
docker run --network host \
  -e CAMOUFOX_MODE=pool \
  -e CAMOUFOX_POOL_SIZE=5 \
  --shm-size=4gb \
  -v camoufox-cache:/root/.cache/camoufox \
  camoufox-connector

Note: Pool mode uses --network host because camoufox assigns WebSocket ports dynamically. On Windows/Mac, run natively or use a Linux VM.

Docker Compose

# Single mode
docker compose up

# Pool mode
docker compose --profile pool up

Custom docker-compose.yml

services:
  camoufox-single:
    build: .
    ports:
      - "8080:8080"
      - "9222:9222"
    environment:
      - CAMOUFOX_MODE=single
      - CAMOUFOX_HEADLESS=true
    shm_size: 2gb
    volumes:
      - camoufox-cache:/root/.cache/camoufox
    restart: unless-stopped

  camoufox-pool:
    build: .
    # Use host network for dynamic WebSocket port access
    network_mode: host
    environment:
      - CAMOUFOX_MODE=pool
      - CAMOUFOX_POOL_SIZE=5
      - CAMOUFOX_HEADLESS=true
    shm_size: 4gb
    volumes:
      - camoufox-cache:/root/.cache/camoufox
    restart: unless-stopped

volumes:
  camoufox-cache:
    name: camoufox-browser-cache

Note: The camoufox-cache volume persists browser binaries between container restarts, improving startup time. Pool mode requires network_mode: host on Linux to support dynamically assigned WebSocket ports.

Use Cases

High-Volume Web Scraping

// Distribute scraping across multiple fingerprints
async function scrapeUrls(urls) {
  const results = await Promise.all(urls.map(async (url) => {
    // Each request gets a different browser/fingerprint
    const { endpoint } = await fetch('http://localhost:8080/next').then(r => r.json());
    const browser = await firefox.connect(endpoint);
    
    try {
      const page = await browser.newPage();
      await page.goto(url);
      return await page.content();
    } finally {
      await browser.close();
    }
  }));
  
  return results;
}

Session Persistence

// Use a specific endpoint for session persistence
const { endpoints } = await fetch('http://localhost:8080/endpoints').then(r => r.json());
const sessionEndpoint = endpoints[0];  // Always use the same browser

// Login once
let browser = await firefox.connect(sessionEndpoint);
let page = await browser.newPage();
await page.goto('https://example.com/login');
// ... perform login
await browser.close();

// Subsequent requests use the same session
browser = await firefox.connect(sessionEndpoint);
page = await browser.newPage();
await page.goto('https://example.com/dashboard');  // Already logged in

Load Balancing with Health Checks

async function getHealthyEndpoint() {
  const health = await fetch('http://localhost:8080/health').then(r => r.json());
  
  if (health.status !== 'healthy') {
    throw new Error('No healthy browsers available');
  }
  
  const { endpoint } = await fetch('http://localhost:8080/next').then(r => r.json());
  return endpoint;
}

Performance Tips

  1. Use pool mode for parallel tasks - Each browser instance can handle multiple pages concurrently
  2. Set appropriate pool size - Rule of thumb: 1-2 browsers per CPU core
  3. Enable --block-images - Significantly speeds up page loads for text-based scraping
  4. Use --headless - Reduces memory and CPU usage
  5. Monitor with /stats - Watch connection distribution and adjust pool size accordingly

Troubleshooting

Browser fails to start

# Check if Camoufox is installed with GeoIP support
python -c "from camoufox.sync_api import Camoufox; print('OK')"

# Install with GeoIP support (required for --geoip flag)
pip install camoufox[geoip]
python -m playwright install firefox

GeoIP database error

If you see InvalidDatabaseError: Error opening database file, install camoufox with GeoIP support:

pip install camoufox[geoip]

Or disable GeoIP if you don't need it:

camoufox-connector --no-geoip

Connection refused

# Check if server is running
curl http://localhost:8080/health

# Check if browser WebSocket is accessible
curl -I ws://localhost:9222

Out of memory in Docker

# Increase shared memory (required for browsers)
docker run --shm-size=2gb camoufox-connector

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT License - see LICENSE for details.

Credits

Metadata

Release files for camoufox-connector 1.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for camoufox-connector 1.1.0
File Size Uploaded
camoufox_connector-1.1.0.tar.gz 39.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for camoufox-connector 1.1.0
File Interpreter ABI Platform
camoufox_connector-1.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 68.3 kB

Release files / camoufox_connector-1.1.0.tar.gz

Download URL camoufox_connector-1.1.0.tar.gz
Size 39.2 kB
Tags Source
SHA-256 checksum
How to use checksums
56a503630b1dabd7c36b17d1b104867ad6a2c76ace7d318b31cbf6c9b7a1e88f
BLAKE2b-256 checksum
How to use checksums
c4871103dd4e25ed652205ca8b8619977b0c9cb87372aed32dc081e5daa4a070
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 4, 2026.

Transparency log

Release files / camoufox_connector-1.1.0-py3-none-any.whl

Download URL camoufox_connector-1.1.0-py3-none-any.whl
Size 29.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
38926871c7e2e4d3a010b7781f2243cb9eb284263f992594523085b60d74504c
BLAKE2b-256 checksum
How to use checksums
73b2e9a0ea3596dfe97cd17b862a4c25bbf7aa1ac1aa3eb98a1777d47804425f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 4, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.1.0 This release

2 release files

1.0.3

2 release files

1.0.2

2 release files

1.0.1

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page