Florian Schneider
Florian Schneider

Reputation: 320

nginx limit_req does not work

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

Answers (1)

Alexey Ten
Alexey Ten

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

Related Questions