Nginx configuration errors (or how to write rewrites correctly)

    Hello habra

    On duty, we have to work with web developers who sometimes write their scripts with rewrites that they have to adapt for nginx. I have to rake what is written there.

    Everyone who wants to get help with rewriting can ask questions in the comments, then, probably, one more post will be drawn from this.


    Error number 1, the most fatal.


    A huge number of times were mentioned in the newsletter. Namely, the use of if at the location level.
    The problem is that if in the location are not arranged as we imagine. We think that a request arrives, a condition is checked, if it is true, configuration changes are made. But in reality, everything is completely different. At startup, nginx generates separate location configurations for true and false conditions in if. Some creepy examples:
    location /
    {
        set $true1 1; set $true2 1;
        if($true1) { proxy_pass http://127.0.0.1:8080; }
        if($true2) { set $expr 123; }
    }

    Segmentation fault in a workflow when trying to find upstream for proxy_pass from the first if. And the thing is that he was not inherited in location, where both conditions are correct.

    location /
    {
        set $true 1;
        try_files /maintenance.html $uri @fallback;
        if($true) { set $expr 123; }
    }
    location @fallback
    {
        proxy_pass 127.0.0.1:8080;
    }

    Complete ignore try_files. It's just that it is not in the location, which turned out with the truth of the expression.

    Under "set $ expr 123;" you can understand almost any expression in if that does not specify a handler for the request - all set, limit_rate, etc.

    However, in one case, you can still use if in location - if we leave this location right after if. There are two ways to do this:
    1) Through rewrite ... last;
    location /
    {
        try_files /maintenance.html $uri @fallback;
        if($cookie_uid = '1') { rewrite ^ /user/panel last; }
    }
    location /user
    {
        proxy_pass 127.0.0.1:8080;
    }

    2) Via return ...;
    location /
    
    At the same time, through return we can both finish processing the request and go to another location via error_page.
    By the way, if at the server level acts exactly as we expect. When using it, global problems should not arise.

    Mistakes number 2, less fatal.


    After apache and its htaccess with RewriteRule and RewriteCond, I really want to cram everything into one location with ifs that will take processing to another place. But it is ugly, wrong and inefficient.
    Error number 2.1, about if (-e ...)

    Especially in order to beautifully record such rewrites, a special directive was invented - try_files. In the simplest version, it is usually written like this:
    try_files $uri $uri/ @fallback
    which means:
    1. Check if the requested file exists. If so, give it away; if not, go on.
    2. Check if the directory with the requested name exists. If yes, give it away; if not, go on.
    3. Transfer the processing request to the named location @fallback.

    Attention! In @fallback when using fastcgi you should not do rewrite. Just write fastcgi_parm with the required script. Thus, a design like Turns into. In the case of using HTTP and not FastCGI as a backend, you can write it like this:
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule ^(.*)$ index.php


    root /path/to/root;
    location / { try_files $uri $uri/ @fallback; }
    location @fallback
    {
        fastcgi_pass backend;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root/index.php; # тут, кстати, злобный хабрапарсер съел слово S_C_R_I_P_T, будьте аккуратнее - регистр важен!
    }
    


    root /path/to/root;
    location / { try_files $uri $uri/ /index.php; }
    

    You need to understand that all arguments of try_files, except the last one, will be perceived as simple files for upload, and the last argument will already be a new target (that is, you can write a processing uri or a named location there). Therefore, an attempt to write try_files $ uri $ uri / /index_new.php /index_old.php will not do anything good - instead of executing index_new.php, its contents will be returned.

    With the help of try_files, one more surprising thing can be done - without editing the configs, close the entire site for maintenance. This is done by writing try_files /maintenance.html $ uri $ uri / @fallback; and then simply creating / moving the /maintenance.html file with the technical work report. This will not give the expected performance drop due to the use of open_file_cache, which also caches unsuccessful attempts to open the file.

    Mistake number 2.2, about if ($ uri ~)

    So, there is a fairly simple rule - if your configuration has a line starting at the «if ($ uri ~», then nginx is configured incorrectly entire uri validation logic to something is to be implemented through the location, which now support and isolation.. Moves in
    RewriteCond %{REQUEST_URI} ^/rss[0-9]{1-6}
    RewriteRule ^(.*)$ rss.php


    location ~ /rss[0-9]{1-6}
    {
        fastcgi_pass backend;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root/rss.php; # тут, кстати, злобный хабрапарсер съел слово S_C_R_I_P_T, будьте аккуратнее - регистр важен!
    }


    Mistake number 2.3, about if ($ host ~)

    Everything said for if ($ uri ~) is also true for if ($ host ~). To use these rules, just create a server with server_name as a regexp!

    Not a mistake, but a rake with selections

    If you dare to rewrite the config using allocations in server / location then you will certainly come across a small problem. It consists in the fact that server entries are erased when entering the location with the regexp, location allocations are overwritten when entering the if or using rewrite. Alas, I am not very familiar with apache rewrites to provide code, so I will describe what is required in words.
    Требуется все запросы вида abc.mysite.com/xyz.php перенаправлять на mysite.com/abc/index.php?p=xyz
    In order for this to work, you have to write like this:
    server
    {
        server_name ~ ^(.*)\.mysite\.com$;
        set $subdom $1;
        location ~ \/(.*)\.php 
        {
            set $script $1;
            rewrite ^ http://mysite.com/$subdom/index.php?p=$script;
        }
    }

    In the next versions of nginx, it is planned to make separate prefixes for allocations at each level (for example, the rewriting described above can be described as
    rewrite ^ http://mysite.com/$sc_1/index.php?p=$lc_1;
    ), but so far this is not, so you have to write like that.

    Another good practice is to give meaningful names to variables :-).

    Conclusion


    Thanks to everyone who read the post to the end. It turned out to be quite long, but I wanted to describe everything as purely and clearly as possible.
    Of course, there are other configuration errors - buffers, paths, but this is the concern of the system administrator, not the webmaster.

    Materials used:
    Russian nginx mailing list nginx
    Documentation

    Also popular now: