コンテンツにスキップ

レートを制限する

nginxのlimit_reqは、すべてのワーカーが使う共有メモリのゾーンで、キーごとにリクエストを数え、レートを超えたリクエストを拒否します。キーには、nginxのどの変数でも使えます。Rubyのスクリプトがmruby_setでキーを計算すれば、APIキーのような、Rubyがリクエストから取り出した値ごとに制限をかけられます。カウンタはnginxが持ちます。

スクリプトは、リクエストヘッダX-API-KeyのAPIキーを返します。ヘッダの無いリクエストではクライアントのアドレスを返します。こうして、どのリクエストもキーが空になりません。limit_reqは、キーが空のリクエストを数えないからです。ゾーンは、キーごとに1分に1リクエストを許します。nginxは、limit_req_status 429とerror_page 429 @limitedに従って、拒否したリクエストのレスポンスをステータス429とJSONのボディにします。

test/conf/conf.d/75-guide-rate-limit.conf
limit_req_zone $api_key zone=api_keys:1m rate=1r/m;
server {
listen 18166;
location /api/ {
mruby_set $api_key build/nginx/html/guide_rate_limit.rb;
limit_req zone=api_keys;
limit_req_status 429;
error_page 429 @limited;
mruby_content_handler_code 'Nginx.rputs "accepted\n"';
}
location @limited {
default_type application/json;
return 429 '{"error":"rate_limited"}\n';
}
}
test/html/guide_rate_limit.rb
r = Nginx::Request.new
key = r.headers_in["X-API-Key"].to_s
key.empty? ? "addr:#{r.var.remote_addr}" : "key:#{key}"
test/html/guide_rate_limit.request:ケースが送るリクエスト
GET /api/orders HTTP/1.1
Host: 127.0.0.1:18166
X-API-Key: demo-key
Connection: close
test/html/guide_rate_limit.response:ケースが比べるレスポンス
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
{"error":"rate_limited"}

CIが動くたびに、ケースのtest/t/cases/guides.rbがこのリクエストを送ります。ケースは、ステータス行、ここに載せたヘッダ、ボディ全体を比べます。5個のファイルを最後に変えたのは、2026-10-29のコミット32219e53です。

ケースはリクエストを2回送ります。1回目のレスポンスは、ボディがacceptedの200です。同じ1分のうちに送った2回目のリクエストには、上のレスポンスが返ります。

  • mruby_setはrewriteフェーズで動き、limit_reqはその後のpreaccessフェーズで動きます。そのため、nginxがリクエストを数えるときには、変数に値が入っています。フェーズの順はリクエストのフェーズにあります。
  • カウンタは、Rubyではなくnginxの共有メモリのゾーンにあります。各ワーカーは自分のmrb_stateを持つので、Rubyのグローバル変数のカウンタでは、1つのワーカーのリクエストしか数えられません。
  • limit_req_zoneはhttpブロックのディレクティブです。limit_reqとlimit_req_statusは、httpブロック、server、locationに書けます。