Skip to content

Instantly share code, notes, and snippets.

@lcrilly
Created February 20, 2026 09:36
Show Gist options
  • Select an option

  • Save lcrilly/8c0838fbb7f28f5d4250fd35ed85b7c1 to your computer and use it in GitHub Desktop.

Select an option

Save lcrilly/8c0838fbb7f28f5d4250fd35ed85b7c1 to your computer and use it in GitHub Desktop.
Request quotas for NGINX

NGINX configuration to implement finite request quotas

This configuration implements finite request quotas. Without changes, each client IP address is limted to 10 requests per minute.

With each response, a header RateLimit is returned that indicates the remaining quota for that client. The format of this header follows the draft RFC for this field. https://datatracker.ietf.org/doc/draft-ietf-httpapi-ratelimit-headers/

NGINX JavaScript (njs) is used to manage the quota counter and perform the quota reset.

js_import quota.js;
js_shared_dict_zone zone=quotas:1M type=number;
map 1 $quota_qty {
default 10; # SET TO number of requests permitted within the quota period
}
map 1 $quota_key {
default $remote_addr; # SET to the key used to apply the quota (per consumer)
}
server {
location @quota_reset {
js_periodic quota.reset interval=60s; # SET TO the frequency of the quota period
}
listen 80;
location / {
auth_request /_quota_check;
auth_request_set $quota_remaining $sent_http_remaining;
error_page 403 = @too_many_requests;
add_header RateLimit '"defaut";r=$quota_remaining' always;
root /usr/share/nginx/html;
}
location = /_quota_check {
internal;
js_content quota.check;
}
location @too_many_requests {
add_header Content-Type application/json;
add_header RateLimit '"defaut";r=$quota_remaining' always;
return 429 '{"status":429","message":"Quota exhausted"}\n';
}
}
export default { check, reset }
function check(r) {
if (r.variables.quota_key == '') {
ngx.log(ngx.WARN, 'QUOTA KEY IS EMPTY');
r.status = 401;
} else {
if (ngx.shared.quotas.get(r.variables.quota_key) == undefined) {
ngx.log(ngx.WARN, 'QUOTA INIT [' + r.variables.quota_key + '] TO ' + r.variables.quota_qty);
ngx.shared.quotas.add(r.variables.quota_key, Number(r.variables.quota_qty - 1));
r.status = 204;
} else if (ngx.shared.quotas.get(r.variables.quota_key) < 1) {
ngx.log(ngx.WARN, 'QUOTA EXHAUSTED FOR ' + r.variables.quota_key);
r.status = 403;
} else {
ngx.shared.quotas.incr(r.variables.quota_key, -1);
r.status = 204;
}
}
r.headersOut.remaining = ngx.shared.quotas.get(r.variables.quota_key);
r.finish();
}
function reset(r) {
ngx.log(ngx.WARN, 'QUOTAS RESET');
ngx.shared.quotas.clear();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment