a7-plugin-limit-count
Overview
The limit-count plugin rate-limits requests using a fixed-window counter
algorithm. Define a maximum number of requests (count) within a time interval
(time_window). Supports per-IP, per-consumer, per-header, or custom variable
keys. For distributed API7 EE deployments, use Redis or Redis-cluster as the
shared counter backend.
When to Use
- Simple request counting (e.g., 100 requests per hour)
- API quota enforcement per consumer or API key
- Shared rate limits across multiple API7 EE nodes (via Redis)
- Grouped quotas across multiple routes
Plugin Configuration Reference
Core Fields
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
count | integer | Yes* | — | Max requests allowed in the time window. > 0 |
time_window | integer | Yes* | — | Time window in seconds. > 0 |
key_type | string | No | "var" | Key type: "var", "var_combination", or "constant" |
key | string | No | "remote_addr" | Variable name or combination for counting |
rejected_code | integer | No | 503 | HTTP status on rejection (200–599) |
rejected_msg | string | No | — | Custom rejection message body |
group | string | No | — | Share counters across routes with same group ID |
policy | string | No | "local" | Storage: "local", "redis", or "redis-cluster" |
show_limit_quota_header | boolean | No | true | Include X-RateLimit-* headers in responses |
allow_degradation | boolean | No | false | Allow requests when plugin fails |
*Required unless using rules array.
Redis Fields (when policy: "redis")
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
redis_host | string | Yes | — | Redis server address |
redis_port | integer | No | 6379 | Redis port |
redis_username | string | No | — | Redis ACL username |
redis_password | string | No | — | Redis password |
redis_database | integer | No | 0 | Redis database index |
redis_timeout | integer | No | 1000 | Timeout in milliseconds |
redis_ssl | boolean | No | false | Enable TLS to Redis |
Redis Cluster Fields (when policy: "redis-cluster")
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
redis_cluster_nodes | array[string] | Yes | — | Array of "host:port" (min 2) |
redis_cluster_name | string | Yes | — | Cluster name |
redis_password | string | No | — | Cluster password |
redis_timeout | integer | No | 1000 | Timeout in milliseconds |
redis_cluster_ssl | boolean | No | false | Enable TLS |
Key Types
key_type | key Format | Example | Description |
|---|---|---|---|
"var" | NGINX variable (no $) | "remote_addr" | Single variable |
"var_combination" | $var1 $var2 | "$remote_addr $consumer_name" | Multiple variables combined |
"constant" | Any string | "global" | Same counter for all requests |
Response Headers
When show_limit_quota_header: true (default):
| Header | Description |
|---|---|
X-RateLimit-Limit | Total quota for the time window |
X-RateLimit-Remaining | Remaining requests in current window |
X-RateLimit-Reset | Seconds until counter resets |
Step-by-Step: Basic Rate Limiting
1. Rate limit by client IP (route-level)
a7 route create -g default -f - <<'EOF'
{
"id": "rate-limited-api",
"uri": "/api/*",
"plugins": {
"limit-count": {
"count": 100,
"time_window": 60,
"key_type": "var",
"key": "remote_addr",
"rejected_code": 429,
"rejected_msg": "Rate limit exceeded. Try again later."
}
},
"upstream": {
"type": "roundrobin",
"nodes": [{"host": "backend", "port": 8080, "weight": 1}]
}
}
EOF
100 requests per 60 seconds per client IP.
2. Rate limit per consumer
a7 consumer create -g default -f - <<'EOF'
{
"username": "free-tier",
"plugins": {
"limit-count": {
"count": 100,
"time_window": 3600,
"rejected_code": 429
}
}
}
EOF
a7 consumer create -g default -f - <<'EOF'
{
"username": "premium",
"plugins": {
"limit-count": {
"count": 10000,
"time_window": 3600,
"rejected_code": 429
}
}
}
EOF
Consumer-level limits apply across all routes the consumer accesses.
Common Patterns
Shared quota across routes (group)
{
"plugins": {
"limit-count": {
"count": 1000,
"time_window": 3600,
"group": "api-v1",
"rejected_code": 429
}
}
}
All routes with "group": "api-v1" share the same 1000 req/hour counter.
Important: All routes in a group must have identical limit-count config.
Multi-variable key (IP + consumer)
{
"plugins": {
"limit-count": {
"count": 50,
"time_window": 60,
"key_type": "var_combination",
"key": "$remote_addr $consumer_name",
"rejected_code": 429
}
}
}
Global rate limit (all requests share one counter)
{
"plugins": {
"limit-count": {
"count": 10000,
"time_window": 60,
"key_type": "constant",
"key": "global",
"rejected_code": 429
}
}
}
Distributed rate limiting with Redis
{
"plugins": {
"limit-count": {
"count": 1000,
"time_window": 60,
"key": "remote_addr",
"policy": "redis",
"redis_host": "redis.example.com",
"redis_port": 6379,
"redis_password": "secret",
"redis_database": 0,
"redis_ssl": true,
"rejected_code": 429
}
}
}
Use Redis when running multiple API7 EE nodes to share counters.
Redis cluster
{
"plugins": {
"limit-count": {
"count": 1000,
"time_window": 60,
"key": "remote_addr",
"policy": "redis-cluster",
"redis_cluster_nodes": [
"192.168.1.10:6379",
"192.168.1.11:6379",
"192.168.1.12:6379"
],
"redis_cluster_name": "apisix-cluster",
"redis_password": "secret",
"rejected_code": 429
}
}
}
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Limits not shared across API7 EE nodes | Using policy: "local" (default) | Switch to "redis" or "redis-cluster" |
| Group config rejected | Mismatched configs in same group | Ensure all routes in group have identical limit-count config |
| Unexpected counter reset | Fixed-window boundary | Normal behavior — counters reset at fixed intervals |
| Key empty, all clients share one counter | Variable doesn't exist | Verify key variable name; falls back to remote_addr |
| Rate limit headers missing | show_limit_quota_header: false | Set to true (default) |
| 503 instead of 429 | Default rejected_code is 503 | Set rejected_code: 429 explicitly |
Fixed-Window Algorithm Note
limit-count uses a fixed-window algorithm. Counters reset at exact intervals.
This means a burst at the boundary of two windows can temporarily exceed the
intended rate (e.g., 100 req/min allows 200 requests if 100 come at t=59s and
100 at t=61s). For smoother rate limiting, combine with limit-req (leaky
bucket).
Config Sync Example
version: "1"
gateway_groups:
- name: default
consumers:
- username: free-tier
plugins:
limit-count:
count: 100
time_window: 3600
rejected_code: 429
routes:
- id: rate-limited-api
uri: /api/*
plugins:
limit-count:
count: 1000
time_window: 60
key: remote_addr
rejected_code: 429
upstream:
type: roundrobin
nodes:
- host: backend
port: 8080
weight: 1
This page is generated from a7-plugin-limit-count/SKILL.md in the api7/a7 repository. Browse all skills on the AI Agent Skills page.