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
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file camoufox_connector-1.1.0.tar.gz.
File metadata
- Download URL: camoufox_connector-1.1.0.tar.gz
- Upload date:
- Size: 39.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
56a503630b1dabd7c36b17d1b104867ad6a2c76ace7d318b31cbf6c9b7a1e88f
|
|
| MD5 |
d5a778970dff9a43c34b389f1e84f30b
|
|
| BLAKE2b-256 |
c4871103dd4e25ed652205ca8b8619977b0c9cb87372aed32dc081e5daa4a070
|
Provenance
The following attestation bundles were made for camoufox_connector-1.1.0.tar.gz:
Publisher:
publish.yml on pim97/camoufox-connector
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
camoufox_connector-1.1.0.tar.gz -
Subject digest:
56a503630b1dabd7c36b17d1b104867ad6a2c76ace7d318b31cbf6c9b7a1e88f - Sigstore transparency entry: 2070859656
- Sigstore integration time:
-
Permalink:
pim97/camoufox-connector@e3c310d4a8c6108fafab038c51efb86a764e8526 -
Branch / Tag:
refs/tags/v1.1.0 - Owner: https://github.com/pim97
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e3c310d4a8c6108fafab038c51efb86a764e8526 -
Trigger Event:
release
-
Statement type:
File details
Details for the file camoufox_connector-1.1.0-py3-none-any.whl.
File metadata
- Download URL: camoufox_connector-1.1.0-py3-none-any.whl
- Upload date:
- Size: 29.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
38926871c7e2e4d3a010b7781f2243cb9eb284263f992594523085b60d74504c
|
|
| MD5 |
a2dd15742b0714200a125aaf75793814
|
|
| BLAKE2b-256 |
73b2e9a0ea3596dfe97cd17b862a4c25bbf7aa1ac1aa3eb98a1777d47804425f
|
Provenance
The following attestation bundles were made for camoufox_connector-1.1.0-py3-none-any.whl:
Publisher:
publish.yml on pim97/camoufox-connector
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
camoufox_connector-1.1.0-py3-none-any.whl -
Subject digest:
38926871c7e2e4d3a010b7781f2243cb9eb284263f992594523085b60d74504c - Sigstore transparency entry: 2070859685
- Sigstore integration time:
-
Permalink:
pim97/camoufox-connector@e3c310d4a8c6108fafab038c51efb86a764e8526 -
Branch / Tag:
refs/tags/v1.1.0 - Owner: https://github.com/pim97
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e3c310d4a8c6108fafab038c51efb86a764e8526 -
Trigger Event:
release
-
Statement type: