Nginx caching symlinks

I have a deployment system on my web server, every time an app is deployed it creates a new timestamped directory and symlinks "current" to the new directory. This all workded good and great on apache, but on the new nginx server I've set up, it looks like a script from the "old" deployment is being run instead of the new symlinked one.

I have read some tutorials and posts on how to resolve this but there is not much info and nothing seems to work. Here is my vhost file:

server {
    listen 80;

    server_name ~^(www\.)?(?<sname>.+?).testing.domain.com$;
    root /var/www/$sname/current/public;
    index index.html index.htm index.php;

    location / {
        try_files $uri $uri/ /index.php$is_args$args;
    }

    location ~* \.(jpg|jpeg|gif|png|bmp|ico|pdf|flv|swf|exe|html|htm|txt|css|js) {
        add_header        Cache-Control public;
        add_header        Cache-Control must-revalidate;
        expires           7d;
    }

    location ~ \.php$ {
        #fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/var/run/php/php7.1-fpm.sock;
        include fastcgi_params;
        fastcgi_param DOCUMENT_ROOT $realpath_root;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_index index.php;
    }

    location ~ /\.ht {
        deny all;
    }
}

and here is my fastcgi_params:

fastcgi_param   SCRIPT_FILENAME         $document_root$fastcgi_script_name;
fastcgi_param   QUERY_STRING        $query_string;
fastcgi_param   REQUEST_METHOD      $request_method;
fastcgi_param   CONTENT_TYPE        $content_type;
fastcgi_param   CONTENT_LENGTH      $content_length;

fastcgi_param   SCRIPT_NAME     $fastcgi_script_name;
fastcgi_param   REQUEST_URI     $request_uri;
fastcgi_param   DOCUMENT_URI        $document_uri;
fastcgi_param   DOCUMENT_ROOT           $realpath_root;
fastcgi_param   SERVER_PROTOCOL     $server_protocol;

fastcgi_param   GATEWAY_INTERFACE   CGI/1.1;
fastcgi_param   SERVER_SOFTWARE     nginx/$nginx_version;

fastcgi_param   REMOTE_ADDR     $remote_addr;
fastcgi_param   REMOTE_PORT     $remote_port;
fastcgi_param   SERVER_ADDR     $server_addr;
fastcgi_param   SERVER_PORT     $server_port;
fastcgi_param   SERVER_NAME     $server_name;

fastcgi_param   HTTPS           $https if_not_empty;

# PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param   REDIRECT_STATUS     200;
fastcgi_param PATH_TRANSLATED $document_root$fastcgi_script_name;

I would really appreciate if someone could help me out with this as at the moment every deployment involves deleting previous deployment. System is Ubuntu 14.04.5 LTS ; PHP 7.1 ; Nginx nginx/1.4.6 (Ubuntu)


Solution 1:

Embedded Variables, $realpath_root: an absolute pathname corresponding to the root or alias directive’s value for the current request, with all symbolic links resolved to real paths

The solution of using $realpath_root instead of $document_root is copy-pasted all around the Q/A sites and forums; it is actually hard to avoid finding it.Yet, I've only seen it well explained once by Rasmus Lerdorf. It's worth sharing as it describes why it works and when it should be used.

So, when you deploy via something like Capistrano which does a symlink swap on the document root, you want all new requests to get the new files, but you don't want to screw over requests that are currently executing as the deploy is happening. What you really need to create a robust deploy environment is to have your web server be in charge of this. The web server is the piece of the stack that understands when a new request is starting. The opcode cache is too deep in the stack to know or care about that.

With nginx this is quite simple. Just add this to your config:

fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;

This tells nginx to realpath resolve the docroot symlink meaning that as far as your PHP application knows, the target of the symlink if the real document_root. Now, once a request starts, nginx will resolve the symlink as it stands at that point and for the duration of the request it will use the same docroot directory, even if the symlink switch happening mid-request. This entirely eliminates the symptoms described here and it is the correct approach. This isn't something that can be solved at the opcache level.

Kanishk Dudeja had problems with this and added a useful notice: make sure these changes will actually be in final configuration, i.e. after include fastcgi_params; which otherwise overrides them.

Solution 2:

From https://unix.stackexchange.com/questions/157022/make-nginx-follow-symlinks, it seems you may be able to work around the issue by changing

fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

to

fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;

(i.e. changing the path from $document_root to $realpath_root).

I don't have access to an nginx server at present to confirm this (my home server is currently undergoing a rebuild), but the solution seems to be collaborated by https://medium.com/@kanishkdudeja/truly-atomic-deployments-with-nginx-and-php-fpm-aed8a8ac1cd9.