Rather than converting every image manually and updating HTML with <picture> tags, Nginx can automatically serve a modern format (WebP/AVIF) when the browser supports it, falling back to the original otherwise — without any HTML changes required.
Prerequisites
- Nginx installed with the
ngx_http_map_module(included in standard Nginx builds) - WebP/AVIF versions of your images generated alongside the originals
Step 1 — Generate WebP/AVIF Versions Alongside Originals
for f in /var/www/mysite/html/images/*.jpg; do
cwebp -q 80 "$f" -o "${f%.jpg}.webp"
done
See How to Optimize Images for Web Performance for the conversion commands in detail. The key requirement here: each photo.jpg needs a corresponding photo.webp at the same path.
Step 2 — Detect Browser Support via the Accept Header
http {
map $http_accept $webp_suffix {
default "";
"~*webp" ".webp";
}
}
Browsers that support WebP send image/webp in their Accept header — this map checks for that.
Step 3 — Serve the WebP Version When Available and Supported
server {
location ~* ^/images/(.+)\.(jpg|jpeg|png)$ {
add_header Vary Accept;
try_files /images/$1.$2$webp_suffix /images/$1.$2 =404;
}
}
This attempts to serve the WebP version first if the browser supports it and the file exists, falling back to the original format otherwise — all transparent to the HTML, which just references image.jpg as normal.
The Vary Header (Important for Caching)
add_header Vary Accept tells caches (browser and any CDN in front) that the response differs based on the Accept header — without this, a cache might incorrectly serve a WebP response to a browser/proxy that doesn't support it.
Adding AVIF Support (Preferred Format When Available)
map $http_accept $avif_suffix {
default "";
"~*avif" ".avif";
}
location ~* ^/images/(.+)\.(jpg|jpeg|png)$ {
add_header Vary Accept;
try_files /images/$1.$2$avif_suffix /images/$1.$2$webp_suffix /images/$1.$2 =404;
}
Tries AVIF first (best compression), then WebP, then falls back to the original — giving every browser the best format it actually supports.
Automating Image Conversion on Upload
For dynamic sites accepting user uploads, integrate conversion into your upload handling code (using an image processing library) rather than a manual batch script, ensuring every new upload automatically gets WebP/AVIF variants generated.
Verifying It's Working
curl -H "Accept: image/webp" -I https://yourdomain.com/images/photo.jpg
Check the response — while the URL still shows .jpg, Nginx should be internally serving the .webp file content if it exists.
Common Errors
WebP never gets served despite browser support — verify the .webp file actually exists at the exact same path/name as the original, and check the map directive is correctly matching the Accept header.
Wrong format served to some visitors after CDN caching — verify the CDN/proxy respects the Vary: Accept header and caches variants separately.
Best Practices
- Always set the
Vary: Acceptheader when serving format-negotiated content - Generate WebP/AVIF as part of your build or upload pipeline, not as a manual afterthought
- Test with explicit
Acceptheaders via curl to confirm the negotiation logic works correctly
Continue Reading
- How to Optimize Images for Web Performance
- How to Host a Static Website on a VPS with Nginx
- How to Perform a Complete Website Speed Audit
Browse more articles in Static Site Hosting & Frontend Deployment.
