Nginx Responsive Image Resizing on Debian

2024-08-082024-08-12

Images are great and I want to have a lot of them. Only problem is the hassle to resize and organize them to be web friendly. I found that nginx had a module to resize images and cache the result but all the configs I found to do so were problematic or insufficient in some way, so I wrote my own.

Installing the image_filter module

Instructions copied from Nginx prebuilt Debian packages (also has instructions for other distros).

From the Debian Repo

While it should be fine to just install libnginx-mod-http-image-filter by doing

sudo apt update
sudo apt install libnginx-mod-http-image-filter

From the Nginx Repo

For reasons I cannot remember I am using the Nginx prebuilt Debian package from the official Nginx repo.

  1. Install prereqs
    sudo apt install curl gnupg2 ca-certificates lsb-release debian-archive-keyring
    
  2. Fetch the nginx signing key and verify that the downloaded file contains the correct key (check here if viewing in 2027)
    curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor \
     | sudo tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null
    
    gpg --dry-run --quiet --no-keyring --import --import-options \
     import-show /usr/share/keyrings/nginx-archive-keyring.gpg
    
    pub   rsa4096 2024-05-29 [SC]
          8540A6F18833A80E9C1653A42FD21310B49F6B46
    uid                      nginx signing key <[email protected]>
    
    pub   rsa2048 2011-08-19 [SC] [expires: 2027-05-24]
          573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62
    uid                      nginx signing key <[email protected]>
    
    pub   rsa4096 2024-05-29 [SC]
          9E9BE90EACBCDE69FE9B204CBCDCD8A38D88A2B3
    uid                      nginx signing key <[email protected]>
    
  3. Add the repo to apt and use repository pinning to prefer the Nginx repo over the Debian repo
    echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] \
    http://nginx.org/packages/debian `lsb_release -cs` nginx" \
     | sudo tee /etc/apt/sources.list.d/nginx.list
    
    echo -e "Package: *\nPin: origin nginx.org\nPin: release o=nginx\nPin-Priority: 900\n" \
     | sudo tee /etc/apt/preferences.d/99nginx
    
  4. Update then install nginx-module-image-filter
    sudo apt update
    sudo apt install nginx-module-image-filter
    

Using the image_filter Module

Note: if you are building Nginx from scratch you can enable the image_filter module during build time and don’t need to load it at runtime like here.

In the /etc/nginx/nginx.conf add the following line to the outermost scope (outside stream, http, or the such)

load_module modules/ngx_http_image_filter_module.so;

Note that ngx is not a typo. Check that the configuration is OK.

nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Adding the Image Server to Nginx

My personal requirements for the config:

# image resizing server
server {
    listen 28956;
    server_name 127.0.0.1;
    root /srv/website/static;               # TODO: your path

    # I can only hope you don't have a directory called "invalid-option"
    location ~ "^/invalid-option/" {
        return 404;
    }

    location ~ "^/resize/(?<image>.+)$" {
        alias /srv/website/static/$image;
        image_filter resize $arg_width -;
        image_filter_jpeg_quality 75;
        image_filter_buffer 15M; # max image size
    }
}

# cache settings
proxy_cache_path /tmp/nginx-images-cache/ levels=1:2 keys_zone=images:32m inactive=48h max_size=512m;

# image serving server
server {
    listen 443 ssl;
    http2 on;
    server_name static.koitu.com;           # TODO: your host

    ssl_certificate <your fullchain>;       # TODO
    ssl_certificate_key <your privkey>;     # TODO

    root /srv/website/static;               # TODO: your path

    set $resize_option "/invalid-option";
    # only when width is unset do we return the image in original size
    if ($arg_width = "") {
        set $resize_option "";
    }
    # only allow a limited selection of resolutions to resize into
    if ($arg_width ~ "^(240|480|720|1280)$") {
        set $resize_option "/resize";
    }

    location ~* "\.(png|jpg|jpeg)$" {
        proxy_pass http://127.0.0.1:28956$resize_option$request_uri;

        # cache all returned images
        proxy_cache images;
        proxy_cache_valid 200 48h;
    }
}

In this config I specified a max cache size of 0.5 GB since my website is stored on a HDD and my cache is on a SSD. I am also only allowing resizing to 240px, 480px, 720px, and 1280px but its up to you how you want to configure your own setup.

After you are happy check the config with nginx -t then service nginx reload then place an image in the path and test it out by either opening in your browser or using wget.

Example: my domain is static.koitu.com and say I placed the file test.jpg at /srv/website/static/test.jpg then I can visit

https://static.koitu.com/test.jpg?width=240
https://static.koitu.com/test.jpg?width=480
https://static.koitu.com/test.jpg?width=720
https://static.koitu.com/test.jpg?width=1280
https://static.koitu.com/test.jpg

Adding Responsive Images to Your Website

See here for more details.

In my case, some of my images at original size are too big to really use (unless you are viewing my page in 8k for some reason). As a result, the only options I give are for viewing at the reduced resolutions.

The following is an example of using hugo

{{ $src := .Params.iamge }}
<img src="{{ $src }}?width=1280"
     srcset="{{ $src }}?width=1280 1280w,
             {{ $src }}?width=720 720w,
             {{ $src }}?width=480 480w,
             {{ $src }}?width=240 240w"
     loading="lazy">

Which generates the following HTML that uses to use responsive images

<img src="https://static.koitu.com/test.jpg?width=1280"
     srcset="https://static.koitu.com/test.jpg?width=1280 1280w,
             https://static.koitu.com/test.jpg?width=720 720w,
             https://static.koitu.com/test.jpg?width=480 480w,
             https://static.koitu.com/test.jpg?width=240 240w"
     loading="lazy">