Skip to main content

limit-count

Limits the number of requests in a time window, per key.

Category TrafficAdd it to Route, Service, Consumer, Consumer group, Global rule, Plugin configEngine plugin limit-countPriority 1002

What it does​

limit-count counts the requests that reach the route or other object it is added to, under a key. The key is the value that tells callers apart: by default the client address, remote_addr, or another request variable like the name of the consumer an authentication plugin found, consumer_name. The key is what requests are counted by, not an API key. While a key is within its Count for the Time window, requests pass, and every answer carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset.

The first request over the count is answered with the Rejected code, 503 unless you set another, and with your Rejected message in a JSON body if you wrote one. Each gateway node keeps its own count unless Policy points at Redis, where the nodes share one. With Policy local, once one node has refused an address, another node still lets it through.

When to use it​

  • A quota per client address or per consumer, for example 100 requests a minute.
  • Several limits on one object at once, each counted under its own key, for example one per consumer and one per client address. A request has to stay within every limit that applies to it.
  • A quota that several gateway nodes count together, through Redis.
  • One quota shared by several routes or services: give them the same Group.

Not for smoothing a burst to a steady rate — see limit-req (Limits the request rate with a leaky bucket) or capping how many requests are in flight at once — see limit-conn (Limits concurrent connections per key).

Guides​

Pages that walk through a job with limit-count, step by step.

Set it up​

Allow 100 requests a minute for each client address​

Under Basics choose One limit for everything here, set Count to 100, set Time window to 60 seconds and leave Key at remote_addr. Under Behavior set Rejected code to 429 and set Rejected message to Too many requests, try again in a minute.

The console's Add plugin panel, filled with this example.
limit-count · as the console saves it
{
"count": 100,
"time_window": 60,
"key": "remote_addr",
"rejected_code": 429,
"rejected_msg": "Too many requests, try again in a minute"
}

What you will see​

  • A request within the count reaches your backend, and its answer carries X-RateLimit-Limit: 100, X-RateLimit-Remaining with the requests left in this window, and X-RateLimit-Reset with the seconds left in it.
  • The window opens with an address's first request, not at a round minute on the clock: that first answer carries X-RateLimit-Reset: 60, the seconds until the window ends.
  • The 101st request in a window from one address is answered 429 with {"error_msg":"Too many requests, try again in a minute"}; its headers show X-RateLimit-Remaining: 0 and X-RateLimit-Reset, the seconds until the window ends.
  • With the default Policy each gateway node counts on its own: once one node has refused an address, the same request to another node is answered with X-RateLimit-Remaining: 99.

More scenarios​

Allow 100 requests a minute for each consumer

Needs an authentication plugin on the same route, such as key-auth, which names the consumer a request belongs to.

Under Basics choose One limit for everything here, set Count to 100, set Time window to 60 seconds and set Key to consumer_name. Under Behavior set Rejected code to 429 and set Rejected message to Too many requests, try again in a minute.

limit-count · as the console saves it
{
"count": 100,
"time_window": 60,
"key": "consumer_name",
"rejected_code": 429,
"rejected_msg": "Too many requests, try again in a minute"
}

What you will see​

  • Each consumer has 100 requests a minute of its own: once one has used them, its next request is answered 429 with {"error_msg":"Too many requests, try again in a minute"}, while another consumer calling from the same address still gets 200 with X-RateLimit-Remaining: 99.
Share one quota between several routes

Every route in the group carries exactly these settings. To change a group’s settings, give it a new name: changed settings under the same name may be refused when you save, or accepted while part of the gateway goes on counting with the old ones.

Under Basics choose One limit for everything here, set Count to 100, set Time window to 60 seconds and leave Key at remote_addr. Under Behavior set Rejected code to 429 and set Rejected message to Too many requests, try again in a minute. Under Advanced set Group to orders.

limit-count · as the console saves it
{
"count": 100,
"time_window": 60,
"key": "remote_addr",
"group": "orders",
"rejected_code": 429,
"rejected_msg": "Too many requests, try again in a minute"
}

What you will see​

  • Every answer carries X-RateLimit-Limit and X-RateLimit-Remaining, and the requests of every route in the group count against the same figures.
  • Once this route has used up the count, a request to another route in the group is answered 429 with {"error_msg":"Too many requests, try again in a minute"}.
Share the request count through a Redis master that sentinels watch

Under Basics choose One limit for everything here, set Count to 1000, set Time window to 60 seconds and leave Key at remote_addr. Under Behavior set Policy to redis-sentinel, set Redis master name to gateway-limits, set Redis password to <secret> and fill Redis sentinels as in the JSON below.

limit-count · as the console saves it
{
"count": 1000,
"time_window": 60,
"key": "remote_addr",
"policy": "redis-sentinel",
"redis_sentinels": [
{
"host": "sentinel-1.internal",
"port": 26379
},
{
"host": "sentinel-2.internal",
"port": 26379
}
],
"redis_master_name": "gateway-limits",
"redis_password": "<secret>"
}

What you will see​

  • Every answer carries X-RateLimit-Limit. A request beyond the count is answered 503 with an HTML error page, because this example sets no Rejected code and no Rejected message.
Share the request count across gateway nodes through a Redis cluster

Under Basics choose One limit for everything here, set Count to 1000, set Time window to 60 seconds and leave Key at remote_addr. Under Behavior set Policy to redis-cluster, set Redis cluster name to gateway-limits, fill Redis cluster nodes as in the JSON below and set Redis password to <secret>.

limit-count · as the console saves it
{
"count": 1000,
"time_window": 60,
"key": "remote_addr",
"policy": "redis-cluster",
"redis_cluster_name": "gateway-limits",
"redis_cluster_nodes": [
"redis-1.internal:6379",
"redis-2.internal:6379",
"redis-3.internal:6379"
],
"redis_password": "<secret>"
}

What you will see​

  • Every answer carries X-RateLimit-Limit. A request beyond the count is answered 503 with an HTML error page, because this example sets no Rejected code and no Rejected message.
Share the request count across gateway nodes through one Redis server

Under Basics choose One limit for everything here, set Count to 1000, set Time window to 60 seconds and leave Key at remote_addr. Under Behavior set Policy to redis, set Redis host to redis.internal, set Redis password to <secret> and leave Redis port at 6379.

limit-count · as the console saves it
{
"count": 1000,
"time_window": 60,
"key": "remote_addr",
"policy": "redis",
"redis_host": "redis.internal",
"redis_port": 6379,
"redis_password": "<secret>"
}

What you will see​

  • Every answer carries X-RateLimit-Limit. A request beyond the count is answered 503 with an HTML error page, because this example sets no Rejected code and no Rejected message.
  • Every gateway node reads the same count: once one node has refused an address, the next request from it to another node is answered 503 as well.
Give each consumer a limit of its own and each client address another

Needs an authentication plugin on the same route, which sets the consumer name. Every rule whose key the request carries counts it, so a consumer is held to its address limit too, and a request without a consumer is counted by its address alone.

Under Basics choose A limit for each key and fill Rules as in the JSON below.

limit-count · as the console saves it
{
"rules": [
{
"count": 1000,
"time_window": 60,
"key": "$consumer_name",
"header_prefix": "Consumer"
},
{
"count": 100,
"time_window": 60,
"key": "$remote_addr",
"header_prefix": "Address"
}
]
}

What you will see​

  • Every answer carries X-Consumer-RateLimit-Limit: 1000 and X-Address-RateLimit-Limit: 100: one set of headers for each rule, named by its Header prefix.
Name the headers that tell clients how much of their quota is leftenvironment-wide settings

Set Limit header to RateLimit-Limit, set Remaining header to RateLimit-Remaining and set Reset header to RateLimit-Reset.

limit-count environment-wide settings · as the console saves it
{
"limit_header": "RateLimit-Limit",
"remaining_header": "RateLimit-Remaining",
"reset_header": "RateLimit-Reset"
}

What you will see​

  • Every answer carries the quota under RateLimit-Limit, what is left of it under RateLimit-Remaining and the seconds until the window ends under RateLimit-Reset, the names set here.

Things to know​

  • Rejected code — Set a Rejected code. Unset, a refused request is answered 503 with an HTML error page rather than JSON, the answer of a service that is unavailable. 429 tells the client it sent too many requests.
  • Rejected message — With a Rejected message, the body of a refused request is JSON with one field, error_msg, for example {"error_msg":"Too many requests, try again in a minute"}.
  • Key — The client address is the address a request comes from, so callers behind one proxy or load balancer share a single count. A request that names another client in X-Forwarded-For is still counted under the address it comes from. Count per consumer, or add real-ip with Trusted addresses set to the load balancer's address, to tell those callers apart.
  • Key — A limit per consumer needs an authentication plugin on the same route; it is the one that sets consumer_name.
  • Key — A variable the request does not carry, or a name with a dollar sign under Variable, is saved without a word, and every request is then counted by client address. So is a combination in which no $name resolves.
  • Window type — With Window type fixed, the default, a key's window opens with its first request, whatever the time on the clock: that answer carries X-RateLimit-Reset: 60, the seconds until the window ends.
  • Count — Count and Time window take a whole number, a $variable like $http_x_quota, or a $env:// or $secret:// reference. Any other text is refused when you save.
  • Count — A header variable like $http_x_quota carries whatever the client sends, so a client could choose its own limit.
  • Rules — A rule's Key is a text with at least one $variable: $remote_addr, not remote_addr. A key that names none is refused when you save.
  • Rules — Two rules with the same Key are refused when you save.
  • Group — Group cannot be set beside Rules; the save is refused.
  • Group — To change a group’s settings, give it a new name: changed settings under the same name may be refused when you save, or accepted while part of the gateway goes on counting with the old ones.
  • Sync interval — With a Redis policy, Sync interval must be smaller than Time window, or the save is refused.
  • Allow degradation — When the limit cannot be counted, because the store is unreachable or a value resolves to nothing, off answers 500 and on lets every request through unlimited.

Settings​

The fields as the console shows them, section by section. A field under “When … is …” is there only while that value is set.

Basics​

Choose one:

  • One limit for everything hereA count and a window, counted under the key below for every request this object serves.
  • A limit for each keyA list of rules, each counting the key it names with a count and a window of its own.

One limit for everything here​

Countcountinteger or stringRequired

How many requests one key may make in each window: a number, or a $variable read from every request, such as $http_x_quota. A header variable such as $http_x_quota carries whatever the client sends, so a client could choose its own limit.

integer over 0 · a whole number above zero, a $variable, or a $secret:// or $env:// reference
pattern^([0-9]*[1-9][0-9]*|[$](env|secret)://.+|[$][{][^}]*[}]|[$][A-Za-z_][A-Za-z0-9_.]*)$
Time windowtime_windowinteger or stringRequired

How long each window lasts: a number, or a $variable read from every request, such as $http_x_window. A header variable such as $http_x_window carries whatever the client sends, so a client could choose its own window.

integer over 0 · in seconds · a whole number above zero, a $variable, or a $secret:// or $env:// reference
pattern^([0-9]*[1-9][0-9]*|[$](env|secret)://.+|[$][{][^}]*[}]|[$][A-Za-z_][A-Za-z0-9_.]*)$

A limit for each key​

Rulesrulesarray of objectsRequired
at least 1 item
Each item:
Countrules[].countinteger or stringRequired

How many requests one key may make in each window of this rule: a number, or a $variable read from every request.

integer over 0 · a whole number above zero, a $variable, or a $secret:// or $env:// reference
pattern^([0-9]*[1-9][0-9]*|[$](env|secret)://.+|[$][{][^}]*[}]|[$][A-Za-z_][A-Za-z0-9_.]*)$
Keyrules[].keystringRequired
a $variable, with or without braces
pattern[$]([{][^}]+[}]|[A-Za-z_][A-Za-z0-9_.]*)
Time windowrules[].time_windowinteger or stringRequired

How long each window of this rule lasts: a number, or a $variable read from every request.

integer over 0 · in seconds · a whole number above zero, a $variable, or a $secret:// or $env:// reference
pattern^([0-9]*[1-9][0-9]*|[$](env|secret)://.+|[$][{][^}]*[}]|[$][A-Za-z_][A-Za-z0-9_.]*)$
Header prefixrules[].header_prefixstring

The prefix the rate-limit headers of this rule carry.

Key typekey_typestringdefault var

Variable reads one request variable by name, Variable combination expands every $name in the text into one key, and Constant counts every request together.

one of constant, var, var_combination
Keykeystringdefault remote_addr

The request variable whose value tells callers apart, such as remote_addr or consumer_name, written without its dollar sign; under Variable combination, text with $names.

Behavior​

Policypolicystringdefault local
one of local, redis, redis-cluster, redis-sentinel

When Policy is redis​

Redis databaseredis_databaseintegerdefault 0
at least 0
Redis hostredis_hoststringRequired
at least 2 characters
Redis keepalive poolredis_keepalive_poolintegerdefault 100
at least 1
Redis keepalive timeoutredis_keepalive_timeoutintegerdefault 10000
at least 1000 · in milliseconds
Redis usernameredis_usernamestring
at least 1 character
Redis passwordredis_passwordstring

Stored on the engine and shown only to roles that manage this object.

Redis portredis_portintegerdefault 6379
at least 1
Redis SSLredis_sslbooleandefault false
Redis SSL verifyredis_ssl_verifybooleandefault false
Server name for TLSredis_server_namestring

The server name sent in the TLS handshake and, with verification on, checked against the certificate; unset, the host is sent, and an address never is.

at least 1 character
Redis timeoutredis_timeoutintegerdefault 1000
at least 1 · in milliseconds
When Policy is redis-cluster· 8 fields
Redis cluster nameredis_cluster_namestringRequired
Redis cluster nodesredis_cluster_nodesarray of stringsRequired
at least 1 item; each 2 to 100 characters
Redis cluster SSLredis_cluster_sslbooleandefault false
Redis cluster SSL verifyredis_cluster_ssl_verifybooleandefault false
Redis keepalive poolredis_keepalive_poolintegerdefault 100
at least 1
Redis keepalive timeoutredis_keepalive_timeoutintegerdefault 10000
at least 1000 · in milliseconds
Redis passwordredis_passwordstring

Stored on the engine and shown only to roles that manage this object.

Redis timeoutredis_timeoutintegerdefault 1000
at least 1 · in milliseconds
When Policy is redis-sentinel· 13 fields
Redis connect timeoutredis_connect_timeoutintegerdefault 1000
at least 1 · in milliseconds
Redis databaseredis_databaseintegerdefault 0
at least 0
Redis keepalive timeoutredis_keepalive_timeoutintegerdefault 60000
at least 1 · in milliseconds
Redis master nameredis_master_namestringRequired
at least 1 character
Redis usernameredis_usernamestring
at least 1 character
Redis passwordredis_passwordstring

Stored on the engine and shown only to roles that manage this object.

Redis read timeoutredis_read_timeoutintegerdefault 1000
at least 1 · in milliseconds
Redis roleredis_rolestringdefault master
one of master, slave
Redis sentinelsredis_sentinelsarray of objectsRequired
at least 1 item
Each item:
Hostredis_sentinels[].hoststringRequired
at least 2 characters
Portredis_sentinels[].portintegerRequired
1 to 65535
Sentinel usernamesentinel_usernamestring
at least 1 character
Sentinel passwordsentinel_passwordstring

Stored on the engine and shown only to roles that manage this object.

Window typewindow_typestringdefault fixed
one of fixed, sliding
Rejected coderejected_codeintegerdefault 503
200 to 599
Rejected messagerejected_msgstring
at least 1 character
Show limit quota headershow_limit_quota_headerbooleandefault true
Allow degradationallow_degradationbooleandefault false

Advanced​

Groupgroupstring

Routes and services that carry the same group share one counter, so their limit settings must be the same. To change a group’s settings, give it a new name: changed settings under the same name may be refused when you save, or accepted while part of the gateway goes on counting with the old ones.

Sync intervalsync_intervalnumberdefault -1
in seconds

Environment-wide settings​

Set once per environment, under Plugins › limit-count. Every limit-count in the environment uses these.

Limit headerlimit_headerstringdefault X-RateLimit-Limit
Remaining headerremaining_headerstringdefault X-RateLimit-Remaining
Reset headerreset_headerstringdefault X-RateLimit-Reset

Every plugin also has Enabled, Priority, Filter, Error response and Pre-function: see Plugin execution settings.

G8 dev · Apache APISIX® 3.19.0
Apache, Apache APISIX and APISIX are trademarks of The Apache Software Foundation.
Facts
Category
Traffic
Add it to
Route, Service, Consumer, Consumer group, Global rule, Plugin config
Engine plugin
limit-count
Priority
1002