Camoufox Connector
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:
- Clients (Node.js, Go, Python, etc.) connect via Playwright
- HTTP API provides endpoints via
GET /next(round-robin) - Load Balancer distributes connections across browser instances
- Browser Pool maintains multiple Camoufox instances with unique fingerprints
- Each client gets a WebSocket endpoint to connect directly to a browser
- 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.
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 whenpreemptedistrue.
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 hostbecause 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-cachevolume persists browser binaries between container restarts, improving startup time. Pool mode requiresnetwork_mode: hoston 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
- Use pool mode for parallel tasks - Each browser instance can handle multiple pages concurrently
- Set appropriate pool size - Rule of thumb: 1-2 browsers per CPU core
- Enable
--block-images- Significantly speeds up page loads for text-based scraping - Use
--headless- Reduces memory and CPU usage - 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
- Camoufox - The anti-detect browser this project wraps
- Playwright - Browser automation framework
- node-camoufox - Inspiration for this project
Links
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)
| File | Size | Uploaded | |
|---|---|---|---|
| camoufox_connector-1.1.0.tar.gz | 39.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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 logRelease 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