1

I am trying to protect Drupal running on Nginx from simple DDoS with limit_conn and limit_req. But I encountered some strange behavior with inheritance of limit_conn directive which I cannot explain.

I have reduced my nginx (1.8.0) config to this bare minimum, which shows the problem:

limit_conn_zone $binary_remote_addr zone=perip:10m;

server {
    server_name test.dev;
    root /var/nginx/drupal; ## <-- Your only path reference.

    #Allow not more than 10 simultaneous connections from one address.
    limit_conn perip  10;

    location / {
        #limit_conn perip 1;
        # This is cool because no php is touched for static content
        try_files $uri @rewrite;
    }

    location @rewrite {
        # Clean URLs are handled in drupal_environment_initialize().
        rewrite ^ /index.php;
    }

    location ~ \.php$ {
        #Allow not more than 1 simultaneous *connection_to_PHP* from one address.
        limit_conn perip 1; 

        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        include fastcgi.conf;
        fastcgi_intercept_errors on;
        fastcgi_pass unix:/var/run/php5-fpm.sock;
    }
}

As you see, I want to limit the number of simultaneous connections to 10 for all requests and to 4 for the php backend. (In this example I have modified the 4 connection to 1, so it would be easier to trigger)

The Nginx documentation at http://nginx.org/en/docs/http/ngx_http_limit_conn_module.html#limit_conn states that:

These directives are inherited from the previous level if and only if there are no limit_conn directives on the current level.

But something strange happens in my tests, it looks like nginx is ignoring the directive limit_conn inside the location ~ \.php$ block:

  1. When I test this config with 5 simultaneous connection ab -n 100 -c 5 http://test.dev/, no blocking happens. As soon as I raise the
    limit to 11 -c 11 nginx starts to block requests.
  2. If I modify the global connection limit from 10 to 5, nginx limit more than 6 connections (-c 6) - it looks like the directive in location ~ \.php$ block is ignored.
  3. If I delete the conn_limit directive at server level, the directive in location ~ \.php$ block, suddenly starts to work!
  4. Even more confusing: if I add the conn_limit directive to location / block, it correctly overwrites the global one!

Maybe the problem is in try_files directive or multiple redirects? I would be very thankful, if someone could explain, why the conn_limitdirective is not being overwritten, like expected.

  • https://trac.nginx.org/nginx/ticket/712#comment:1 – Alexey Ten Sep 30 '15 at 11:34
  • `limit_conn` checked once per connection. And it was checked in `location /`, after that you have internal redirect to `location \.php$`, but nginx won't check `limit_conn` again. – Alexey Ten Sep 30 '15 at 11:36
  • @AlexeyTen: in the link you posted, it tells, that `conn_limit` is checked only once. In my first 3 tests the `limit_conn` directive in `location /` block is commented, so it is not the problem, but maybe the global `limit_conn` gets processed first. But now I am even more confused: in my 4th test the `conn_limit` in `location /` block is processed, although the global `conn_limit` still exists. According to the post, it should be ignored too – hennadiy.verkh Oct 01 '15 at 09:30
  • In your post config `limit_conn` in `location /` is commented so it's inherited from `server` level. – Alexey Ten Oct 01 '15 at 09:41
  • If you check with `ab -n 100 -c 5 http://test.dev/index.php` then `limit_conn` in PHP-block should work. – Alexey Ten Oct 01 '15 at 09:42

1 Answers1

1

Thanks to @AlexeyTen, I hope to be able to answer the question.

Important are two points:

  • The limit_conn directive is processed only once per request!
  • These directives are inherited from the previous level if and only if there are no limit_conn directives on the current level.

TL;DR: if there is a limit_conn directive, nginx peeks in the child block in the execution chain, if there is no limit_conn, the parent one is processed. After that all other limit_conn directives are ignored.

I try to explain it on my example:

In the example I posted, when a "http://test.dev/" request arrives, at first, nginx is not processing limit_conn at server level, but waits to look into matching location location /. But at this level there is no limit_conn directive, so nginx processes the one at the server level. After that, all limit_conn directives are ignored, because it was already processed. That explains my tests 1-3.

In test 4, nginx finds a limit_conn directive in location location / and processes it, but again, on location location @rewrite no limit_conn is found, so the directive at location / is processed. Again, we are ignoring the location ~ \.php$ block.