Camoufox with Residential Proxies: A Code-First Python Guide

How to run Camoufox, the C++-patched Firefox, behind residential proxies: current Python API, why geoip needs a sticky session, a fingerprint coherence checklist, and when Patchright or nodriver fits better.

Camoufox with Residential Proxies: A Code-First Python Guide
In this article

To run Camoufox with residential proxies, pass a Playwright-style proxy dict to Camoufox() or AsyncCamoufox(), set geoip=True so it reads timezone, locale and geolocation from the exit IP, and use a sticky session so the IP Camoufox looked up at launch is the one you browse from. Camoufox is a Firefox build with its fingerprint patches in C++, not a set of JavaScript overrides, so the work left to you is keeping the network identity and the browser identity consistent. The rest of this guide is the code, the checks behind it, and where Camoufox is the wrong tool.

What Camoufox is, and why it pairs with residential proxies

Camoufox is an open-source Firefox fork built for scraping and AI agents. You drive it with the normal Playwright API from Python (the camoufox package on PyPI) or, since September 2026, an official npm launcher. Its README says data is "intercepted at the C++ implementation level", so a page that inspects navigator, screen, WebGL or fonts sees native getters and not patched JavaScript functions.

That is a different approach from the Chromium-side tools. Patchright is a patched Playwright driver that leaves Chrome itself alone and stops the driver from doing detectable things over the Chrome DevTools Protocol (CDP), such as calling Runtime.enable. nodriver drops WebDriver entirely and talks CDP to a stock Chrome. Both make an automated Chrome look like Chrome. Camoufox makes an automated Firefox look like a range of Firefox installs, and Playwright controls it over Juggler, Firefox's own automation protocol, not CDP. The Camoufox patches give Juggler an isolated copy of the page, so Playwright's helpers (for example window.__playwright__binding__) are never visible to page scripts.

Residential proxies come in because Camoufox can't hide the IP. Every launch generates a new device identity: OS, screen, GPU, fonts, voices, language. If that identity says "home desktop in Germany" while the connection comes from a datacenter range in Virginia, the fingerprint work counts for nothing. The README says Camoufox "is intended to be used with rotating proxies (preferably residential IPs)".

Install Camoufox and fetch the browser

Installation is two steps: the Python package, then the patched Firefox binary, which is downloaded separately.

pip install -U "camoufox[geoip]"
python -m camoufox fetch

The [geoip] extra adds the geoip2 reader, and Camoufox downloads a geolocation dataset to go with it. The install docs call it optional but "heavily recommended if you are using proxies", and geoip=True won't run without it. Two version notes that matter when you pin dependencies:

  • As of early October 2026 the current stable release on PyPI is camoufox 0.5.6 (6 September 2026), built against Firefox 152. Builds based on Firefox 156 were being published as GitHub prereleases and 0.5.7b* betas at the end of September. Use stable unless you need a specific fix.
  • The package needs Python 3.10 or newer, and its pyproject.toml pins playwright<1.63 because the server mode imports a private Playwright module. If something else in your project wants the newest Playwright, expect a conflict.

The Python API: sync, async and the options that matter

Camoufox wraps Playwright's Firefox launcher, so once you have the browser object every Playwright call works as usual. The sync and async entry points are context managers:

from camoufox.sync_api import Camoufox

with Camoufox(headless=True) as browser:
    page = browser.new_page()
    page.goto("https://example.com")
    print(page.title())
import asyncio
from camoufox.async_api import AsyncCamoufox

async def main():
    async with AsyncCamoufox(headless=True) as browser:
        page = await browser.new_page()
        await page.goto("https://example.com")
        print(await page.title())

asyncio.run(main())

These keyword arguments matter most for proxied work. The descriptions come from the usage docs and the launch_options() docstring in the pythonlib source:

  • proxy: a Playwright proxy dict (server, username, password). If geoip=True, Camoufox also sends one request through this proxy at launch to learn the exit IP.
  • geoip: True to look the exit IP up automatically, or a literal IP string. From that IP Camoufox sets latitude/longitude, timezone, country, locale and the WebRTC IP. geoip_db chooses the database. On 30 September 2026 the main branch made "GeoIP AIO" the default and deprecated GeoLite2, so check which one your installed version uses.
  • locale: an explicit locale such as "en-US", a country code such as "US" (Camoufox then picks a language from that country's distribution of speakers), or a list. The first entry drives the Intl API.
  • humanize: True, or a float giving the maximum cursor-movement time in seconds. Stable 0.5.6 still draws Bézier curves. The main branch switched on 24 September 2026 to replaying recorded human mouse paths from the Cursory dataset, so you only get that with the Firefox 156 prereleases or later builds.
  • headless: False by default. True uses Firefox headless, which Camoufox patches to look like a normal window. "virtual" runs the browser headful inside an Xvfb virtual display. That option is Linux only and needs the xvfb package installed.
  • os: limits the generated identity to "windows", "macos", "linux" or a list of them.
  • block_webrtc, block_images, block_webgl: toggles. The project's own warnings say blocking WebGL or images has been reported to cause detection on major WAFs.

The virtual display page gives the reasoning for "virtual": Camoufox patches headless mode, but "running in headless mode may still be detectable in the future". On a Linux server, Xvfb costs a little extra memory and removes a whole class of headless tells. That's usually worth it.

A working example: country-targeted, sticky, coherent

The example below opens a multi-step session through the ProxyHat gateway with a German exit, lets Camoufox derive timezone and locale from the exit IP, and checks that the identity holds together before it does any real work. Country and session both go in the proxy username.

import asyncio
import secrets
from camoufox.async_api import AsyncCamoufox

PROXY_USER = "USERNAME"
PROXY_PASS = "PASSWORD"


def proxy_for(country: str, session_id: str) -> dict:
    # Sticky session + country target, both encoded in the username
    return {
        "server": "http://gate.proxyhat.com:8080",
        "username": f"{PROXY_USER}-country-{country}-sid-{session_id}-ttl-10m",
        "password": PROXY_PASS,
    }


COHERENCE_JS = """() => ({
    tz: Intl.DateTimeFormat().resolvedOptions().timeZone,
    offsetMin: new Date().getTimezoneOffset(),
    lang: navigator.language,
    langs: navigator.languages,
    platform: navigator.platform,
})"""


async def run(country: str = "DE") -> None:
    session_id = secrets.token_hex(4)  # one IP for the whole flow
    async with AsyncCamoufox(
        proxy=proxy_for(country, session_id),
        geoip=True,               # timezone/locale/geo/WebRTC from exit IP
        humanize=True,            # humanized cursor movement
        headless="virtual",       # Linux + Xvfb; use headless=True elsewhere
        os=["windows", "macos"],
    ) as browser:
        page = await browser.new_page()

        # 1. What does the outside world see?
        await page.goto("https://ipinfo.io/json")
        exit_info = await page.evaluate("() => JSON.parse(document.body.innerText)")

        # 2. What does the browser claim?
        claimed = await page.evaluate(COHERENCE_JS)

        print("exit:", exit_info.get("ip"), exit_info.get("country"), exit_info.get("timezone"))
        print("browser:", claimed)
        if exit_info.get("timezone") != claimed["tz"]:
            raise RuntimeError("timezone does not match exit IP; abort before real traffic")

        # 3. Real multi-step work on the same IP
        await page.goto("https://example.com/", wait_until="domcontentloaded")
        # ... log in, paginate, submit, all on the same session_id


asyncio.run(run("DE"))

Why it is built this way:

  • The sticky session is essential with geoip=True. The lookup happens once, when the browser launches: Camoufox's public_ip() sends a request through your proxy to a public IP echo service and builds the identity from the answer. If the username has no session, a rotating gateway can give the next connection a different residential IP. In a country with one timezone that rarely shows. In the US, Russia, Brazil or Australia the second IP can be several timezones away from the one the browser reports. A sticky session makes the lookup IP and the browsing IP the same (more in our sticky vs rotating sessions guide).
  • One session ID per browser, not per page. Rotate by closing the browser and launching a new one with a new session_id. That gives you a new IP and a new fingerprint together, which is what a different visitor looks like.
  • The ipinfo check is a cheap tripwire. Any IP-geolocation endpoint will do. The point is to compare what the network says with what the browser says before you spend requests on the target. Geolocation databases disagree at the edges, so treat a mismatch as a reason to relaunch, not as proof something is broken.
  • Prefer the HTTP endpoint. Playwright's network docs document username/password only for HTTP(S) proxies. SOCKS5 (gate.proxyhat.com:1080) is supported as a proxy type, but authenticated SOCKS through a browser is where setups tend to break. HTTP vs SOCKS5 covers the wider trade-offs.

Fingerprint coherence checklist

Coherence means exit IP country, timezone, language, WebRTC address, OS, fonts and GPU all describe one plausible machine in one place. Camoufox handles the browser side; most failures come from settings you override. Check these before scaling up:

  1. Exit IP country ↔ timezone. Check both Intl.DateTimeFormat().resolvedOptions().timeZone and Date.getTimezoneOffset(). Detection scripts compare the IP's geolocated timezone with the browser's, and a mismatch is one of the oldest proxy tells. With geoip=True on a sticky session they match by construction.
  2. Exit IP country ↔ language. navigator.language, navigator.languages and the Accept-Language header should fit the country. Camoufox keeps the header and navigator in sync for you. If you pass locale=, pass a full locale with a region ("de-DE", not "de"): the project warns that a missing region gets generated for you and "can cause suspicion if your IP does not match your locale region".
  3. WebRTC ↔ exit IP. WebRTC can expose your real local or public address and bypass the proxy. With geoip enabled, Camoufox spoofs the WebRTC IPv4 at the protocol level to the exit IP and turns off IPv6 DNS. Leave WebRTC enabled and spoofed rather than blocked, because a desktop Firefox with no WebRTC at all is unusual.
  4. Geolocation API ↔ IP. Camoufox fills latitude and longitude from the GeoIP lookup and sets the permission pref, so navigator.permissions.query({name: "geolocation"}) reports "granted" consistently with the position it hands out. Don't set geolocation in config by hand. The library warns that it "can lead to detection if your target geolocation does not match your IP".
  5. OS ↔ fonts ↔ GPU. The generator draws from a statistical model of real traffic, and a coherence check rejects impossible combinations, such as a Windows user agent with an Apple M1 GPU. Avoid custom_fonts_only=True, and only pass webgl_config pairs the model has seen on that OS (anything else raises ValueError).
  6. User-Agent: leave it alone. Setting User-Agent or navigator fields manually is the quickest way to make the network and the JavaScript disagree. The library warns against both.
  7. IP type ↔ claimed device. A generated consumer desktop on a hosting-provider ASN is an incoherent pair no matter how good the fingerprint is. This is where residential (or ISP) IPs justify their cost. Our browser fingerprinting explainer covers how these signals are combined server-side.
  8. One identity per session. Cookies, IP and fingerprint should all change together or not at all. A returning cookie on a new IP with a new GPU looks less like a person than either a stable session or a clean new one.

Camoufox vs Patchright vs nodriver: which to pick

Pick by engine first. If your target mostly sees Chrome traffic, or checks for V8 behaviour, use a Chromium tool. If you need deep fingerprint rotation without writing JavaScript patches and Firefox traffic is acceptable, use Camoufox. The table summarises the trade-offs as of October 2026.

CamoufoxPatchrightnodriver
EnginePatched Firefox (Gecko + SpiderMonkey)Chromium / Chrome (unpatched browser)Your installed Chrome, Chromium, Edge or Brave
Control protocolJuggler (patched, isolated from page)CDP, with Runtime.enable avoided via isolated worldsCDP directly, no WebDriver
APIPlaywright (Python, JS/TS)Playwright drop-in (Python, Node, community .NET)Own async Python API
TLS fingerprint originFirefox's own NSS stack, so a Firefox ClientHelloReal Chrome's BoringSSL, especially with channel="chrome"Real Chrome's BoringSSL
Fingerprint rotationBuilt in, generated per launch at C++ levelNone. Recommends real Chrome without injectionNone built in
Maintenance (Oct 2026)Active again. Stable 0.5.6 in Sep 2026, but no stable releases between March 2025 and January 2026Tracks Playwright. v1.63.0 driver released Sep 2026Last PyPI release 0.50.3, May 2026
Typical failure modesFirefox-specific scrutiny, engine checks that only Chrome passes, fingerprint inconsistencies that vendors find and add to their checksConsole API disabled, Chromium-only, generic headless and behaviour signals still applyMain-world evaluation is observable, no fingerprint management, smaller API surface than Playwright

The TLS row matters more than it looks. All three use a real browser network stack, so none has the mismatch you get when Python's requests claims to be Chrome. But Camoufox will always present a Firefox handshake. If a site's model expects Chrome-shaped TLS for most of its traffic, see our TLS fingerprinting explainer on how JA3/JA4 are used for that kind of profiling.

Honest limitations

Camoufox is good at one thing, a coherent and rotating Firefox identity with no JavaScript patching, and it comes with real costs.

Firefox is a small slice of traffic

StatCounter puts Firefox at about 6.1% of worldwide desktop browsing in September 2026, against roughly 65% for Chrome. A small population is easier to model, and any anomaly within it stands out more. Some sites challenge non-Chromium browsers more readily. You won't know whether your target does until you test it with your own traffic.

It can't impersonate Chrome

The README says it directly: Camoufox "does not fully support injecting Chromium fingerprints", and some WAFs "test for Spidermonkey engine behavior, which is impossible to spoof". If the site insists on Chrome, no Camoufox configuration will get you there.

Maintenance has been uneven

The release history shows the gap. The last stable build before the pause was v135.0.1-beta.24 on 15 March 2025. The original author paused work, and apart from occasional community commits nothing shipped until a Firefox 146 beta in January 2026. The README then named Clover Labs as the official maintainer. Since then the project has moved quickly: a Firefox 150 build in May, Firefox 152 stable builds from July, an official TypeScript launcher in September and Firefox 156 prereleases by month end. The README still carries the warning "This project is under development. It may not be suitable for stable production use". Pin versions, and test an upgrade before rolling it out.

Fingerprints are a moving target

The project says openly that anti-bot vendors "test Camoufox over and over again to find even 1 unique inconsistency" and then add a check for it. The humanized cursor "may still be detected with sophisticated enough analysis". C++-level spoofing removes a whole category of tells, but it doesn't make you undetectable for good.

Resource cost

The project claims its debloated build runs at around 200 MB and uses less memory than stock Firefox. That's the project's figure, not ours, and your pages will add to it. It's still a full browser per identity. Plan for one browser per concurrent session rather than many contexts sharing a fingerprint, and use plain HTTP clients for endpoints that don't need JavaScript.

What Camoufox does not solve

  • IP reputation. A flagged residential IP is still flagged. Test exits before you use them, for example with our free proxy checker.
  • Rate limits and behaviour. Hundreds of identical paths a minute look automated whatever the fingerprint.
  • CAPTCHAs, logins and access rules. Camoufox doesn't give you permission to collect anything. Respect robots.txt and the site's terms, stay away from data behind authentication you're not entitled to, and treat personal data as GDPR/CCPA-regulated even when it's publicly visible.

A sensible rollout

Start with one country and one sticky session. Log the coherence check output for every launch, and compare block rates against a baseline with Patchright on the same target before you commit to one engine. If Camoufox wins, scale by adding browsers with fresh session IDs, not by sharing one browser across sessions. If it loses on a site that clearly prefers Chrome, the table above tells you where to go next. The residential gateway works the same with any of the three tools. Country codes and plan options are on the locations and residential proxy pages.

Frequently asked questions

How do I use a proxy with Camoufox?

Pass a Playwright-style dict to Camoufox() or AsyncCamoufox(): proxy={'server': 'http://host:port', 'username': '...', 'password': '...'}. Add geoip=True (requires pip install "camoufox[geoip]") so timezone, locale, geolocation and the WebRTC IP are derived from the proxy's exit IP.

Why does Camoufox need geoip=True with a proxy?

Without it, the browser's timezone, language and geolocation reflect defaults rather than the exit IP, and a mismatch between IP location and browser timezone is a classic proxy tell. Camoufox even prints a warning when a proxy is used without geoip. The lookup runs once at launch through the proxy, so pair it with a sticky session.

Is Camoufox still maintained in 2026?

Yes, but with a gap. No stable releases shipped between March 2025 and January 2026; since then, with Clover Labs named as official maintainer, it has released Firefox 150 and 152-based builds, an official npm launcher and, by late September 2026, Firefox 156 prereleases. The README still labels it as under development.

Camoufox or Patchright: which is less detectable?

Neither universally. Patchright runs real Chrome and avoids CDP leaks, so it blends into the dominant Chrome population and passes engine checks Firefox can't. Camoufox rotates full fingerprints at the C++ level without JavaScript patches, but always looks like Firefox, about 6% of desktop traffic. Test both against your actual target.

Can Camoufox run headless on a Linux server?

Yes. headless=True uses Firefox's headless mode, which Camoufox patches to look like a normal window, and headless="virtual" runs the browser headfully inside an Xvfb virtual display. The virtual option is Linux-only and needs the xvfb package installed; the docs recommend it because headless mode may become detectable.

Verify your proxy setup in seconds

Free proxy checker — confirm your IPs are fast, anonymous and unblocked.

Check proxies free
← Back to Blog