> ## Documentation Index
> Fetch the complete documentation index at: https://verifyngo.chocolatemoo53.com/llms.txt
> Use this file to discover all available pages before exploring further.

# verifyngo config.json: all fields, types, and defaults

> Every field accepted by verifyngo's config.json, with types, default values, and plain-English descriptions of what each setting does.

This page lists every field recognised in `config.json`. Fields marked **required** have no default and must be set before verifyngo will start. All duration values use Go's duration string format: `"30s"`, `"10m"`, `"2h"`, `"168h"` (7 days).

***

## Core

| Field | Type | Default | Description |
| - | - | - | - |
| `listen_addr` | `string` | — | Address and port verifyngo listens on, e.g. `"127.0.0.1:8080"`. |
| `upstream_url` | `string` | **required** | Base URL of the backend that verifyngo proxies to, e.g. `"http://127.0.0.1:8886"`. |
| `cookie_secret` | `string` | **required** | Secret used to sign the verification cookie. Must be at least 32 bytes and stay constant across restarts, or all existing cookies become invalid. Generate with `openssl rand -hex 32`. |
| `cookie_name` | `string` | `"cp_verified"` | Name of the verification cookie set in the visitor's browser. |
| `cookie_ttl` | `duration` | `"168h"` | How long a solved CAPTCHA cookie is valid. Default is 7 days. |
| `default_action` | `string` | `"challenge"` | What to do when no rule matches. Accepted values: `"challenge"`, `"allow"`, `"deny"`, `"tarpit"`. |
| `metrics_addr` | `string` | `""` | Address for a Prometheus `/metrics` listener, for example `"127.0.0.1:9100"`. Disabled when empty. The endpoint has no authentication, so bind it to localhost or a private network only. |
| `anonymize_ips` | `bool` | `true` | When `true`, visitor IPs are truncated before they are written to logs: IPv4 to /24, IPv6 to /48. Set to `false` to log full addresses. While this is on, AbuseIPDB reporting is disabled because reports need the real IP. Blacklist fetching still works. |
| `response_csp` | `string` | `""` | `Content-Security-Policy` header added to proxied upstream responses that do not already set one. Leave empty to add no header. |

***

## IP Trust

| Field | Type | Default | Description |
| - | - | - | - |
| `trust_real_ip` | `bool` | `false` | When `true`, verifyngo reads the visitor IP from `X-Forwarded-For`, but only when the connecting address is in `trusted_proxies`. Requires `trusted_proxies` to be set, or verifyngo refuses to start. |
| `trust_fingerprint_headers` | `bool` | `false` | When `true`, verifyngo reads the `X-TLSFP` or `X-JA4` TLS fingerprint headers set by your proxy. Only honoured when the connecting address is in `trusted_proxies`. Requires `trusted_proxies` to be set, or verifyngo refuses to start. Enable it only when every request passes through the proxy that sets these headers. |
| `trusted_proxies` | `[]string` | `[]` | **Required when `trust_real_ip` or `trust_fingerprint_headers` is `true`.** IP addresses or CIDR ranges of your reverse proxies. No ranges are trusted by default, including loopback and private ranges, so list every proxy explicitly. Only the connecting address is checked against this list, and `X-Forwarded-For` from any other address is ignored. |
| `whitelist` | `[]string` | `[]` | IP addresses or CIDR ranges that always bypass all checks and are never challenged. |

***

## Paths

| Field | Type | Default | Description |
| - | - | - | - |
| `bypass_paths` | `[]string` | See below | Regex patterns matched against the request path. Matching requests are never challenged. Defaults to a built-in list covering common static assets (see below). Set to `null` (omit the field) to restore the default list. |
| `always_pass_paths` | `[]string` | `[]` | Regex patterns for paths that are always proxied upstream unconditionally, even if the visitor is banned. Useful for health-check endpoints. |
| `static_dir` | `string` | `""` | Path prefix for verifyngo's own built-in static assets (challenge page resources). |
| `rules_file` | `string` | `""` | Path to an external rules file. Hot-reloaded every \~10 seconds. |
| `policy_file` | `string` | `""` | Path to an external policy file (ASN-based rules). Hot-reloaded every \~10 seconds. If both `rules_file` and `policy_file` are set, `policy_file` takes priority and is the only file watched for changes. |

**Default `bypass_paths` list**

When `bypass_paths` is not set in `config.json`, verifyngo uses the following patterns:

```text theme={null}
^/favicon\.ico$
^/robots\.txt$
^/sitemap\.xml$
^/\.well-known/
^/(?:manifest\.json|site\.webmanifest|manifest\.webmanifest)$
\.webmanifest$
^/browserconfig\.xml$
^/apple-touch-icon(?:-precomposed)?(?:-\d+x\d+)?\.png$
\.(?:css|js|mjs|png|jpe?g|gif|svg|webp|avif|ico|woff2?|ttf|otf|eot|wasm|mp4|webm)$
```

***

## CAPTCHA

### Provider selection

| Field | Type | Default | Description |
| - | - | - | - |
| `provider` | `string` | **required** | Which CAPTCHA provider to use. Accepted values: `"cap"`, `"turnstile"`, `"hcaptcha"`, `"slider"`. |

### `cap` (self-hosted Cap)

| Field | Type | Default | Description |
| - | - | - | - |
| `cap.api_url` | `string` | `""` | Public URL of the Cap instance, shown to the visitor's browser, e.g. `"https://captcha.example.com"`. |
| `cap.verify_url` | `string` | `""` | Internal URL used by verifyngo to verify tokens server-side, e.g. `"http://127.0.0.1:3000"`. Must be reachable by the verifyngo process, not the browser. |
| `cap.site_key` | `string` | `""` | Cap site key. |
| `cap.secret_key` | `string` | `""` | Cap secret key used for server-side verification. |
| `cap.widget_script_url` | `string` | `""` | Override URL for the Cap widget script. Leave empty to use the default from `api_url`. |
| `cap.csp` | `string` | `""` | `Content-Security-Policy` sent with the challenge page. Use `{nonce}` as the placeholder for the per-request nonce. Leave empty to have verifyngo build the policy itself. |

### `slider` (built-in slider puzzle)

A self-hosted slider puzzle that needs no third-party service. Visitors drag a piece into place. Set `slider.enabled` to `true` and then use `"slider"` as the provider.

| Field | Type | Default | Description |
| - | - | - | - |
| `slider.enabled` | `bool` | `false` | Enable the slider puzzle. |
| `slider.tolerance` | `int` | `8` | How far off the answer can be and still count as correct. |
| `slider.ttl` | `duration` | `"10m"` | How long a puzzle stays valid before it expires. |
| `slider.max_challenges` | `int` | `5000` | Maximum number of puzzles kept at once. |
| `slider.width` | `int` | `320` | Puzzle width in pixels. Values below `200` are raised to `200`. |
| `slider.height` | `int` | `120` | Puzzle height in pixels. Values below `60` are raised to `60`. |
| `slider.min_solve_time` | `duration` | `"700ms"` | Minimum time a visitor must take to solve the puzzle. Faster answers are rejected. |

### `turnstile` (Cloudflare Turnstile)

| Field | Type | Default | Description |
| - | - | - | - |
| `turnstile.site_key` | `string` | `""` | Turnstile site key from the Cloudflare dashboard. |
| `turnstile.secret_key` | `string` | `""` | Turnstile secret key used for server-side verification. |

### `hcaptcha`

| Field | Type | Default | Description |
| - | - | - | - |
| `hcaptcha.site_key` | `string` | `""` | hCaptcha site key. |
| `hcaptcha.secret_key` | `string` | `""` | hCaptcha secret key used for server-side verification. |

***

## Walk-away & Ban

Walk-away tracking counts how many times a visitor abandons the challenge page without solving it. When the threshold is exceeded the visitor is banned.

When `trust_fingerprint_headers` is enabled and a request carries a TLS fingerprint (`X-TLSFP` or `X-JA4`, set by a proxy such as the nginx Lua module in `lua/ja4_fingerprint.lua`), walk-aways are also counted per fingerprint. The count is kept separately for each fingerprint within the client's /24 (IPv4) or /48 (IPv6) network. Once the threshold is reached, that fingerprint is blocked for `ban.duration` from that network, so other IPs using the same TLS client are not affected.

| Field | Type | Default | Description |
| - | - | - | - |
| `walkaway.threshold` | `int` | `10` | Number of walk-aways before the visitor's IP is banned. |
| `walkaway.ttl` | `duration` | `"2h"` | How long a walk-away counter entry lives before it is reset. |
| `ban.duration` | `duration` | `"24h"` | How long a banned IP remains banned. |

***

## Progressive

Progressive mode observes traffic passively on specified paths before deciding to challenge. Visitors who exceed `max_requests` within `request_window` are challenged.

| Field | Type | Default | Description |
| - | - | - | - |
| `progressive.enabled` | `bool` | `false` | Enable progressive challenge mode. |
| `progressive.passive_paths` | `[]string` | `[]` | Regex patterns for paths observed in passive mode. Requests to these paths increment the counter without immediately triggering a challenge. |
| `progressive.passive_ttl` | `duration` | `"30m"` | How long a passive observation window entry lives. |
| `progressive.max_requests` | `int` | `50` | Number of requests within `request_window` that triggers a challenge. |
| `progressive.request_window` | `duration` | `"10m"` | Sliding window duration for counting requests. |

***

## Rules

Rules run in order, and the first matching rule decides the action. Rules are set in the `rules` array, or in an external file with `rules_file`.

| Field | Type | Description |
| - | - | - |
| `match.path` | `string` | Regex matched against the request path. |
| `match.ua` | `string` | Regex matched against the `User-Agent` header. |
| `match.cidr` | `[]string` | IP addresses or CIDR ranges to match. |
| `action` | `string` | `"allow"` (alias `"pass"`), `"deny"` (alias `"drop"`), `"tarpit"` (alias `"poison"`), or `"challenge"` (aliases `"check"`, `"none"`). Any other value is treated as `"challenge"`. |

***

## Tor

verifyngo recognises two kinds of Tor traffic:

* **Onion:** requests from an onion circuit address, or requests with a `.onion` `Host` header that arrive from a loopback or private address.
* **Exit:** requests from an address on the Tor Project's exit list.

Tor traffic never gets the passive pass. Tor traffic is also never checked against, or reported to, AbuseIPDB. Exit addresses are shared by many people, so they are never banned. Each request without a cookie gets a challenge, and failed attempts are not counted against the address.

| Field | Type | Default | Description |
| - | - | - | - |
| `tor.exit_list` | `bool` | `false` | Fetch the Tor Project's exit list and classify matching addresses as `"exit"`. |
| `tor.refresh` | `duration` | `"1h"` | How often the exit list is downloaded again. |

Policy conditions can read the traffic kind as `tor`, which is `"onion"`, `"exit"`, or `""`:

```yaml theme={null}
  - name: no-tor-admin
    conditions:
      - 'tor != "" && path.startsWith("/admin")'
    action: deny
```

### Onion services with circuit IDs

Without circuit IDs, every onion visitor arrives from the same address and is treated like an exit. With circuit IDs, each circuit gets its own address, so walk-aways, failed attempts, and bans apply per circuit, as they do for any other visitor. Tor then opens each connection with a PROXY protocol header, so the onion needs its own nginx port. Set `trust_real_ip` to `true` and list that nginx in `trusted_proxies`. See the Tor section of the README for the full torrc and nginx setup.

## Tarpit

Visitors sent to the tarpit get generated filler text instead of the real site, which wastes the time of automated clients that ignore rate limits.

| Field | Type | Default | Description |
| - | - | - | - |
| `tarpit.seed` | `string` | `cookie_secret` | Seed for the text generator. Set it to keep output stable across restarts. |
| `tarpit.corpus` | `string` | `""` | Path to a text file used to train the generator. The file must contain more than 200 words, or the built-in corpus is used. |

***

## Store

The store holds ban state, walk-away counters, and progressive counters.

| Field | Type | Default | Description |
| - | - | - | - |
| `store.backend` | `string` | `"memory"` | Storage backend. Accepted values: `"memory"`, `"file"`, `"redis"`. |

### `store.file` (file backend)

| Field | Type | Default | Description |
| - | - | - | - |
| `store.file.path` | `string` | `""` | Path to the JSON file used to persist store data. |
| `store.file.save_interval` | `duration` | `"30s"` | How often the in-memory state is flushed to disk. |

### `store.redis` (Redis backend)

| Field | Type | Default | Description |
| - | - | - | - |
| `store.redis.addr` | `string` | `"127.0.0.1:6379"` | Address of the Redis server. |
| `store.redis.password` | `string` | `""` | Redis password (leave empty if not set). |
| `store.redis.db` | `int` | `0` | Redis database index. |
| `store.redis.key_prefix` | `string` | `"verifyngo"` | Prefix applied to all keys written by verifyngo. |

***

## ASN

ASN lookups power policy-file rules that match on autonomous system numbers or organisation names.

| Field | Type | Default | Description |
| - | - | - | - |
| `asn.geoip_db_path` | `string` | `""` | Path to a MaxMind GeoLite2-ASN `.mmdb` file. Required for ASN-based rules when whois is disabled. |
| `asn.whois_enabled` | `bool` | `true` | Use WHOIS to resolve ASN information at startup. |
| `asn.whois_addr` | `string` | `"whois.radb.net:43"` | WHOIS server address used for lookups. |
| `asn.whois_timeout` | `duration` | `"30s"` | Timeout for each WHOIS query. |
| `asn.whois_strict` | `bool` | `false` | When `true`, a WHOIS timeout or error during startup is treated as fatal and verifyngo will exit. When `false`, a warning is logged and startup continues. |

***

## AbuseIPDB

When enabled, verifyngo reports banned IPs to [AbuseIPDB](https://www.abuseipdb.com/) after they have been banned a configurable number of times.

| Field | Type | Default | Description |
| - | - | - | - |
| `abuseipdb.enabled` | `bool` | `false` | Enable AbuseIPDB reporting. |
| `abuseipdb.api_key` | `string` | `""` | Your AbuseIPDB API key. |
| `abuseipdb.categories` | `string` | `""` | Comma-separated AbuseIPDB category IDs to attach to reports, e.g. `"19,21"`. |
| `abuseipdb.comment` | `string` | `""` | Comment included with each report. |
| `abuseipdb.report_after_bans` | `int` | `3` | Number of bans an IP must accumulate before it is reported. |
| `abuseipdb.blacklist.enabled` | `bool` | `false` | Download AbuseIPDB's blacklist and deny every IP on it. Requires `abuseipdb.api_key`; verifyngo exits at startup if the key is missing. |
| `abuseipdb.blacklist.refresh` | `duration` | `"6h"` | How often the blacklist is downloaded again. |

***

## Branding

All branding fields are optional. They customise the appearance of the challenge page shown to visitors.

| Field | Type | Default | Description |
| - | - | - | - |
| `branding.logo_url` | `string` | `""` | URL of a logo image displayed on the challenge page. |
| `branding.css_url` | `string` | `""` | URL of a custom stylesheet that overrides the default challenge page styles. |
| `branding.accent_color` | `string` | `"#4A90D9"` | Primary accent colour used for buttons and highlights. |
| `branding.background_color` | `string` | `"#000000"` | Challenge page background colour. |
| `branding.text_color` | `string` | `"#f2f2f2"` | Challenge page body text colour. |
| `branding.font_family` | `string` | `"sans-serif"` | CSS `font-family` value applied to the challenge page. |
| `branding.font_url` | `string` | `""` | URL of a web font stylesheet to load (e.g. a Google Fonts URL). |
| `branding.title` | `string` | `""` | Site name shown in the challenge page heading. |
| `branding.details_text` | `string` | See below | Explanatory paragraph shown beneath the CAPTCHA widget. |
| `branding.contact_url` | `string` | `""` | URL linked from a "contact us" prompt on the challenge page. |
| `branding.lang` | `string` | `"en"` | Language code set on the challenge page, for example `"de"` or `"fr"`. |

**Default `branding.details_text`**

```text theme={null}
This page runs a quick, one-time check to confirm you're a real visitor and
not an automated bot. It only takes a moment, and once you've solved it you
won't see this again for a while.
```

***

> [accordion] Full example config.json
>
> ```json theme={null}
> {
>   "listen_addr": "127.0.0.1:8080",
>   "upstream_url": "http://127.0.0.1:8886",
>   "cookie_secret": "CHANGE_ME_openssl_rand_hex_32",
>   "cookie_name": "cp_verified",
>   "cookie_ttl": "168h",
>   "provider": "cap",
>   "cap": {
>     "api_url": "https://captcha.example.com",
>     "verify_url": "http://127.0.0.1:3000",
>     "site_key": "your-cap-site-key",
>     "secret_key": "your-cap-secret-key",
>     "widget_script_url": ""
>   },
>   "turnstile": {
>     "site_key": "",
>     "secret_key": ""
>   },
>   "hcaptcha": {
>     "site_key": "",
>     "secret_key": ""
>   },
>   "trust_real_ip": false,
>   "trusted_proxies": [],
>   "static_dir": "/static",
>   "rules_file": "/rules.txt",
>   "policy_file": "",
>   "always_pass_paths": ["^/health$", "^/healthz$", "^/status$"],
>   "asn": {
>     "geoip_db_path": "",
>     "whois_enabled": true,
>     "whois_addr": "whois.radb.net:43",
>     "whois_timeout": "30s",
>     "whois_strict": false
>   },
>   "store": {
>     "backend": "memory",
>     "file": {
>       "path": "/data/store.json",
>       "save_interval": "30s"
>     },
>     "redis": {
>       "addr": "redis:6379",
>       "password": "",
>       "db": 0,
>       "key_prefix": "verifyngo"
>     }
>   },
>   "progressive": {
>     "enabled": false,
>     "passive_paths": [
>       "^/$",
>       "/this-specific-path"
>     ],
>     "passive_ttl": "30m",
>     "max_requests": 50,
>     "request_window": "10m"
>   },
>   "branding": {
>     "logo_url": "/static/logo.svg",
>     "css_url": "/static/custom.css",
>     "title": "My Website",
>     "accent_color": "#444444",
>     "background_color": "#000000",
>     "text_color": "#f2f2f2",
>     "font_family": "'My Font', sans-serif",
>     "font_url": "",
>     "details_text": "This page runs a quick, one-time check to confirm you're a real visitor and not an automated bot. It only takes a moment, and once you've solved it you won't see this again for a while.",
>     "contact_url": "mailto:contact@example.com"
>   },
>   "whitelist": [
>     "203.0.113.10",
>     "198.51.100.0/24"
>   ],
>   "default_action": "challenge",
>   "rules": [
>     {
>       "match": { "path": "^/api/" },
>       "action": "allow"
>     },
>     {
>       "match": { "ua": "python-requests|curl/|Go-http-client" },
>       "action": "deny"
>     },
>     {
>       "match": { "cidr": ["185.220.0.0/16"] },
>       "action": "deny"
>     },
>     {
>       "match": { "path": "^/search" },
>       "action": "challenge"
>     }
>   ],
>   "walkaway": {
>     "threshold": 10,
>     "ttl": "2h"
>   },
>   "ban": {
>     "duration": "24h"
>   },
>   "abuseipdb": {
>     "enabled": false,
>     "api_key": "",
>     "categories": "19,21",
>     "comment": "verifyngo: exceeded walk-away threshold",
>     "report_after_bans": 3
>   }
> }
> ```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.