# Configuration: abuse protection

> Source: https://ferron.sh/docs/configuration/content/abuse-ban

This page documents `abuse_protection` and `abuse_event` directives for configuring lightweight IP-based abuse protection. When a client exceeds configured thresholds (for example, rate limit breaches or brute-force failures), Ferron temporarily bans the IP address.

The abuse protection module tracks abuse events that other HTTP modules emit. These modules include rate limiting and basic authentication. It temporarily bans IPs that exceed configured thresholds within time windows. The system stores bans in memory and they automatically expire after the configured duration.

## `abuse_protection`

```ferron
example.com {
    abuse_protection {
        ban_duration "15m"
        log_rejections

        rate_limit_threshold {
            events 5
            window "300s"
        }

        brute_force_threshold {
            events 3
            window "120s"
        }
    }
}
```

You can place the `abuse_protection` block inside an HTTP host block.

| Nested directive        | Arguments                 | Description                                                                                                     | Default              |
| ----------------------- | ------------------------- | --------------------------------------------------------------------------------------------------------------- | -------------------- |
| `ban_duration`          | `<duration>`              | How long to ban an IP.                                                                                          | `"15m"` (15 minutes) |
| `rate_limit_threshold`  | block                     | Ban after N rate limit events in window.                                                                        | 5 in 300s            |
| `brute_force_threshold` | block                     | Ban after N brute force failures in window.                                                                     | 3 in 120s            |
| `custom_threshold`      | block                     | Ban after N custom events in window.                                                                            | none                 |
| `error_rate_threshold`  | block                     | Ban after N error responses (for example, 404, 403) in window.                                                  | none                 |
| `allowlist`             | `<string> [<string> ...]` | IP addresses or CIDR ranges exempt from bans. You can specify this directive multiple times.                    | none                 |
| `log_rejections`        | `[bool]`                  | Re-enable per-request ban rejection debug logs for forensics. Off by default; ban lifecycle transitions are logged instead. | `false` |

### Threshold blocks

Each threshold block (`rate_limit_threshold`, `brute_force_threshold`, `custom_threshold`) configures when a specific event type triggers a ban:

| Nested directive | Arguments    | Description                                            |
| ---------------- | ------------ | ------------------------------------------------------ |
| `events`         | `<int>`      | Number of events required to trigger a ban (required). |
| `window`         | `<duration>` | Time window for counting events (required).            |

### Error rate threshold block

The `error_rate_threshold` block configures when error response patterns trigger a ban:

| Nested directive | Arguments                 | Description                                                             | Default |
| ---------------- | ------------------------- | ----------------------------------------------------------------------- | ------- |
| `events`         | `<int>`                   | Number of error responses required to trigger a ban.                    | 50      |
| `window`         | `<duration>`              | Time window for counting error responses.                               | `"60s"` |
| `status_codes`   | `<string> [<string> ...]` | HTTP status codes that count as errors (for example, `"404"`, `"403"`). | `"404"` |

**Configuration example, stricter thresholds:**

```ferron
example.com {
    abuse_protection {
        ban_duration "30m"

        rate_limit_threshold {
            events 3
            window "60s"
        }

        brute_force_threshold {
            events 2
            window "120s"
        }
    }
}
```

Ban after:

- 3 rate limit events in 60s (30-min ban), OR
- 2 brute force failures in 120s (30-min ban)

**Configuration example, lenient thresholds:**

```ferron
example.com {
    abuse_protection {
        ban_duration "5m"

        rate_limit_threshold {
            events 10
            window "600s"
        }

        brute_force_threshold {
            events 10
            window "600s"
        }
    }
}
```

Ban after:

- 10 rate limit events in 10 minutes (5-min ban), OR
- 10 brute force failures in 10 minutes (5-min ban)

**Configuration example, trusted IP list:**

```ferron
example.com {
    abuse_protection {
        allowlist "10.0.0.0/8" "192.168.1.1"
    }
}
```

Ferron never bans IPs in the `allowlist`, even if they exceed thresholds. This is useful for protecting internal networks, monitoring systems, or other trusted infrastructure.

**Configuration example, error rate threshold:**

```ferron
example.com {
    abuse_protection {
        ban_duration "15m"

        error_rate_threshold {
            events 10
            window "60s"
            status_codes "404" "403"
        }
    }
}
```

Ban after 10 error responses (404 or 403) within 60 seconds. This detects hostile scanning behavior such as probing for old vulnerabilities or non-existent plugin paths.

## `abuse_event`

```ferron
example.com {
    abuse_protection {
        ban_duration "15m"

        custom_threshold {
            events 5
            window "300s"
        }
    }

    match WORDPRESS_SCAN {
        request.uri.path ~ r"/wp-(?:admin|login)(?:.php$|/|$)"
    }

    if WORDPRESS_SCAN {
        abuse_event wordpress_scan # <-- register WordPress scanning
    }
}
```

You can place the `abuse_event` block inside an HTTP host block.

You can use this directive to define custom abuse protection rules. For example, it can protect your website from automated vulnerability scanners. It is also useful when configuring a honeypot within Ferron.

## Behavior

### Event tracking

The module tracks events per IP address and event type:

- **Rate limit events**: the rate limiting module emits them when a client exceeds its rate limit.
- **Brute force events**: the basic authentication module emits them when a client has repeated failed authentication attempts.
- **Custom events**: available for other modules (and `abuse_event` directive) to emit custom abuse events.
- **Error rate events**: the abuse protection module emits them when a response status code matches configured error codes. For example, 404 and 403. It uses `run_inverse` to observe responses after the pipeline completes.

The system stores events in a sliding time window. When the number of events within the window reaches the configured threshold, the system immediately bans the IP.

### Ban mechanics

- **Ban duration**: fixed duration (default 15 minutes). It is independent from the event counting window.
- **TTL-based expiry**: bans automatically expire after the configured duration. The system needs no background eviction threads.
- **Per-IP tracking**: the module tracks each IP address independently. It tracks different event types separately for the same IP.
- **No persistence**: the system stores bans in memory. They are not preserved across server restarts.

> [!tip]
> If the system bans your IP immediately, check configured thresholds. You may have `events 1` or `events 2`, or very short `window` values (for example, 10s) that are too aggressive. Reduce the `ban_duration` to shorten ban times, or increase the `events` threshold to require more violations before banning.

> [!warning]
> For manual unbanning, you must wait for the ban to expire naturally.

### Allowlist behavior

The stage skips any IP that matches an entry in the `allowlist` before it runs ban checks. This allows you to protect internal services, monitoring systems, or known-trusted infrastructure from accidental bans.

## Examples

### Basic abuse protection (defaults)

```ferron
example.com {
    abuse_protection
}
```

Uses default thresholds: bans IPs for 15 minutes if:

- 5 rate limit breaches in 5 minutes, OR
- 3 brute force failures in 2 minutes

### Disable abuse protection

```ferron
example.com {
    abuse_protection false
}
```

## Observability

### Metrics

The abuse protection module emits the following metrics:

| Metric                      | Type    | Attributes                                                                 | Description                                          |
| --------------------------- | ------- | -------------------------------------------------------------------------- | ---------------------------------------------------- |
| `ferron.abuseban.rejected`  | Counter | `ferron.abuseban.reason` (`"rate_limit"`, `"brute_force"`)                 | Requests rejected due to IP ban                      |
| `ferron.abuseban.triggered` | Counter | `ferron.abuseban.reason` (`"rate_limit"`, `"brute_force"`, `"error_rate"`) | Requests that triggered an IP ban                    |
| `ferron.abuseban.expired`   | Counter | `ferron.abuseban.reason`                                                   | IP bans that expired                                 |
| `ferron.abuseban.active_bans` | Gauge | None                                                                       | Current number of active IP bans                     |

### Logs

- **`WARN`**: logged when the module triggers a ban for an IP.
- **`INFO`**: logged once when a ban expires.
- Per-request ban rejections are **not** logged by default: under a ban wave they would exhaust the observability pipeline. Rejection volume remains visible via the `ferron.abuseban.rejected` counter. Set `log_rejections` to re-enable per-request `DEBUG` rejection logs for forensics.

### Structured logs

| Description (summary) | Level | Attributes                                                                                                                                                                |
| --------------------- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Ban rejection         | DEBUG | Only emitted when `log_rejections` is set. `client.address` (client IP address), `ferron.abuseban.reason` (`"rate_limit"`, `"brute_force"`), `ferron.abuseban.remaining_secs` (remaining seconds before ban expires) |
| Ban triggered         | WARN  | `client.address` (client IP address), `ferron.abuseban.reason` (`"rate_limit"`, `"brute_force"`)                                                                          |
| Ban expired           | INFO  | `client.address` (client IP address), `ferron.abuseban.reason`                                                                                                            |

### Access log fields

The abuse protection module contributes the following fields to the HTTP access log line:

| Field                            | Type   | Description                                               |
| -------------------------------- | ------ | --------------------------------------------------------- |
| `ferron.abuseban.action`         | string | Action taken: `skip` (not banned) or `rejected` (banned). |
| `ferron.abuseban.reason`         | string | Ban reason (rejection only).                              |
| `ferron.abuseban.remaining_secs` | int    | Seconds remaining on ban (rejection only).                |

### Trace spans

The abuse protection stage sets the following attributes on its `ferron.stage.abuse_protection` span:

| Attribute                        | Type   | Description                                                                         |
| -------------------------------- | ------ | ----------------------------------------------------------------------------------- |
| `ferron.abuseban.action`         | string | Action taken: `skip` (not banned) or `rejected` (banned).                           |
| `ferron.abuseban.reason`         | string | The reason for the ban, when rejected.                                              |
| `ferron.abuseban.remaining_secs` | int    | Remaining ban duration in seconds, when rejected.                                   |
| `error.type`                     | string | Set to `ip_banned` when Ferron rejects the request, enabling trace UI highlighting. |

## See also

- [Rate limiting](https://ferron.sh/docs/configuration/content/rate-limit.md)
- [HTTP basic authentication](https://ferron.sh/docs/configuration/security/basic-auth.md)
- [Routing and URL processing](https://ferron.sh/docs/configuration/routing/url-processing.md)

## Best practices

`ferron doctor` reports the following best-practice checks for directives on this page.

- **`abuse_protection false`**: Disabling IP banning for repeated abuse events removes a layer of protection. Keep it enabled unless another layer handles abusive clients.
- **`allowlist` with wildcard**: Exempting every source address from abuse protection should apply only to known trusted clients.