Nginx dynamic image resizing with caching

At Sturm we build web apps that perform beautifully on both tiny mobile devices and huge desktop monitors. To achieve these, we use Nginx to resize images on-the-fly. This approach was used in our recent Course Finder project with Federation University.

The Nginx web server comes with a handy image filter module, but most tutorials don't configure it with performance in mind. Performance means caching.

Let's start with the basics:

server {
    # Basic image resizing server, no caching.
    server_name example.com;

    location ~ "^/media/(?<width>\d+)/(?<image>.+)$" {
        alias /var/www/images/$image;
        image_filter resize $width -;
        image_filter_jpeg_quality 75;
        image_filter_buffer 8M;
    }
}

Don't forget to reload your Nginx configuration:

$ sudo service nginx force-reload

This server block will respond to a request like http://example.com/media/768/mountains.jpg with the file /var/www/images/mountains.jpg. It will resize the image down to 768 pixels wide if necessary and compress it using JPEG quality 75. Requests where the original image is 8MB will be rejected.

Unfortunately our simple example doesn't perform so well, since it does a resize for every single request. Let's test it using Apache Bench (from apache2-utils on a Debian-based GNU/Linux). We'll make 100 requests using 5 concurrent connections:

$ ab -n 100 -c 5 http://example.com/media/768/mountains.jpg
...
Requests per second:    4.14 [#/sec] (mean)
...

Oh no, only four requests per second!

The solution is to cache the resized image so that the work is only done once a day. Since Nginx can't cache and resize at the same time, we'll need a trick. That trick is to use two server blocks. The first will be an internal-onlyserver that resize images. The second will be a public server that proxies requests to our internal server and then caches the result:

server {
    # Internal image resizing server.
    server_name localhost;
    listen 8888;

    location ~ "^/media/(?<width>\d+)/(?<image>.+)$" {
        alias /var/www/images/$image;
        image_filter resize $width -;
        image_filter_jpeg_quality 75;
        image_filter_buffer 8M;
    }
}

proxy_cache_path /tmp/nginx-images-cache/ levels=1:2 keys_zone=images:10m inactive=24h max_size=100m;

server {
    # Public-facing cache server.
    server_name example.com;

    # Only serve widths of 768 or 1920 so we can cache effectively.
    location ~ "^/media/(?<width>(768|1920))/(?<image>.+)$" {
        # Proxy to internal image resizing server.
        proxy_pass http://localhost:8888/media/$width/$image;
        proxy_cache images;
        proxy_cache_valid 200 24h;
    }

    location /media {
        # Nginx needs you to manually define DNS resolution when using
        # variables in proxy_pass. Creating this dummy location avoids that.
        # The error is: "no resolver defined to resolve localhost".
        proxy_pass http://localhost:8888/;
    }
}

Now that we're caching the resized images, Apache Bench shows a slow first request, but thereafter serves over 7000 requests per second!

blogroll

FSF member since 2006 Become a Conservancy Supporter! EFF member

social

Powered by Pelican and Python. Theme by Smashing Magazine.