Skip to content

About

FlareSolverr Go Edition - High-performance Cloudflare bypass proxy written in Go

Resources

Contributing

Security policy

Stars

23 stars

Watchers

0 watching

Forks

Latest commit

 

History

244 Commits

Folders and files

Repository files navigation

FlareSolverr Go Edition

A high-performance Cloudflare bypass proxy server written in Go. This is a drop-in replacement for the original Python FlareSolverr with significant performance improvements.

Drop-in Replacement

This project is fully API-compatible with the original FlareSolverr. You can replace your existing FlareSolverr instance without changing any client code:

  • Same API endpoints (/ and /v1)
  • Same request/response format
  • Same command names (request.get, request.post, sessions.create, etc.) plus new sessions.keepalive
  • Same default port (8191)

Just swap the Docker image or binary and you're done.

Features

  • Browser Pooling - Reuses browser instances instead of spawning new ones per request (150-250MB vs 400-700MB)
  • Direct CDP Protocol - Uses Chrome DevTools Protocol directly, bypassing Selenium overhead
  • Go Concurrency - Native goroutines for better concurrency than Python's GIL
  • Memory Management - Active memory monitoring with automatic browser recycling
  • Session Support - TTL-based session management with per-request TTL override, keepalive command, and automatic cleanup
  • Cloudflare Bypass - Solves JavaScript challenges and Turnstile CAPTCHAs
  • Anti-Fingerprinting - The same browser fingerprint on every request, stateless or session, with a per-request timezone and User-Agent
  • Per-Session Chrome Flags - Custom window size, language, timezone, and Chrome flags per session with dedicated browser instances
  • Human-Like Behavior - Bezier curve mouse movements, randomized timing, and natural scroll patterns
  • Two-Phase CDP Bypass - Bypasses Cloudflare's managed challenge loop by launching a clean Chrome without CDP for challenge resolution
  • External CAPTCHA Fallback - Pluggable provider registry with 2Captcha, CapSolver, and anti-captcha.com for Turnstile and hCaptcha
  • hCaptcha Support - Detects hCaptcha challenges, extracts sitekeys, and solves via external providers with token injection
  • Hot-Reload Selectors - Update challenge selectors via file watching or remote URL without restarts
  • Adaptive Solving - Per-domain tracking of which solving methods work best
  • Custom JS Execution - Run arbitrary JavaScript on pages after challenge solving
  • Binary Downloads - Download files as base64 through the browser (bypasses TLS fingerprinting)
  • Prometheus Metrics - /metrics endpoint compatible with Prometheus/Grafana
  • OpenAPI Docs - /docs endpoint serves the full API specification
  • CLI Dashboard - Optional split-screen TUI showing live requests and server stats (DASHBOARD_ENABLED=true)
  • Multi-Architecture - Docker images for amd64, arm64, and armv7
  • Docker Support - Production-ready Docker image with Xvfb

Quick Start

Docker (Recommended)

docker run -d -p 8191:8191 --name flaresolverr rorqualx/flaresolverr-go:latest

The image declares /tmp/rod as a VOLUME, so Chromium's per-browser user-data dirs land in an anonymous Docker volume (cleaned up by docker rm -v) instead of the container's writable layer. These dirs are removed after every browser close, so they do not accumulate on disk.

⚠️ Do not mount a tmpfs at /tmp/rod (or /tmp). It is a common suggestion for "putting browser scratch space in RAM", but it breaks Chromium launch (GitHub issue #10):

  • tmpfs defaults to noexec, so Chromium's launch helper cannot exec → permission denied.
  • A nested tmpfs at /tmp/rod breaks Chrome's process-singleton lock → Failed to get the debug url: Opening in existing browser session. This is the failure most commonly hit on Unraid.

The container works correctly with no tmpfs flag — just use the default anonymous volume. If you really want browser scratch space in RAM, mount a tmpfs with the exec option at a path that is not nested under /tmp/rod.

From Source

go build -o flaresolverr ./cmd/flaresolverr
./flaresolverr

Check Version

./flaresolverr --version

API Reference

The API accepts POST requests with JSON body at both / and /v1 endpoints. All commands return the same response format.

Endpoints

Endpoint Method Description
/ POST Main API endpoint (legacy)
/v1 POST Main API endpoint (recommended)
/health GET Health check with pool counters (no API key needed)
/stats GET Health fields plus per-domain statistics and selector reload state (API key required when enabled)
/metrics GET Prometheus-compatible metrics
/docs GET OpenAPI 3.0 specification (YAML)

Commands

request.get - Fetch a URL

Navigates to a URL, solves any Cloudflare challenges, and returns the page content.

curl -X POST http://localhost:8191/v1 \
  -H "Content-Type: application/json" \
  -d '{
    "cmd": "request.get",
    "url": "https://example.com",
    "maxTimeout": 60000
  }'

request.post - Submit a POST request

Navigates to a URL with POST data.

curl -X POST http://localhost:8191/v1 \
  -H "Content-Type: application/json" \
  -d '{
    "cmd": "request.post",
    "url": "https://example.com/login",
    "postData": "username=user&password=pass",
    "maxTimeout": 60000
  }'

sessions.create - Create a persistent session

Creates a session that persists cookies and browser state across requests.

curl -X POST http://localhost:8191/v1 \
  -H "Content-Type: application/json" \
  -d '{
    "cmd": "sessions.create",
    "session": "my-session-id",
    "session_ttl_minutes": 60
  }'

The optional session_ttl_minutes parameter overrides the global SESSION_TTL for this session (1-1440 minutes). If omitted, the server default is used.

sessions.list - List active sessions

curl -X POST http://localhost:8191/v1 \
  -H "Content-Type: application/json" \
  -d '{
    "cmd": "sessions.list"
  }'

sessions.keepalive - Refresh a session's TTL

Touches the session to prevent expiration. Optionally extends the TTL.

curl -X POST http://localhost:8191/v1 \
  -H "Content-Type: application/json" \
  -d '{
    "cmd": "sessions.keepalive",
    "session": "my-session-id",
    "keepaliveTtl": 120
  }'

If keepaliveTtl is provided (in minutes), the session's TTL is updated to that value. If omitted, the session is simply touched to reset its inactivity timer.

sessions.destroy - Destroy a session

curl -X POST http://localhost:8191/v1 \
  -H "Content-Type: application/json" \
  -d '{
    "cmd": "sessions.destroy",
    "session": "my-session-id"
  }'

Request Parameters

Parameter Type Required Description
cmd string Yes Command to execute
url string For request.* Target URL to navigate to
session string No Session ID for persistent sessions
session_ttl_minutes int No Per-session TTL override in minutes (1-1440, default: server SESSION_TTL)
maxTimeout int No Maximum timeout in milliseconds (default: 60000)
cookies array No Cookies to set before navigation
proxy object No Proxy configuration for this request
postData string For request.post URL-encoded POST data
returnOnlyCookies bool No Return only cookies, not HTML
returnScreenshot bool No Return base64 PNG screenshot
disableMedia bool No Block images, CSS, fonts to speed up loading
waitInSeconds int No Wait N seconds before returning response
contentType string No POST content type: application/json or application/x-www-form-urlencoded
headers object No Custom HTTP headers (max 50)
tabsTillVerify int No Tab presses for Turnstile keyboard navigation (0-50)
download bool No Download URL as binary, return base64 in response field
followRedirects bool No Follow HTTP redirects (default: true)
userAgent string No Override User-Agent for this request
returnRawHtml bool No Return raw HTML before JavaScript renders
executeJs string No Custom JavaScript to execute after solving
captchaSolver string No External captcha provider for this request: 2captcha, capsolver, anticaptcha or 9kw (one of the server's configured providers, unless captchaApiKey is given), or none to forbid external solving for this request
captchaApiKey string No Your own API key for captchaSolver. Used for this request only, for at most one paid solve, even when the server-side fallback is disabled. Requires captchaSolver. Never logged or stored
keepaliveTtl int No New TTL in minutes for sessions.keepalive (0 = just touch, max 1440)
cookieExtractDelay int No Seconds to wait before extracting cookies (0-30). Captures late-set JS cookies
browserFlags object No Per-session Chrome flag overrides (sessions.create only). See below
fingerprint object No Only overrides.timezone is honored; fingerprint profiles have been removed. See below

Cookie Object

{
  "name": "session_id",
  "value": "abc123",
  "domain": ".example.com",
  "path": "/",
  "secure": true,
  "httpOnly": true
}

Proxy Object

{
  "url": "http://proxy.example.com:8080",
  "username": "user",
  "password": "pass"
}

Supported proxy schemes: http, https, socks4, socks5

Browser Flags Object

Per-session Chrome flag overrides. Sessions created with custom flags get a dedicated browser (not from the pool) that is closed when the session is destroyed.

{
  "windowSize": "1280,720",
  "language": "fr-FR",
  "timezone": "Europe/Paris",
  "headless": true,
  "disableGpu": false,
  "extraArgs": ["--disable-extensions"]
}
Field Type Description
windowSize string Window dimensions as "width,height" (e.g. "1280,720")
language string Accept-Language override (e.g. "fr-FR", "de-DE")
timezone string Timezone for stealth patches (e.g. "Europe/Paris")
headless bool Override global headless setting
disableGpu bool Force software rendering
extraArgs array Additional Chrome flags (validated against security whitelist)

Security: extraArgs are validated against an allowed whitelist. Dangerous flags like --disable-web-security and --remote-debugging-port are blocked.

Example: Create a French-language session with a smaller viewport:

curl -X POST http://localhost:8191/v1 \
  -H "Content-Type: application/json" \
  -d '{
    "cmd": "sessions.create",
    "session": "my-french-session-01",
    "browserFlags": {
      "windowSize": "1280,720",
      "language": "fr-FR",
      "timezone": "Europe/Paris"
    }
  }'

Fingerprint Object

Fingerprint profiles have been removed. They never took effect on stateless requests, and every request now presents the same browser whether or not it uses a session.

The fingerprint field is still accepted, so existing clients keep working, but only one option in it does anything:

{
  "overrides": { "timezone": "Asia/Tokyo" }
}
Field Type Description
overrides.timezone string IANA timezone ID for this request, applied through the browser's own timezone emulation. Takes priority over TZ and a session's browserFlags.timezone

profile, disablePatches and every other override are ignored; the server logs one warning per request naming them. To change the User-Agent, use the top-level userAgent field: the client hints and navigator.platform follow it.

Response Format

{
  "status": "ok",
  "message": "Challenge solved successfully",
  "startTimestamp": 1704067200000,
  "endTimestamp": 1704067205000,
  "version": "1.0.0",
  "solution": {
    "url": "https://example.com/",
    "status": 200,
    "response": "<html>...</html>",
    "cookies": [
      {
        "name": "cf_clearance",
        "value": "...",
        "domain": ".example.com",
        "path": "/",
        "expires": 1704153600,
        "httpOnly": true,
        "secure": true,
        "sameSite": "None"
      }
    ],
    "userAgent": "Mozilla/5.0...",
    "screenshot": "base64...",
    "turnstile_token": "..."
  }
}

Response Fields

Field Type Description
status string "ok" or "error"
message string Human-readable status message
startTimestamp int Request start time (Unix ms)
endTimestamp int Request end time (Unix ms)
version string FlareSolverr version
solution object Solution data (on success)
sessions array List of session IDs (for sessions.list)

Solution Fields

Field Type Description
url string Final URL after redirects
status int HTTP status code
response string Page HTML content
cookies array All cookies from the page
userAgent string Browser user agent
screenshot string Base64 PNG (if requested)
turnstile_token string Cloudflare Turnstile token (if present)
localStorage object All localStorage key-value pairs (for debugging)
sessionStorage object All sessionStorage key-value pairs (for debugging)
responseHeaders object Extracted response metadata (cf-ray, etc.)
responseTruncated bool true if HTML was truncated due to 10MB size limit (optional)
cookieError string Error message if cookies could not be retrieved (optional)
responseEncoding string "base64" when download=true (optional)
executeJsResult string Result of executeJs custom JavaScript (optional)
browserVersion string Chrome major version for TLS profile matching (optional)
rateLimited bool true if rate limiting detected (optional)
suggestedDelayMs int Recommended delay before retry in ms (optional)
errorCode string Specific error code like CF_1015 (optional)
errorCategory string Error category: rate_limit, access_denied, captcha, geo_blocked (optional)

Rate Limit Detection

When the target site returns a rate limiting or access denied response, additional fields are included in the solution:

{
  "solution": {
    "url": "https://example.com/",
    "status": 403,
    "response": "<html>Access denied...</html>",
    "rateLimited": true,
    "suggestedDelayMs": 5000,
    "errorCode": "ACCESS_DENIED",
    "errorCategory": "access_denied"
  }
}

Supported Error Codes:

Code Category Description Suggested Delay
CF_1015 rate_limit Cloudflare rate limit 60s
CF_1020 access_denied Cloudflare suspicious request 30s
CF_1006-1008 access_denied Cloudflare access denied 30s
CF_1009 geo_blocked Cloudflare geo-restriction N/A
CF_1010 access_denied Browser signature rejected 30s
ACCESS_DENIED access_denied Generic access denied 5s
RATE_LIMITED rate_limit Generic rate limit 10s
TOO_MANY_REQUESTS rate_limit Too many requests 10s
HTTP_429 rate_limit HTTP 429 status 60s
HTTP_503 rate_limit Service unavailable 30s
CAPTCHA_REQUIRED captcha CAPTCHA challenge N/A

Response Headers

The following headers are included in responses for domain-level statistics:

Header Description
X-Domain-Suggested-Delay Recommended delay in ms based on domain history
X-Domain-Error-Rate Error rate (0.0-1.0) for this domain
X-Domain-Request-Count Total requests tracked for this domain

Configuration

All configuration is done via environment variables.

Server Settings

Variable Default Description
HOST 0.0.0.0 Server bind address
PORT 8191 Server port

Browser Settings

Variable Default Description
HEADLESS true Run browser in headless mode
BROWSER_PATH (auto) Path to Chrome/Chromium executable
BROWSER_POOL_SIZE 3 Number of browser instances in pool
BROWSER_POOL_TIMEOUT 30s Timeout for acquiring a browser
MAX_MEMORY_MB 4096 Memory used by the whole container before idle browsers are recycled. If the container has a memory limit below this, 90% of that limit is used instead

Session Settings

Variable Default Description
SESSION_TTL 30m Session time-to-live
SESSION_CLEANUP_INTERVAL 1m Cleanup interval for expired sessions
MAX_SESSIONS 20 Maximum concurrent sessions (about 150MB each while a page is open)

Timeout Settings

Variable Default Description
DEFAULT_TIMEOUT 60s Default request timeout
MAX_TIMEOUT 300s Maximum allowed timeout

Proxy Settings

Variable Default Description
PROXY_URL (none) Default proxy URL for all requests
PROXY_USERNAME (none) Default proxy username
PROXY_PASSWORD (none) Default proxy password
PROXY_LIST (none) Pool of egress proxies (comma/newline-separated, embedded user:pass@ ok). Enables clean-egress routing
PROXY_STRATEGY sticky-domain Egress selection: sticky-domain (same exit IP per site — keeps cf_clearance valid), round-robin, or per-request
CLEARANCE_CACHE_ENABLED true Reuse a minted cf_clearance (+UA) per domain/egress to skip repeat solves
CLEARANCE_TTL 25m Max lifetime of a cached cf_clearance

Security Settings

Variable Default Description
RATE_LIMIT_ENABLED true Enable rate limiting
RATE_LIMIT_RPM 60 Requests per minute per IP
TRUST_PROXY false Use X-Forwarded-For / X-Real-IP for rate limiting. Only enable behind a reverse proxy, and set TRUSTED_PROXIES
TRUSTED_PROXIES (none) Comma-separated CIDRs (or IPs) of your reverse proxies. With this set, forwarding headers are believed only from these addresses and X-Forwarded-For is read right to left. Without it, TRUST_PROXY believes the header from any client, which lets a client evade rate limiting
ALLOWED_HOSTS (none) Extra hostnames accepted in the Host header, comma-separated (.example.com matches subdomains, * disables the check). IP addresses, localhost, the machine's hostname and single-label names such as a Compose service name are always accepted. See Browser-borne request protection
CORS_ALLOWED_ORIGINS (none) Comma-separated web origins allowed to call the API from a browser. Requests carrying any other Origin header are rejected with 403
STRICT_CONTENT_TYPE true Require Content-Type: application/json on API requests (415 otherwise)
ALLOW_LOCAL_PROXIES false Allow a proxy in an API request to point at localhost or a private address. Does not apply to PROXY_URL, which is trusted configuration
IGNORE_CERT_ERRORS false Ignore TLS certificate errors
DNS_REBINDING_PROTECTION true Pin response URL to the request-time IP. Set false for sites serving identical content across multiple TLDs/CDN IPs (SSRF protection stays on)
EGRESS_GUARD false Route every browser connection through an in-process proxy that refuses private, loopback and cloud-metadata destinations. See Egress guard
API_KEY_ENABLED false Enable API key authentication
API_KEY (none) Required API key (use 16+ chars)
BROWSER_SANDBOX false Run Chromium with its own sandbox instead of --no-sandbox. Needs extra container permissions; see below

Chromium sandbox

By default Chromium runs with --no-sandbox, because the default Docker runtime does not allow what the sandbox needs. That means a browser exploit in a page you ask FlareSolverr to load is contained only by the container itself, so keep the container unprivileged (the bundled docker-compose.yml drops all capabilities and sets no-new-privileges) and do not point it at URLs from untrusted callers without API_KEY_ENABLED.

BROWSER_SANDBOX=true turns Chromium's sandbox back on. It uses unprivileged user namespaces, which Docker's default seccomp profile blocks, so with the default runtime the server exits at startup with No usable sandbox!. One of these must also be set:

Docker option Effect
--security-opt seccomp=<profile>.json A seccomp profile that allows user namespaces. seccomp=unconfined also works but removes all syscall filtering
--cap-add SYS_ADMIN Works, but grants a broad capability; prefer a seccomp profile

Whether user namespaces are available also depends on the host (kernel settings and AppArmor), so test on the machine that will run it. Leave the setting off if the container does not start.

Browser-borne request protection

FlareSolverr is normally run without an API key on an address only local software can reach. A web page open in a browser on that machine or network can reach it too, so the server refuses requests that a web page caused:

  • Host check (DNS rebinding). The Host header must be an IP address, localhost, the machine's own hostname, a single-label name (for example the Compose service name in http://flaresolverr:8191), or a name listed in ALLOWED_HOSTS. Anything else gets 403, and the log line names the exact ALLOWED_HOSTS=... value that would allow it. If you reach FlareSolverr through a dotted name (a reverse proxy at solver.example.com, a Kubernetes name like flaresolverr.default.svc), set ALLOWED_HOSTS.
  • Origin check (cross-site requests). A request with an Origin header not listed in CORS_ALLOWED_ORIGINS gets 403. Programs such as Prowlarr, Jackett and curl do not send Origin.
  • JSON only. POST requests must send Content-Type: application/json, or they get 415. If you use curl -d by hand, add -H "Content-Type: application/json". Set STRICT_CONTENT_TYPE=false to turn this off.

API Key Authentication

When enabled, all requests (except /health) require authentication:

# Via header (recommended)
curl -X POST http://localhost:8191/v1 \
  -H "X-API-Key: your-secret-key" \
  -H "Content-Type: application/json" \
  -d '{"cmd": "sessions.list"}'

# Via query parameter
curl -X POST "http://localhost:8191/v1?api_key=your-secret-key" \
  -H "Content-Type: application/json" \
  -d '{"cmd": "sessions.list"}'

Enable in Docker:

environment:
  - API_KEY_ENABLED=true
  - API_KEY=your-secret-key-at-least-16-chars

CAPTCHA Solver Settings

External CAPTCHA solver fallback for Turnstile and hCaptcha challenges that native solving cannot handle.

Nothing is ever bought unless you opt in. With CAPTCHA_FALLBACK_ENABLED unset or false, FlareSolverr never contacts a paid provider, whatever keys are configured. The only other way a provider is contacted is a request that brings its own captchaSolver + captchaApiKey.

Variable Default Description
CAPTCHA_NATIVE_ATTEMPTS 3 Native solve attempts before external fallback (1-10)
CAPTCHA_FALLBACK_ENABLED false Enable external CAPTCHA solver fallback with the server's keys
CAPTCHA_MAX_EXTERNAL_SOLVES 1 Paid solves one request may buy with the server's keys (0-3). 0 buys nothing even when the fallback is enabled
TWOCAPTCHA_API_KEY (none) 2Captcha API key
CAPSOLVER_API_KEY (none) CapSolver API key (standalone Turnstile widgets and hCaptcha; not Cloudflare managed challenges)
ANTICAPTCHA_API_KEY (none) anti-captcha.com API key
NINEKW_API_KEY (none) 9kw.eu API key (hCaptcha/reCAPTCHA only — does not solve Cloudflare Turnstile)
CAPTCHA_PRIMARY_PROVIDER 2captcha Primary provider: 2captcha, capsolver, anticaptcha, or 9kw
CAPTCHA_SOLVER_TIMEOUT 120s How long to wait for a provider to solve one task (30s-300s)
TWOCAPTCHA_BASE_URL (none) Override the 2Captcha API base URL. For testing and self-hosted gateways
CAPSOLVER_BASE_URL (none) Override the CapSolver API base URL. For testing and self-hosted gateways
ANTICAPTCHA_BASE_URL (none) Override the anti-captcha.com API base URL. For testing and self-hosted gateways
NINEKW_BASE_URL (none) Override the 9kw.eu endpoint URL. For testing and self-hosted gateways

The *_BASE_URL overrides must be http or https URLs without credentials or a query string. API keys for that provider — including a request's own captchaApiKey — are sent to the address you configure, so only point them at a gateway you control. A malformed override is ignored and disables the server-side fallback, so a typo cannot send your keys to the provider's real API.

Supported CAPTCHA types:

  • Turnstile — Cloudflare's challenge widget (native + external solving)
  • hCaptcha — Detected automatically, solved via external provider

How it works:

  1. FlareSolverr attempts native Turnstile solving first (click methods, keyboard, etc.)
  2. If native solving fails after CAPTCHA_NATIVE_ATTEMPTS, it falls back to the external solver
  3. For hCaptcha, external solving is used directly (no native solving available)
  4. External solver extracts the sitekey, submits to the provider, and injects the token
  5. Providers are tried in order: CAPTCHA_PRIMARY_PROVIDER first, then the others alphabetically

What one request can cost:

  • A request buys at most CAPTCHA_MAX_EXTERNAL_SOLVES solves (default 1), counted in tasks created at a provider, across Turnstile and hCaptcha and across every pass of the solve loop. If the challenge is still there after the bought token was injected, FlareSolverr goes back to native solving and does not buy again.
  • A provider that refuses the task outright (unsupported captcha type, rejected key, empty balance) costs nothing; the next provider is tried and the refusal is remembered for the rest of the request.
  • Once a task was created and then timed out, nothing more is bought for that request, from any provider.
  • No task is created when fewer than 10 seconds of the request's maxTimeout remain: the solution could not arrive in time. Native attempts run first and take time, so give requests that should fall back to a provider a generous maxTimeout.
  • If it is unclear whether a provider accepted a task (the create call was sent but answered with a 5xx, an error page, or nothing), the solve counts as spent. A create call that never left the server (DNS or connection failure) does not.
  • A flaky response while waiting for a solution (HTTP 429/5xx, an HTML error page) does not abandon the paid task: polling backs off and retries, giving up only after several failures in a row.
  • CapSolver is not sent Cloudflare managed challenges (the "Just a moment..." interstitial): its proxyless Turnstile task cannot carry the challenge-page data, so the solve would be paid for and rejected. With CapSolver as the only provider, managed challenges are not solved externally.

Per-request control (see the captchaSolver and captchaApiKey request fields):

Request Effect
neither field The server's providers, if CAPTCHA_FALLBACK_ENABLED=true
"captchaSolver": "none" No external solving for this request
"captchaSolver": "anticaptcha" Only that provider, with the server's key (must be configured and the fallback enabled)
"captchaSolver": "2captcha", "captchaApiKey": "..." That provider with your key, for this request only and at most one solve — works even when the server-side fallback is disabled. CAPTCHA_MAX_EXTERNAL_SOLVES does not apply
captchaApiKey without captchaSolver Rejected with a validation error

A per-request key is never written to the logs, stored, or returned in a response or error message.

Metrics: /metrics exposes per-provider counters once a provider has been used: flaresolverr_captcha_attempts_total, flaresolverr_captcha_tasks_total (billable tasks created), flaresolverr_captcha_successes_total, flaresolverr_captcha_failures_total, flaresolverr_captcha_cost_usd_total and flaresolverr_captcha_solve_seconds_total, each labelled provider="...".

Example configuration:

environment:
  - CAPTCHA_FALLBACK_ENABLED=true
  - TWOCAPTCHA_API_KEY=your-2captcha-api-key
  - CAPTCHA_NATIVE_ATTEMPTS=2
  - CAPTCHA_MAX_EXTERNAL_SOLVES=1

Selectors Settings

Hot-reload capable selectors for adapting to Cloudflare changes without restarts. Supports local files and remote URLs.

Variable Default Description
SELECTORS_PATH (none) Path to external selectors.yaml override file
SELECTORS_HOT_RELOAD false Enable file watching for automatic reload
SELECTORS_REMOTE_URL (none) HTTP(S) URL to fetch selectors from
SELECTORS_REMOTE_REFRESH 1h Refresh interval for remote selectors (5m-24h)

When SELECTORS_HOT_RELOAD is enabled, changes to the selectors file are automatically detected and applied without restarting the service.

When SELECTORS_REMOTE_URL is configured, selectors are fetched periodically from the remote URL. File selectors take priority over remote selectors if both are configured.

Egress guard

FlareSolverr checks the URL you ask for and refuses private, loopback and cloud-metadata addresses. That check cannot see what the browser does next. A page can redirect to http://10.0.0.5/, load an image or an iframe from it, fetch() it, open a WebSocket to it, or sit behind a hostname that resolves to a public address when it is checked and to a private one when Chromium connects (DNS rebinding). Without the guard those requests are made, from inside your network, before the final URL is examined.

EGRESS_GUARD=true closes that gap. Every browser and browser context is pointed at a small forward proxy inside the FlareSolverr process, listening on 127.0.0.1 only. For each connection the guard resolves the name once, requires every address it resolves to to be public, and connects to the address it checked. This applies to pooled browsers, sessions, browserFlags sessions and the reconnect bypass, and to loopback as well: with the guard on, a page cannot reach 127.0.0.1 or localhost on the machine running FlareSolverr (which includes FlareSolverr's own API).

What it does not change:

  • TLS and HTTP/2 fingerprints. HTTPS is tunnelled byte for byte; the guard never terminates TLS. The JA4 and HTTP/2 fingerprints are identical with the guard on and off.
  • Request headers. Plain HTTP is forwarded with its headers unchanged and in order. No Via or X-Forwarded-For is added.

What it does change:

  • No HTTP/3. Chromium does not use HTTP/3 (QUIC) through a proxy, so sites that would have moved to HTTP/3 stay on HTTP/2. Cloudflare can see that. The solves tested for this release were unaffected, but a managed-challenge target was not available to measure, which is why the guard is off by default.
  • Refused destinations look like errors. A blocked HTTPS destination fails with net::ERR_TUNNEL_CONNECTION_FAILED; a blocked plain-HTTP destination returns a 403 page from the guard.
  • Proxies. With the guard on, a proxy from a request, a session, PROXY_LIST or PROXY_URL is reached through the guard, which logs in to it itself (http, https and socks5, with or without credentials). A per-request proxy then runs in a browser context on a pooled browser instead of a browser of its own, the reconnect bypass works with proxies that need credentials, and PROXY_USERNAME / PROXY_PASSWORD are applied to PROXY_URL. socks4 proxies are rejected. Wrong proxy credentials are reported as the proxy rejected the supplied credentials.
  • DNS. Names are resolved by FlareSolverr with the system resolver rather than by Chromium.

Limit when a proxy is in use: the upstream proxy resolves hostnames, so the guard cannot check the address a name leads to. It still refuses what it can recognise without resolving (private and special-purpose IP addresses in any notation, localhost names, cloud-metadata names), but a hostname that the proxy resolves to a private address is not caught. Without an upstream proxy there is no such gap.

The guard exports counters on /metrics (flaresolverr_egress_guard_*), including the number of destinations it refused.

Logging & Monitoring

Variable Default Description
LOG_LEVEL info Log level (trace, debug, info, warn, error). Health-check request logs and periodic Server stats are emitted at debug, so set LOG_LEVEL=debug to see them.
LOG_HTML false Log HTML responses (verbose)
LOG_FILE (none) Path to log file (in addition to stdout)
TZ (none) Browser timezone (e.g., America/New_York)
LANG (none) Browser language (e.g., en_GB)
TEST_URL https://www.google.com URL to verify browser works on startup
DASHBOARD_ENABLED true TUI dashboard (auto-disables without TTY)
PPROF_ENABLED false Enable pprof profiling
PPROF_PORT 6060 pprof server port
PPROF_BIND_ADDR 127.0.0.1 pprof bind address

CLI Dashboard

FlareSolverr launches a split-screen terminal dashboard by default when running in an interactive terminal:

┌──────── Incoming Requests ────────┬──────────── Server Stats ──────────┐
│ 16:03:22 POST /v1 example.com 200 │ Server                             │
│ 16:03:21 POST /v1 site.org   200  │  Uptime: 2h 14m  Req/s: 2.1       │
│ 16:03:19 POST /v1 example.com 200 │  Total: 1,247    Goroutines: 42   │
│ 16:03:15 POST /v1 blocked.io 403  │  Memory: 312 MB                   │
│                                    │ Pool                               │
│                                    │  2/3 available                     │
│                                    │ Sessions                           │
│                                    │  4 active                          │
│                                    │ Domains (3)                        │
│                                    │  example.com  842 req  96% ok     │
│                                    │  site.org     231 req  89% ok     │
└────────────────────────────────────┴────────────────────────────────────┘

Left pane: Live request log with method, path, status code, and latency. Right pane: Server stats including uptime, request rate, memory, browser pool state, active sessions, and top domains by request count.

Press q or ctrl+c to exit. Logging is suppressed while the dashboard is active.

The dashboard auto-disables when stdout is not a terminal (e.g., Docker logs, piped output). To explicitly disable it: DASHBOARD_ENABLED=false.

Docker Compose Example

version: "3.8"
services:
  flaresolverr:
    image: rorqualx/flaresolverr-go:latest
    container_name: flaresolverr
    ports:
      - "8191:8191"
    environment:
      - LOG_LEVEL=info
      - BROWSER_POOL_SIZE=3
      - MAX_TIMEOUT=300s
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL

Migration from Python FlareSolverr

  1. Stop your existing FlareSolverr container
  2. Update your docker-compose.yml or run command to use this image
  3. Start the new container
  4. Your existing clients will work without any changes

No code changes required in your applications.

Cookie Format Difference

There is one minor difference in cookie field naming:

Field Python FlareSolverr Go Edition
Expiration expiry expires

Both values are Unix timestamps (seconds since epoch). If your client code explicitly checks for cookie expiration, use this pattern to handle both:

JavaScript/TypeScript:

const expiry = cookie.expiry ?? cookie.expires;

Python:

expiry = cookie.get('expiry') or cookie.get('expires')

Go:

expiry := cookie.Expiry
if expiry == 0 {
    expiry = cookie.Expires
}

Additional Cookie Fields

The Go edition includes extra fields not in Python FlareSolverr:

Field Type Description
size int Cookie size in bytes
session bool Whether it's a session cookie

These are optional and can be safely ignored if not needed.

Performance Comparison

Metric Python FlareSolverr Go Edition
Memory per session 400-700 MB 150-250 MB
Startup time 5-10s <1s
Request latency Higher (Selenium) Lower (CDP)
Concurrent requests Limited (GIL) Native goroutines

Health Check

curl http://localhost:8191/health

Returns:

{
  "status": "ok",
  "message": "FlareSolverr is ready",
  "version": "1.0.0",
  "pool": {
    "size": 3,
    "available": 2,
    "acquired": 150,
    "released": 148,
    "recycled": 5,
    "errors": 2
  }
}

/health needs no API key, so it only reports liveness and pool counters. Per-domain statistics are on /stats, which requires the API key when one is enabled:

curl http://localhost:8191/stats

Returns the /health fields plus:

{
  "domainStats": {
    "example.com": {
      "requestCount": 45,
      "successCount": 42,
      "errorCount": 3,
      "rateLimitCount": 2,
      "avgLatencyMs": 2340,
      "lastRequestTime": "2025-01-15T10:35:00Z",
      "lastRateLimited": "2025-01-15T10:30:00Z",
      "suggestedDelayMs": 5000
    }
  },
  "defaults": {
    "minDelayMs": 1000,
    "maxDelayMs": 30000
  }
}

Pool Statistics

Field Description
size Configured pool size (number of browser instances)
available Browsers currently idle and ready for requests
acquired Total browsers acquired from pool
released Total browsers returned to pool
recycled Browsers recycled due to memory or errors
errors Total browser operation errors

Domain Statistics

When requests have been made, the health endpoint includes per-domain statistics:

Field Description
requestCount Total requests to this domain
successCount Successful requests (2xx/3xx, no rate limiting)
errorCount Failed requests
rateLimitCount Requests that were rate limited
avgLatencyMs Average response time
lastRateLimited Timestamp of last rate limit event
suggestedDelayMs Recommended delay between requests

The suggestedDelayMs is calculated using an algorithm inspired by Scrapy's AutoThrottle, considering latency, error rates, and recent rate limiting events.

Performance Tuning

Understanding Concurrency

FlareSolverr uses a browser pool to handle concurrent requests efficiently:

  • BROWSER_POOL_SIZE controls how many requests can be processed simultaneously
  • RATE_LIMIT_RPM controls the maximum requests per minute per IP

Example behavior with defaults (BROWSER_POOL_SIZE=3, RATE_LIMIT_RPM=60):

  • 3 requests can be processed in parallel
  • Additional requests queue until a browser becomes available
  • Rate limiting kicks in at 60 requests/minute per client IP

Tuning for Your Use Case

Scenario Recommended Settings
Low volume, fast response BROWSER_POOL_SIZE=2, RATE_LIMIT_RPM=30
Medium volume BROWSER_POOL_SIZE=3, RATE_LIMIT_RPM=60 (defaults)
High volume, more memory BROWSER_POOL_SIZE=5, RATE_LIMIT_RPM=120
Single client, max throughput BROWSER_POOL_SIZE=5, RATE_LIMIT_ENABLED=false

Memory Considerations

Each browser instance consumes approximately:

  • 100-150MB base memory
  • +50-100MB per active page (during request processing)

For a pool size of 3 with 5 active pages, expect 500-700MB total memory usage.

Use MAX_MEMORY_MB to set a memory ceiling. When exceeded, browsers are automatically recycled.

Troubleshooting

Common Issues

"Access denied" errors

  • IP blocked: The target site may have blocked your IP. Try using a proxy.
  • Bot detection: Some sites have aggressive bot detection. Session reuse can help maintain cookies.
  • Rate limiting: Reduce request frequency or use RATE_LIMIT_RPM to self-throttle.

Session requests failing

  • Sessions auto-expire after SESSION_TTL (default: 30 minutes)
  • Use sessions.keepalive to refresh a session's TTL without making a full request
  • Use keepaliveTtl parameter to extend the TTL (e.g., "keepaliveTtl": 120 for 2 hours)
  • Always check if session exists with sessions.list before using
  • Destroy and recreate sessions if they become stale

High memory usage

  • Reduce BROWSER_POOL_SIZE (each browser uses 100-150MB)
  • Enable disableMedia: true in requests to skip images/CSS
  • Set MAX_MEMORY_MB to trigger automatic browser recycling

Browser pool exhaustion

  • Increase BROWSER_POOL_SIZE for higher concurrency
  • Increase BROWSER_POOL_TIMEOUT if requests timeout waiting for browsers
  • Check /health endpoint to monitor pool stats

Container crashes

  • Ensure adequate memory (minimum 512MB recommended)
  • Check Docker logs for OOM kills
  • Reduce BROWSER_POOL_SIZE if memory constrained

Debug Mode

Enable debug logging to see detailed information:

docker run -e LOG_LEVEL=debug -p 8191:8191 rorqualx/flaresolverr-go:latest

Getting Help

  • GitHub Issues - Bug reports and feature requests
  • Check /health endpoint for pool statistics
  • Review container logs for error messages

Changelog

See CHANGELOG.md for version history and release notes.

Attribution

Built with Claude Code by Anthropic

License

MIT License - See LICENSE for details.

About

FlareSolverr Go Edition - High-performance Cloudflare bypass proxy written in Go

Resources

Contributing

Security policy

Stars

23 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages