TIL: resizing images on-the-fly with nginx
Because I’ve started using Wordpress as a Markdown backend for 11ty, the 11ty image plugin no longer works, which is a bummer. So for a while I’ve been serving images at their default resolution which is to say, too big.
As it turns out, there’s an nginx module, image_filter, that can be used to resize images per request. I’m using Ubuntu, and the module doesn’t exist in the standard repositories, so I followed nginx’s guide to adding their repositories.
After that, I ran sudo apt update && sudo apt install nginx-module-image-filter
, and then added the following line to my nginx conf file (/etc/nginx/nginx.conf
):
load_module modules/ngx_http_image_filter_module.so;
Serving resized images
As a baseline, I can serve resized images by just adding the image_filter
directive to a location that matches images, e.g.:
location ~* ^(/.+)\.(jpg|jpeg|jpe|png|gif)$ {
add_header Vary Accept;
image_filter resize 1000 -;
expires 30d;
}
This works, but it increases server load a fair bit, because it’s now resizing every image on every single request.
Caching the requests
So, now I need to cache my requests. I can add a separate server that I use proxy_pass
to call. This server will serve resized images, and then I can use proxy_cache
to cache the responses for however long I choose.
server {
server_name localhost;
listen 8888;
location ~* ^(/.+)\.(jpg|jpeg|jpe|png|gif)$ {
root /path/to/my/images;
image_filter resize 1000 -;
}
}
proxy_cache_path /tmp/images-cache/ levels=1:2 keys_zone=images:10m inactive=24h max_size=100m;
# main server block
server {
listen 80;
location ~* ^(/.+)\.(jpg|jpeg|jpe|png|gif)$ {
add_header Vary Accept;
proxy_pass http://localhost:8888$uri;
proxy_cache images;
proxy_cache_valid 200 30d;
expires 30d;
}
location /wp-content/uploads {
# You need to explicitly define DNS resolution when using
# variables in the proxy_pass directive. This trick resolves that.
proxy_pass http://localhost:8888/;
}
}
So now what’s happening is that any call to an image file is being proxied to a new backend running on port 8888, and caching the responses. The backend is the part responsible for serving resized images. I’ve put a 30-day cache on the images, but to be honest it can probably be longer because I very rarely update these.
Next steps
This is great, and has really reduced the size of the images I’m serving, but next I’d like to have it serve webp versions of images if they exist - but that will require preprocessing the images and then attempting to serve them.