Serve the Optimal Image Format with Nginx

by Eric Fortis

Why let Nginx determine the best image format to serve?

  1. There's no need to change client-side code.
  2. Unlike <picture> tags, video posters have no standard way of specifying many URLs for fetching the optimal format supported by the client.

Here's how

Upload the images with the modern extension appended.

  • foo.png
  • foo.png.jxl
  • foo.png.avif
  • foo.png.webp

By the way, here's a script to convert them.

For dispatching the most efficient format, Nginx will check the Accept request header. For example, Chrome 85 and up say they support AVIF by sending its media (MIME) type:

Accept: image/avif,image/webp,image/apng,image/*,*/*;q=0.8

nginx.conf 1

http {
  # ...

  map $http_accept $img_ext {
    ~image/jxl    '.jxl';
    ~image/avif   '.avif';
    ~image/webp   '.webp';
    default       '';

  server {
    # ...
    location /images {
      add_header  Vary Accept;
      try_files   $uri$img_ext $uri =404;

On requests under /images, Nginx defines the custom variable $img_ext with the extension associated to the first matched media type regex of the map, using the Accept header ($http_accept) as input parameter.

Then, try_files checks if there's a file with that extension appended, like foo.png.avif, and serves it. The middle $uri is a fail-safe. For instance, maybe the AVIF isn't uploaded yet, or the client is directly requesting an AVIF image such as images/foo.png.avif.

Lastly, Vary: Accept is set as a response header. This is only needed if the server has caching reverse proxies in front (e.g. intercepting CDNs). Otherwise, those proxies would always serve the same format sent to the first client that requested the particular image.2


Make sure the JXL and AVIF media types are registered.

types {
  # ...
  image/jxl   jxl;
  image/avif  avif;




for mime in image/avif image/webp image/png; do
  response_mime=$(curl -so /dev/null --head \
    --header "Accept: $mime" \
    --write-out "%{content_type}" $img)

  if [ "$response_mime" = $mime ]; then
    echo "OK $mime"
    echo "FAIL $mime Got: $response_mime" >&2
    exit 1


1. E. Lazutkin (2014) Serve files with Nginx conditionally 2. R. Mulhuijzen (2014) Best Practices for Using the Vary Header

Sponsored by: