レート制限

OxPHP はIP単位のレート制限を組み込みで備えているため、外部依存や運用が必要なインフラはありません。有効化すると、クライアントIPごとにリクエスト数を追跡し、設定したしきい値をクライアントが超えた時点で 429 Too Many Requests レスポンスを返します。

仕組み

レート制限は、クライアントのIPアドレスをキーとする固定ウィンドウカウンターを使用します。各IPはそれぞれ独立したカウンターとウィンドウを持ちます。

  1. リクエストが到着すると、OxPHP は内部トラッカーからクライアントIPを検索します。
  2. エントリが存在しない場合、または現在のウィンドウが期限切れの場合は、カウンターをゼロにして新しいウィンドウが開始されます。
  3. リクエストごとにカウンターが加算されます。
  4. カウンターが RATE_LIMIT を超えると、サーバーはただちにレート制限ヘッダー付きの 429 レスポンスを返します。リクエストはルーティングやPHPの実行より前に拒否されます。

レート制限されたリクエストも、アクセスログとメトリクスには引き続き記録されます。

設定

Variable Default Description
RATE_LIMIT 0 ウィンドウ内でIPごとに許可する最大リクエスト数。0 はレート制限を完全に無効化し、オーバーヘッドはゼロになります
RATE_WINDOW_SECONDS 60 レート制限ウィンドウの長さ(秒)
bash
# Allow 100 requests per IP per 60-second window RATE_LIMIT=100 RATE_WINDOW_SECONDS=60

レスポンスヘッダー

拒否されたリクエストは、以下のヘッダーを含む 429 Too Many Requests レスポンスを返します。

Header Description
Retry-After 現在のウィンドウがリセットされるまでの秒数
x-ratelimit-limit ウィンドウあたりに許可される最大リクエスト数
x-ratelimit-remaining 現在のウィンドウで残っているリクエスト数(レート制限中は 0
x-ratelimit-reset 現在のウィンドウがリセットされるまでの秒数
x-request-id このレスポンスをアクセスログと対応付けるためのリクエストID

429 レスポンスの例:

http
HTTP/1.1 429 Too Many Requests Retry-After: 45 x-ratelimit-limit: 100 x-ratelimit-remaining: 0 x-ratelimit-reset: 45 x-request-id: 67e2a1f412341a2b0042 429 Too Many Requests

トラブルシューティング

正当なユーザーがレート制限されてしまう

しきい値が実際のトラフィックパターンに対して低すぎる可能性があります。メトリクスで429レスポンスの発生率を確認し、それに応じて RATE_LIMIT または RATE_WINDOW_SECONDS を調整してください。

レート制限されたリクエスト数を 確認 します:

bash
curl http://localhost:9090/metrics | grep rate_limited

対処: RATE_LIMIT を増やすか、RATE_WINDOW_SECONDS を延長して、クライアントに余裕を持たせます。

企業のNAT配下のユーザーが同じIPカウンターを共有してしまう

OxPHP は送信元IPでレート制限を行います。共有されたNATやプロキシの配下にいるすべてのユーザーは、1つのカウンターを共有します。これが問題になる場合は、OxPHP の組み込みリミッター(RATE_LIMIT=0)を無効化し、ユーザー識別子にアクセスできる上位レベル(ロードバランサーやAPIゲートウェイなど)でレート制限を適用することを検討してください。

リバースプロキシの背後にある場合は?

TRUSTED_PROXIES を設定して、レート制限がプロキシのIPではなく実際のクライアントIPを使用するようにしてください。信頼済みプロキシを参照してください。

複数インスタンスをまたいでレート制限が機能しない

OxPHP のレート制限はインメモリかつインスタンス単位です。ロードバランサーの背後で複数の OxPHP インスタンスを実行する場合、各インスタンスはそれぞれ独立したカウンターを追跡します。クライアントは各インスタンスに対して RATE_LIMIT 件のリクエストを送ることができ、429を発生させずに済んでしまいます。インスタンスをまたいで協調したレート制限を行うには、ロードバランサーやAPIゲートウェイのレベルで外部リミッターを使用してください。

IPローテーション攻撃でメモリが増加する

OxPHP は最大100,000件のユニークなIPアドレスを追跡します。この上限に達すると、新しいエントリを追加する前に期限切れのエントリが削除されます。攻撃者がIPを高速にローテーションさせることでメモリが増加するのを観測した場合でも、自動クリーンアップによって影響は一定量のメモリに抑えられます。

Docker の例

compose.yaml
services: app: image: ghcr.io/oxphp/oxphp:0.10.0 ports: - "8080:80" environment: RATE_LIMIT: "100" RATE_WINDOW_SECONDS: "60" volumes: - ./app:/var/www/html:ro

ベストプラクティス

  • 控えめに始める。 低めの上限(例: 毎分60リクエスト)から始め、観測されたトラフィックパターンに基づいて増やしていきます。上限を緩めるほうが、過負荷になったサーバーから復旧するよりも簡単です。
  • 複数インスタンスのデプロイでは共有レート制限を使う。 OxPHP のレート制限はインスタンス単位です。インスタンスをまたいで協調した制限を行うには、ロードバランサーやAPIゲートウェイのレベルでレート制限を適用してください。
  • 429レスポンス率を監視する。 メトリクスでレート制限されたリクエストの割合を追跡し、しきい値の設定ミスや予期しないトラフィックの急増を検知します。

補足

  • 固定ウィンドウアルゴリズム。 リミッターはスライディングウィンドウではなく固定ウィンドウカウンターを使用します。クライアントは2つのウィンドウの境界において、バーストで設定上限の 2x までのリクエストを送ることができます。
  • IP単位のみ。 レート制限は送信元IPアドレスをキーとします。APIキーやユーザーIDといったカスタムキーはサポートされていません。
  • インメモリの状態。 レート制限のカウンターは複数の OxPHP インスタンス間で共有されません。
  • 自動クリーンアップ。 トラッカーが100,000件のIPを超えると期限切れのエントリがクリーンアップされ、ウィンドウが期限切れになったすべてのエントリが削除されます。

関連項目