← All posts

Nginx. Caching with fastcgi_cache

Nginx logo

I got tired of my blog taking so long to load, but I don’t have time yet to switch from Wordpress to another engine, and the move to a new, more powerful server keeps getting delayed, so I decided to add caching on the Nginx side. Since the blog runs on fastcgi, I’ll be using fastcgi_cache. If you’re using Nginx+Apache, you should use proxy_cache; as far as I understand, there aren’t any major differences.

I still need to keep some blocks out of the cache, so I’ll be using ssi.

Configuration

I have many hosts on one server, but I only need to configure caching for one host. Let’s define the cache for my blog in nginx.conf, in the http section:


fastcgi_cache_path /var/cache/nginx/adw0rd
    levels=1:2
    keys_zone=adw0rd:16m
    max_size=256m
    inactive=1d;

Where,

fastcgi_cache_path - the path to the cache directory; create it beforehand. levels= - split cache files into subdirectories of the specified levels, so “1:2” looks like this:


/0/ab/b7f54b2df7773722d382f4809d650ab0

Or like this if you don’t specify LEVEL, that is, leave levels empty (levels=):


/b7f54b2df7773722d382f4809d650ab0

max_size - the maximum cache size. incative - the cache lifetime.

Virtual host configuration

I split my configuration files into the main one (nginx.conf) and host-specific ones (adw0rd.conf, pyha.conf, etc.). Now let’s work through adw0rd.conf:


server {
    # Порт который слушаем
    listen 80;
    # Имя хоста
    server_name adw0rd.ru;
    # Путь до htdocs (document_root)
    root /path/to/www;

    # включаем использование ssi
    ssi on;

    location / {
        # Если такой файл не существует физически, то переходить на крайний аргумент, то есть @wordpress
        try_files $uri @wordpress;
    }

    # обработчик для wordpress
    location @wordpress {

        # Проксируем на php-fpm
        fastcgi_pass php-fpm;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root/index.php;
        fastcgi_param SCRIPT_NAME /index.php;

        # Настройки кеширования
        fastcgi_temp_path /tmp/nginx 1 2; # Каталог для хранение временных файлов
        fastcgi_cache adw0rd; # Имя зоны кеша
        fastcgi_cache_key "$request_method|$host|$request_uri"; # Из этих данных строится хеш
        fastcgi_hide_header "Set-Cookie"; # Не передавать клиенту заголовки кук
        fastcgi_cache_min_uses 1; # Кол-во запросов, после которых ответ будет закеширован
        fastcgi_cache_valid 10m; # Время жизни кеша
        fastcgi_cache_use_stale error timeout invalid_header http_500; # Случаи при которых будет использоватся старый кеш

    }

    # обработчик для динамических данных на странице, ниже я покажу как юзать это
    location ~ ^/ssi/ {
        fastcgi_pass php-fpm;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;        
    }

    # Обработчик *.php файлов, например "wp-comments-post.php"
    location ~ \.php$ {

        # если файл физически не существует, то переходим в @wordpress
        try_files $uri @wordpress;

        # иначе обрабатываем запрос
        fastcgi_pass php-fpm;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

You can read about all the directives in the official documentation: ngx_http_fastcgi_module directives.

Testing

Reload Nginx:


nginx -s reload

To test the cache, I added an HTML comment to the template, generated like this:


<!-- cache: <?php echo time() ?> -->

Now I can see in the page source whether the page has been cached or not.

Inserting dynamic blocks with SSI

Edit your page template and insert something like this:


<!-- uncached_data: <!--# include virtual="/ssi/uncached.php" wait="no" --> -->

Now make the file “/ssi/uncached.php” look like this:


<?php
echo time();

Since we have a handler for /ssi/* that doesn’t serve cached data, our dynamic content will be loaded—it’s that simple! :)

Addendum

This section was added on August 8, 2011, to fill in the material missing from this article. The main reason was the article Transparent caching in nginx for everyone; thanks to its authors.

The fastcgi_ignore_headers directive for ignoring instructions from the FastCGI server

The fastcgi_ignore_headers directive disables processing of certain header fields in the FastCGI server’s response.
You can specify the fields “X-Accel-Redirect”, “X-Accel-Expires”, “Expires”, and “Cache-Control” in this directive.

fastcgi_ignore_headers Expires Cache-Control;

In other words, we tell Nginx to cache pages in all cases, regardless of the caching headers set by the FastCGI server.

The fastcgi_cache_bypass and fastcgi_no_cache directives

The directive specifies the conditions under which the response will not be taken from the cache.
If the value of at least one of the variable strings is not empty and is not equal to “0”, the response is not taken from the cache:

fastcgi_cache_bypass $cookie_session $cookie_nocache;

The directive specifies the conditions under which the response will not be saved to the cache.
If the value of at least one of the variable strings is not empty and is not equal to “0”, the response will not be saved:

fastcgi_no_cache $cookie_session $cookie_nocache;

Controlling caching through the X-Accel-Expires header

Let’s control caching from PHP:

<?php
header("X-Accel-Expires: $seconds"); // ставим на $seconds секунд
header("X-Accel-Expires: 0"); // или отключаем кеширование для определенной страницы

Forcing a cache refresh

Let’s update our bypass by adding another variable for the “X-Update” header:

fastcgi_cache_bypass $cookie_session $http_x_update;

And now let’s refresh the cache:

curl -s -o /dev/null -H "X-Update: 1" example.com

As the article’s authors recommend, you need to change “$http_x_update” and “X-Update” to something of your own so that script kiddies can’t mess around with it.

Useful resources

Here’s a short list of articles you may find useful:

Comments are closed. Read the original discussion in Russian →