Reputation: 320
I'm trying to implement a simple rate limiting system with nginx (v1.6.2)
sites-available/mysite.com:
limit_req_zone $binary_remote_addr zone=myzone:10m rate=2r/m;
server {
listen 80;
server_name mysite.com;
root /var/www/vhosts/mysite.com;
error_log [..];
access_log [..];
include conf.d/php-fpm.conf;
location = / {
limit_req zone=myzone burst=3 nodelay;
index index.html;
}
location / {
try_files $uri =404;
}
location ^~ /pages {
include conf.d/php-fpm.conf;
internal;
}
location = /email {
rewrite ^(.*)$ /pages/email.html;
}
location = /email/subscribe {
limit_req zone=myzone burst=2 nodelay;
rewrite ^(.*)$ /pages/email.php?action=subscribe;
}
location ~ /api {
limit_req zone=myzone burst=5 nodelay;
rewrite ^(.*)$ /pages/api.php;
}
}
conf.d/php-fpm.conf:
location ~ \.php$ {
if (!-f $document_root$fastcgi_script_name) {
return 404;
}
fastcgi_pass unix:/var/run/php5-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
fastcgi_param PATH_INFO $fastcgi_path_info;
include fastcgi_params;
}
nginx.conf:
Nothing interesting, only
include sites-enabled/*;
Rate limiting /
works fine. I get an error 503 if I do too many requests to this page.
The problem: Neither /email/subscribe
, /api
nor /api/test
is rate limited, and I don't know why. It must have to do something with rewrite
, but what's the problem?
Any ideas? I tried everything!
Please note: I've changed filenames and URL endpoints.
Upvotes: 5
Views: 8698
Reputation: 14354
The problem is that nginx process request in several phases and rewrite phase goes before preaccess one (this is where limit_req
is applied). So in you config requests are rewritten to /pages/...
before they had a chance to be limited. To avoid this you either should stay in the same location block after rewrite (using break
flag) or a little hack with try_files
.
I prefer first option, so your config could look like this:
limit_req_zone $binary_remote_addr zone=myzone:10m rate=2r/m;
server {
listen 80;
server_name mysite.com;
root /var/www/vhosts/mysite.com;
error_log [..];
access_log [..];
include conf.d/php-fpm.conf;
location = / {
limit_req zone=myzone burst=3 nodelay;
index index.html;
}
location / {
try_files $uri =404;
}
location ^~ /pages {
include conf.d/php-fpm.conf;
internal;
}
location = /email {
rewrite ^(.*)$ /pages/email.html;
}
location = /email/subscribe {
limit_req zone=myzone burst=2 nodelay;
rewrite ^(.*)$ /pages/email.php?action=subscribe break;
fastcgi_pass unix:/var/run/php5-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /api {
limit_req zone=myzone burst=5 nodelay;
rewrite ^(.*)$ /pages/api.php break;
fastcgi_pass unix:/var/run/php5-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
If you go with second option, your config will be a little cleaner, but a bit hacky. We will use the fact that try_files
phase runs after limit_req
phase and that try_files
makes internal redirect to it's last argument.
limit_req_zone $binary_remote_addr zone=myzone:10m rate=2r/m;
server {
listen 80;
server_name mysite.com;
root /var/www/vhosts/mysite.com;
error_log [..];
access_log [..];
include conf.d/php-fpm.conf;
location = / {
limit_req zone=myzone burst=3 nodelay;
index index.html;
}
location / {
try_files $uri =404;
}
location ^~ /pages {
include conf.d/php-fpm.conf;
internal;
}
location = /email {
rewrite ^(.*)$ /pages/email.html;
}
location = /email/subscribe {
limit_req zone=myzone burst=2 nodelay;
try_files SOME_NONEXISTENT_FILE /pages/email.php?action=subscribe;
}
location ~ /api {
limit_req zone=myzone burst=5 nodelay;
try_files SOME_NONEXISTENT_FILE /pages/api.php;
}
}
Upvotes: 7