a7-plugin-limit-req
Overview
The limit-req plugin rate-limits requests using the leaky bucket algorithm.
Unlike limit-count (fixed window), it provides smooth traffic shaping by
throttling burst requests with configurable delays. This prevents traffic spikes
from overwhelming upstream services.
When to Use
- Smooth traffic to protect upstream from sudden spikes
- Enforce per-second QPS limits
- Throttle (delay) excess requests instead of rejecting them immediately
- Combine with
limit-countfor both per-second and per-hour limits
Plugin Configuration Reference
Core Fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
rate | number | Yes | — | Sustained requests per second (QPS). > 0 |
burst | number | Yes | — | Extra burst capacity above rate. >= 0 |
key | string | Yes | — | Variable to count requests by |
key_type | string | No | "var" | Key type: "var" or "var_combination" |
rejected_code | integer | No | 503 | HTTP status on rejection (200–599) |
rejected_msg | string | No | — | Custom rejection message body |
nodelay | boolean | No | false | If true, don't delay burst requests |
allow_degradation | boolean | No | false | Allow requests when plugin fails |
policy | string | No | "local" | Storage: "local", "redis", or "redis-cluster" |
Redis Fields (when policy: "redis")
| Field | Type | Required | Default |
|---|---|---|---|
redis_host | string | Yes | — |
redis_port | integer | No | 6379 |
redis_username | string | No | — |
redis_password | string | No | — |
redis_database | integer | No | 0 |
redis_timeout | integer | No | 1000 |
redis_ssl | boolean | No | false |
Redis Cluster Fields (when policy: "redis-cluster")
| Field | Type | Required | Default |
|---|---|---|---|
redis_cluster_nodes | array[string] | Yes | — |
redis_cluster_name | string | Yes | — |
redis_password | string | No | — |
redis_timeout | integer | No | 1000 |
redis_cluster_ssl | boolean | No | false |
Leaky Bucket Algorithm
Incoming requests → [ Bucket (burst capacity) ] → Leak at 'rate' per second → Upstream
↓ overflow (> rate + burst)
Rejected (503/429)
| Request Rate | Behavior |
|---|---|
≤ rate | Processed immediately |
> rate but ≤ rate + burst | Delayed (smoothed) if nodelay: false; immediate if nodelay: true |
> rate + burst | Rejected with rejected_code |
nodelay Explained
nodelay: false(default): Burst requests are delayed (API7 EE sleeps) to smooth traffic. Higher latency for burst requests but protects upstream.nodelay: true: Burst requests are processed immediately without delay. Better latency but upstream sees spikes up torate + burst.
Step-by-Step: Basic Rate Limiting
1. Strict QPS limit (no burst)
a7 service create -g default -f - <<'EOF'
{
"id": "rate-limit-backend",
"name": "rate-limit-backend",
"upstream": {
"type": "roundrobin",
"nodes": [{"host": "backend", "port": 8080, "weight": 1}]
}
}
EOF
a7 route create -g default -f - <<'EOF'
{
"id": "strict-qps",
"uri": "/api/*",
"service_id": "rate-limit-backend",
"plugins": {
"limit-req": {
"rate": 10,
"burst": 0,
"key": "remote_addr",
"rejected_code": 429,
"nodelay": true
}
}
}
EOF
10 requests per second per IP. Anything above is immediately rejected.
2. Smooth traffic with burst allowance
a7 service create -g default -f - <<'EOF'
{
"id": "smooth-api-backend",
"name": "smooth-api-backend",
"upstream": {
"type": "roundrobin",
"nodes": [{"host": "backend", "port": 8080, "weight": 1}]
}
}
EOF
a7 route create -g default -f - <<'EOF'
{
"id": "smooth-api",
"uri": "/api/*",
"service_id": "smooth-api-backend",
"plugins": {
"limit-req": {
"rate": 5,
"burst": 10,
"key": "remote_addr",
"rejected_code": 429
}
}
}
EOF
- 5 req/s sustained rate
- Up to 10 extra burst requests (delayed to smooth traffic)
- Requests above 15/s rejected with 429
Common Patterns
Multi-variable key
{
"plugins": {
"limit-req": {
"rate": 10,
"burst": 5,
"key_type": "var_combination",
"key": "$remote_addr $http_x_api_version",
"rejected_code": 429
}
}
}
Separate buckets per (IP + API version header) combination.
Combine limit-req + limit-count
{
"plugins": {
"limit-req": {
"rate": 10,
"burst": 20,
"key": "remote_addr",
"rejected_code": 429
},
"limit-count": {
"count": 1000,
"time_window": 3600,
"key": "remote_addr",
"rejected_code": 429
}
}
}
limit-req: Smooths per-second traffic (10 QPS with burst)limit-count: Enforces hourly quota (1000/hour)
This prevents both short-term spikes and long-term abuse.
Distributed rate limiting with Redis
{
"plugins": {
"limit-req": {
"rate": 100,
"burst": 50,
"key": "remote_addr",
"policy": "redis",
"redis_host": "redis.example.com",
"redis_port": 6379,
"redis_password": "secret",
"rejected_code": 429
}
}
}
limit-req vs limit-count
| Aspect | limit-req | limit-count |
|---|---|---|
| Algorithm | Leaky bucket | Fixed window |
| Unit | Requests per second | Requests per time window |
| Burst handling | Delays or allows | Hard reject |
| Traffic shaping | Smooth | Bursty at window boundaries |
| Response headers | None | X-RateLimit-* |
| Group support | No | Yes |
| Best for | QPS protection, traffic shaping | Quota enforcement, API plans |
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| High latency on burst | nodelay: false delays requests | Set nodelay: true for lower latency |
| All burst requests rejected | burst: 0 | Increase burst to allow some excess |
| No rate limit headers | limit-req doesn't add headers | Use limit-count if headers needed |
| Limits not shared across API7 EE nodes | policy: "local" | Switch to "redis" or "redis-cluster" |
| Key empty, single bucket for all | Variable doesn't exist | Verify key variable name |
Config Sync Example
version: "1"
services:
- id: smooth-api-backend
name: smooth-api-backend
upstream:
type: roundrobin
nodes:
- host: backend
port: 8080
weight: 1
routes:
- id: smooth-api
uri: /api/*
service_id: smooth-api-backend
plugins:
limit-req:
rate: 10
burst: 20
key: remote_addr
rejected_code: 429
This page is generated from a7-plugin-limit-req/SKILL.md in the api7/a7 repository. Browse all skills on the AI Agent Skills page.