How to Rate Limit Requests to Specific PHP Files with Nginx

WordPress login and XML-RPC endpoints at wp-login.php and xmlrpc.php respectively are the mostly widely abused targets for bots scanning the web for sites with weak passwords. Here is a simple Nginx configuration snippet that allows you to set the limit_req depending on the request path and method.

First, define the limit_req_zone at the http {} level and conditionally enable it only for POST requests to the desired request URIs by setting the limit request zone input to $binary_remote_addr while keeping it undefined for all the un-mapped requests:

map "$request_method:$uri" $wp_post_limit_key {
	POST:/wp-login.php $binary_remote_addr;
	POST:/xmlrpc.php $binary_remote_addr;
}

limit_req_zone $wp_post_limit_key zone=wp_post_limit_zone:10m rate=5r/m;

where:

  • $wp_post_limit_key is our custom variable name that is either unset by default or equal to $binary_remote_addr when the current request should be rate-limited.
  • wp_post_limit_zone is our custom zone name that must be referenced in limit_req of each server {} block.
  • rate=5r/m sets the allowed request rate from the same IP to 5 requests per minute. You can change it to 2r/s if you wanted to permit 2 request per second.

And then add it to the PHP location in each of your server {} blocks:

location ~ \.php$ {
	try_files $uri =404;
        limit_req zone=wp_post_limit_zone;
	// ...
}

You can define multiple limit_req_zone configurations for different request mapping logic and apply them all to the same PHP location block.

Adjust the HTTP Response Code

By default Nginx responds with HTTP 503 Service Unavailable status code to all throttled requests. Use the limit_req_status directive to change it to 429 Too Many Requests in either http {} or server {} block:

limit_req_status 429; 

2 Comments

  1. Josh Betz says:

    Oh! I like this use of the map with the method and URL.

    • Kaspars says:

      Right!? I have been using it to also detect WebP support via $http_accept and then append any image requests with .webp as they get generated on upload.

Leave a Reply