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

# Persist ban and walk-away state across process restarts

> Configure verifyngo to store ban lists and walk-away counters in memory, a JSON file, or Redis so state survives restarts and scales across instances.

By default verifyngo keeps all state — walk-away counters, ban lists, and recent-path logs — in memory. That state resets whenever the process restarts. For production deployments you can persist state to a JSON file, so bans survive restarts, or to Redis, which also lets multiple verifyngo instances share a single ban list behind a load balancer.

## Storage backends

Choose the backend that fits your deployment. All three options use the same `store` key in your config file.

<AccordionGroup>
  <Accordion title="Memory (default)">
    ```json theme={null}
    {
      "store": {
        "backend": "memory"
      }
    }
    ```

    No extra setup is needed. verifyngo starts immediately and holds all state in process memory. This is fine for a single instance where you don't mind losing ban state when the process restarts — for example, during development or low-traffic deployments.
  </Accordion>

  <Accordion title="File">
    ```json theme={null}
    {
      "store": {
        "backend": "memory",
        "file": {
          "path": "/data/store.json",
          "save_interval": "30s"
        }
      }
    }
    ```

    <Note>
      The `backend` field stays `"memory"`. Setting `file.path` enables periodic persistence on top of the in-memory store — it does not change the backend itself.
    </Note>

    verifyngo saves a snapshot of its in-memory state to the file every `save_interval`. On startup it reads the snapshot back, so walk-away counters and active bans survive restarts. Expired entries are skipped on load, so you never carry stale bans forward.

    If you are running in Docker, mount `/data` (or whichever directory you choose) as a named volume so the snapshot file is not lost when the container is recreated:

    ```yaml theme={null}
    volumes:
      - verifyngo_data:/data
    ```
  </Accordion>

  <Accordion title="Redis">
    ```json theme={null}
    {
      "store": {
        "backend": "redis",
        "redis": {
          "addr": "redis:6379",
          "password": "",
          "db": 0,
          "key_prefix": "verifyngo"
        }
      }
    }
    ```

    Redis is the right choice when you run multiple verifyngo instances behind a load balancer. All instances connect to the same Redis server and share a single ban list, so a ban triggered by one instance is immediately visible to all others. Redis handles expiry natively, so there is no snapshot file to manage.

    `key_prefix` is prepended to every key verifyngo writes (for example `verifyngo:blocked:1.2.3.4`). Set a unique prefix if you share a Redis instance with other services.
  </Accordion>
</AccordionGroup>

## Store fields

<ParamField body="backend" type="string" default="memory">
  Storage backend to use. Accepted values are `"memory"` and `"redis"`. When set to `"memory"`, you can optionally pair it with `file.path` to add periodic persistence.
</ParamField>

<ParamField body="file.path" type="string">
  Path to the JSON snapshot file used for periodic persistence when `backend` is `"memory"`. If this field is omitted, no snapshot is written or read. verifyngo creates any missing parent directories automatically.
</ParamField>

<ParamField body="file.save_interval" type="string" default="30s">
  How often to write the in-memory snapshot to disk. Accepts any Go duration string, for example `"10s"`, `"1m"`, or `"5m"`. The snapshot is only written when the state has changed since the last write.
</ParamField>

<ParamField body="redis.addr" type="string" default="127.0.0.1:6379">
  Address of the Redis server in `host:port` form. Use the service name when running inside Docker Compose (for example `"redis:6379"`).
</ParamField>

<ParamField body="redis.password" type="string">
  Password for Redis authentication. Leave as an empty string if your Redis instance does not require a password.
</ParamField>

<ParamField body="redis.db" type="integer" default="0">
  Redis database number to select. Defaults to `0`, which is the standard Redis default database.
</ParamField>

<ParamField body="redis.key_prefix" type="string" default="verifyngo">
  String prepended to every Redis key verifyngo writes, separated by a colon (for example `verifyngo:blocked:1.2.3.4`). Change this if you share a Redis instance with other services so there are no key collisions.
</ParamField>

## Walk-away and ban settings

verifyngo tracks how many times a given IP has "walked away" — that is, hit a challenge-protected path without completing the CAPTCHA. Once that count reaches the threshold within the configured time window, the IP is temporarily banned and blocked from all upstream traffic until the ban expires.

```json theme={null}
"walkaway": {
  "threshold": 10,
  "ttl": "2h"
},
"ban": {
  "duration": "24h"
}
```

<ParamField body="walkaway.threshold" type="integer" default="10">
  Number of walk-aways an IP is allowed before it is banned. Each walk-away increments a counter that expires after `walkaway.ttl`. Once the IP completes a challenge successfully, its counter is reset to zero.
</ParamField>

<ParamField body="walkaway.ttl" type="string" default="2h">
  The sliding window in which walk-aways are counted. After this duration passes with no further walk-aways, the counter resets automatically. Accepts any Go duration string, for example `"30m"` or `"4h"`.
</ParamField>

<ParamField body="ban.duration" type="string" default="24h">
  How long an IP stays banned after hitting the walk-away threshold. During this period all requests from that IP are blocked with a `403` response. Accepts any Go duration string, for example `"1h"` or `"48h"`.
</ParamField>
