Traffic Control Middlewares
Plugins that decide whether a request makes it to the dispatcher at all — rate limits, CORS, IP allow/deny, bot patterns, payload size caps.
rate-limit— sliding-window request rate limitingcors— Cross-Origin Resource Sharingip-restriction— allow/deny by IP or CIDRbot-detection— User-Agent-based blockingrequest-size-limit— body-size cap
rate-limit
Limits request rate per client using a sliding window algorithm. Implements IETF draft-ietf-httpapi-ratelimit-headers.
x-barbacane-middlewares:
- name: rate-limit
config:
quota: 100
window: 60
policy_name: default
partition_key: client_ip
Configuration
| Property | Type | Default | Description |
|---|---|---|---|
quota | integer | required | Maximum requests allowed in the window |
window | integer | required | Window duration in seconds |
policy_name | string | default | Policy name for RateLimit-Policy header and the rate-limit bucket-key prefix |
partition_key | string | client_ip | Rate limit key source |
Partition key sources
client_ip— Client IP fromX-Forwarded-FororX-Real-IPheader:<name>— Header value (e.g.,header:X-API-Key)context:<key>— Context value set by an upstream middleware (e.g.,context:auth.sub)- Any static string — same limit for all requests sharing that string
Response headers
On allowed requests:
X-RateLimit-Policy— Policy name and configurationX-RateLimit-Limit— Maximum requests in windowX-RateLimit-Remaining— Remaining requestsX-RateLimit-Reset— Unix timestamp when window resets
The same four headers are also set on the request forwarded to the upstream. When instances are stacked, the response reports the policy with the fewest requests remaining, and a 429 from one of them carries only that policy’s RateLimit headers below, not the X-RateLimit-* of the policies that allowed the request.
On rate-limited requests (429):
RateLimit-Policy— IETF draft headerRateLimit— IETF draft combined headerRetry-After— Seconds until retry is allowed
Layered rate limits (stacking)
Stack multiple instances with **distinct policy_name**s to enforce layered limits — for example, a per-IP burst cap and a per-user daily budget:
x-barbacane-middlewares:
- name: rate-limit
config:
policy_name: per-ip-burst
quota: 100
window: 60
partition_key: client_ip
- name: rate-limit
config:
policy_name: per-user-daily
quota: 10000
window: 86400
partition_key: "context:auth.sub"
policy_name is also the bucket-key prefix. If two stacked instances share a policy_name, they share the bucket — only the tighter of the two will be effective. Always override policy_name when stacking.
cors
Handles Cross-Origin Resource Sharing per the Fetch specification. Processes preflight OPTIONS requests and adds CORS headers to responses.
x-barbacane-middlewares:
- name: cors
config:
allowed_origins:
- https://app.example.com
- https://admin.example.com
allowed_methods:
- GET
- POST
- PUT
- DELETE
allowed_headers:
- Authorization
- Content-Type
expose_headers:
- X-Request-ID
max_age: 86400
allow_credentials: false
Configuration
| Property | Type | Default | Description |
|---|---|---|---|
allowed_origins | array | [] | Allowed origins (["*"] for any, or specific origins) |
allowed_methods | array | ["GET", "POST"] | Allowed HTTP methods |
allowed_headers | array | [] | Allowed request headers (beyond simple headers) |
expose_headers | array | [] | Headers exposed to browser JavaScript |
max_age | integer | 3600 | Preflight cache time (seconds) |
allow_credentials | boolean | false | Allow credentials (cookies, auth headers) |
Origin patterns
Origins can be:
- Exact match:
https://app.example.com - Wildcard subdomain:
*.example.com(matchessub.example.com) - Wildcard:
*(only whenallow_credentials: false)
Error responses
403 Forbidden— Origin not in allowed list403 Forbidden— Method not allowed (preflight)403 Forbidden— Headers not allowed (preflight)
Preflight responses
Returns 204 No Content with:
Access-Control-Allow-OriginAccess-Control-Allow-MethodsAccess-Control-Allow-HeadersAccess-Control-Max-AgeVary: Origin, Access-Control-Request-Method, Access-Control-Request-Headers
ip-restriction
Allows or denies requests based on client IP address or CIDR ranges. Supports both allowlist and denylist modes.
x-barbacane-middlewares:
- name: ip-restriction
config:
allow:
- 10.0.0.0/8
- 192.168.1.0/24
deny:
- 10.0.0.5
message: "Access denied from your IP address"
status: 403
Configuration
| Property | Type | Default | Description |
|---|---|---|---|
allow | array | [] | Allowed IPs or CIDR ranges (allowlist mode) |
deny | array | [] | Denied IPs or CIDR ranges (denylist mode) |
message | string | Access denied | Custom error message for denied requests |
status | integer | 403 | HTTP status code for denied requests |
Behavior
- If
denyis configured, IPs in the list are blocked (denylist takes precedence) - If
allowis configured, only IPs in the list are permitted (allowlist mode) - Client IP is extracted from
X-Forwarded-For,X-Real-IP, or direct connection - Supports both single IPs (
10.0.0.1) and CIDR notation (10.0.0.0/8)
Error response
Returns Problem JSON (RFC 7807):
{
"type": "urn:barbacane:error:ip-restricted",
"title": "Forbidden",
"status": 403,
"detail": "Access denied",
"client_ip": "203.0.113.50"
}
bot-detection
Blocks requests from known bots and scrapers by matching the User-Agent header against configurable deny patterns. An allow list lets trusted crawlers bypass the deny list.
x-barbacane-middlewares:
- name: bot-detection
config:
deny:
- scrapy
- ahrefsbot
- semrushbot
- mj12bot
- dotbot
allow:
- Googlebot
- Bingbot
block_empty_ua: false
message: "Automated access is not permitted"
status: 403
Configuration
| Property | Type | Default | Description |
|---|---|---|---|
deny | array | [] | User-Agent substrings to block (case-insensitive substring match) |
allow | array | [] | User-Agent substrings that override the deny list (trusted crawlers) |
block_empty_ua | boolean | false | Block requests with no User-Agent header |
message | string | Access denied | Custom error message for blocked requests |
status | integer | 403 | HTTP status code for blocked requests |
Behavior
- Matching is case-insensitive substring:
"bot"matches"AhrefsBot","DotBot", etc. - The allow list takes precedence over deny: a UA matching both allow and deny is allowed through
- Missing
User-Agentis permitted by default; setblock_empty_ua: trueto block it - Both
denyandalloware empty by default — the plugin is a no-op unless configured
Error response
Returns Problem JSON (RFC 7807):
{
"type": "urn:barbacane:error:bot-detected",
"title": "Forbidden",
"status": 403,
"detail": "Access denied",
"user_agent": "scrapy/2.11"
}
The user_agent field is omitted when the request had no User-Agent header.
request-size-limit
Rejects requests that exceed a configurable body size limit. Checks both Content-Length header and actual body size.
x-barbacane-middlewares:
- name: request-size-limit
config:
max_bytes: 1048576 # 1 MiB
check_content_length: true
Configuration
| Property | Type | Default | Description |
|---|---|---|---|
max_bytes | integer | 1048576 | Maximum allowed request body size in bytes (default: 1 MiB) |
check_content_length | boolean | true | Check Content-Length header for early rejection |
Error response
Returns 413 Payload Too Large with Problem JSON:
{
"type": "urn:barbacane:error:payload-too-large",
"title": "Payload Too Large",
"status": 413,
"detail": "Request body size 2097152 bytes exceeds maximum allowed size of 1048576 bytes."
}