Conditionally Serving WebP Images With Nginx

Introduction

This page was created to demonstrate how JPG/PNG images converted to WebP format via optimise-images.sh batch image converter tool can be served conditionally via Nginx web server to web browsers that support WebP image formats. See forum thread for the WebP conversion details. The optimise-images.sh batch conversion and resizer tool now also has a new option - optimise-webp-nginx mode which allows the below Nginx config syntax to be auto generated by your defined web directory path containing your JPG/PNG and WebP images as well as can auto generate a static html gallery comparing side by side your optimised resized JPG/PNG with your WebP converted image.

Serving WebP Images With Nginx

WebP format images are usually much smaller in size than JPG/PNG files (upto 80% smaller) allowing web pages to load faster and are only supported by some web browsers like Google Chrome and Opera. So for non supported browsers like Microsoft Internet Explorer/Edge, Mozilla Firefox and Safari, we need to fallback to the standard JPG or PNG format based images. Below guide is for Nginx users who are not using Nginx ngx_pagespeed module which has filters which allow on the fly auto conversion of JPG, PNG and GIF files to WebP format for just web browsers that support it. You can read up on benefits of ngx_pagespeed module here.

Step 1. In order for Nginx to conditionally serve WebP format images to only web browsers that support it, we can setup a conditional Nginx map which inspects the client's web browser Accept Header for WebP mime type. Centmin Mod 123.09beta01 and newer versions already has setup instructed /usr/local/nginx/conf/webp.conf include file in /usr/local/nginx/conf/nginx.conf. However, if you're using an older version, you'll need to now edit nginx.conf i.e. for Centmin Mod Nginx servers at /usr/local/nginx/conf/nginx.conf, add an includes file within http{} server context like below:

include /usr/local/nginx/conf/webp.conf;

Step 2. Within /usr/local/nginx/conf/webp.conf add the following contents (updated April 2, 2020):

map $http_accept $webpok {
   default   0;
   "~*webp"  1;
}

map $http_cf_cache_status $iscf {
   default   1;
   ""        0;
}

map $webpok$iscf $webp_extension {
  11          "";
  10          ".webp";
  01          "";
  00          "";
}

Updated version allows proper compatibility with Cloudflare Proxy and DNS only modes. When Cloudflare Proxy CDN mode detected, disable Centmin Mod Nginx webP due to Cloudflare cache not supporting HTTP Vary header and when Cloudflare DNS only mode detected or in absence of Cloudflare, enable Centmin Mod Nginx webP. So when web browser supports HTTP Accept header with image/webp mime type, set $webpok variable to value of 1 and when cf-cache-status header exists, set $iscf variable to 1 and if doesn't exist set $iscf variable to 0.

Then if $webpok$iscf combined variables have a value of 10 for $webpok = 1 and $iscf = 0, it means web browser supports HTTP Accept header with image/webp for request header - indicating webP browser support and when cf-cache-status doesn't exist $iscf = 0 which will enable Nginx webP support by assigning $webp_extension variable with value of .webp for extension appending to original image name.

Step 3. Within your Nginx vhost's location context i.e. /webp, you can add the following - remove the noindex/nofollow header if you want the page to directory /webp to be indexed by search engines and remove autoindex if you don't want directory contents to be viewable without a index page. For Nginx ngx_pagespeed users, you may have enabled JPG/PNG auto conversion to WebP filter which may affect the below setup, so you would need to uncomment the pagespeed off (for ngx_pagespeed 1.12.34.3-stable and below) or pagespeed unplugged (for ngx_pagespeed 1.13.35.1-beta and above versions) by removing the front hash to disable ngx_pagespeed filter within /webp directory.

location /webp {
  #pagespeed off;
  #pagespeed unplugged;
  autoindex on;
  add_header X-Robots-Tag "noindex, nofollow";
  location ~* ^/webp/.+\.(png|jpe?g)$ {
    expires 30d;
    add_header Vary "Accept";
    add_header Cache-Control "public, no-transform";
    try_files $uri$webp_extension $uri =404;
  }
}

This second example below is for Wordpress's /wp-content/uploads/ directory. Notice the differences for the inner location context match for .png and .jpg/.jpeg images. You'll need to adjust that for your specific web applications intended usage. The autoindex on directive is also commented out/disabled as you probably do not want images in the uploads directly directly visible and full listed when accessed via the directory name.

# webp extension support if you are converting /uploads images to webp
location ~ ^/wp-content/uploads/ {
  #pagespeed off;
  #pagespeed unplugged;
  #autoindex on;
  #add_header X-Robots-Tag "noindex, nofollow";
  location ~* ^/wp-content/uploads/(.+/)?(.+)\.(png|jpe?g)$ {
   expires 30d;
   add_header Vary "Accept";
   add_header Cache-Control "public, no-transform";
   try_files $uri$webp_extension $uri =404;
  }
}

The Cache-Control header's no-transform is explained here:

No transformations or conversions should made to the resource. The Content-Encoding, Content-Range, Content-Type headers must not be modified by a proxy. A non- transparent proxy might, for example, convert between image formats in order to save cache space or to reduce the amount of traffic on a slow link. The no-transform directive disallows this.

Step 4. Restart Nginx server for changes to take affect:

service nginx restart

Demo WebP Images

Below is a demo of PNG embedded image, bees.png which has corresponding bees.png.webp format image in same directory. If this page is viewed in a web browser that supports WebP and is served from a web server like Nginx with above configuration, you will see the image mime type reported as WebP instead of PNG while the image extension remains as displaying bees.png. Otherwise, if served from a web server without such a configuration, you will see the original bees.png image being served instead with mime type reported as PNG.

Original bees.png image file size is 177,424 bytes (173.27 KB) and bees.png.webp optimised file size is 10,520 bytes (10.27 KB)

webp demo

Opera & Chrome web browser users will see the WebP version (Mime Type = WebP)

opera devtools

While unsupported web browser like Firefox, IE11/Edge and Safari users will see the PNG version (Mime Type = PNG)

firefox devtools

WebP Comparison Gallery

Below is example of optimise-images.sh batch conversion and resizer tool's new option - optimise-webp-nginx mode's auto generated static html gallery comparing your optimised and resized JPG/PNG images side by side with WebP converted copy.

------------------------------------------------------------------------------
image profile
image name : width : height : quality : transparency : image depth (bits) : size : user: group
------------------------------------------------------------------------------
images in /usr/local/nginx/html/webp
logged at /home/optimise-logs/profile-log-010517-115230.log
------------------------------------------------------------------------------
image : bees.png : 444 : 258 : 92 : False : 8 : 175296 : root : nginx
image : bees.png.webp : 444 : 258 : 92 : False : 8 : 10520 : root : nginx
image : dslr_canon_eos_m6_1.jpg : 1200 : 800 : 82 : False : 8 : 161086 : root : nginx
image : dslr_canon_eos_m6_1.jpg.webp : 1200 : 800 : 92 : False : 8 : 61544 : root : nginx
image : dslr_nikon_d7200_1.jpg : 2048 : 1365 : 82 : False : 8 : 374954 : root : nginx
image : dslr_nikon_d7200_1.jpg.webp : 2048 : 1365 : 92 : False : 8 : 173414 : root : nginx
image : dslr_nikon_d7200_2.jpg : 1365 : 2048 : 82 : False : 8 : 516224 : root : nginx
image : dslr_nikon_d7200_2.jpg.webp : 1365 : 2048 : 92 : False : 8 : 212754 : root : nginx
image : png24-image1.png : 600 : 400 : 92 : False : 8 : 386063 : root : nginx
image : png24-image1.png.webp : 600 : 400 : 92 : False : 8 : 27104 : root : nginx
image : png24-interlaced-image1.png : 600 : 400 : 92 : False : 8 : 443931 : root : nginx
image : png24-interlaced-image1.png.webp : 600 : 400 : 92 : False : 8 : 27104 : root : nginx
image : samsung_s7_mobile_1.jpg : 2048 : 1536 : 82 : False : 8 : 256253 : root : nginx
image : samsung_s7_mobile_1.jpg.webp : 2048 : 1536 : 92 : False : 8 : 69490 : root : nginx
image : webp-study-source-firebreathing.png : 1024 : 752 : 92 : False : 8 : 1194091 : root : nginx
image : webp-study-source-firebreathing.png.webp : 1024 : 752 : 92 : False : 8 : 71860 : root : nginx

------------------------------------------------------------------------------
Original or Existing Images:
------------------------------------------------------------------------------
| Avg width | Avg height | Avg quality | Avg size   | Total size (Bytes) | Total size (KB) |
| --------- | ---------- | ----------- | --------   | ------------------ | --------------- |
| 1166      | 945        | 87          | 438487     | 3507898            | 3426            |

------------------------------------------------------------------------------
Optimised WebP Images:
------------------------------------------------------------------------------
| Avg width | Avg height | Avg quality | Avg size   | Total size (Bytes) | Total size (KB) |
| --------- | ---------- | ----------- | --------   | ------------------ | --------------- |
| 1166      | 945        | 92          | 81724      | 653790             | 638             |

When resizing is disabled to work with original images via IMAGICK_RESIZE='n'

------------------------------------------------------------------------------
image profile
image name : width : height : quality : transparency : image depth (bits) : size : user: group
------------------------------------------------------------------------------
images in /home/nginx/domains/domain.com/public/images
logged at /home/optimise-logs/profile-log-010517-114117.log
------------------------------------------------------------------------------
image : bees.png : 444 : 258 : 92 : False : 8 : 175467 : root : nginx
image : bees.png.webp : 444 : 258 : 92 : False : 8 : 10520 : root : nginx
image : dslr_canon_eos_m6_1.jpg : 1200 : 800 : 82 : False : 8 : 164947 : root : nginx
image : dslr_canon_eos_m6_1.jpg.webp : 1200 : 800 : 92 : False : 8 : 61544 : root : nginx
image : dslr_nikon_d7200_1.jpg : 6000 : 4000 : 82 : False : 8 : 3701729 : root : nginx
image : dslr_nikon_d7200_1.jpg.webp : 6000 : 4000 : 92 : False : 8 : 1639822 : root : nginx
image : dslr_nikon_d7200_2.jpg : 4000 : 6000 : 82 : False : 8 : 3177146 : root : nginx
image : dslr_nikon_d7200_2.jpg.webp : 4000 : 6000 : 92 : False : 8 : 1094418 : root : nginx
image : png24-image1.png : 600 : 400 : 92 : False : 8 : 387519 : root : nginx
image : png24-image1.png.webp : 600 : 400 : 92 : False : 8 : 27104 : root : nginx
image : png24-interlaced-image1.png : 600 : 400 : 92 : False : 8 : 444852 : root : nginx
image : png24-interlaced-image1.png.webp : 600 : 400 : 92 : False : 8 : 27104 : root : nginx
image : samsung_s7_mobile_1.jpg : 4032 : 3024 : 82 : False : 8 : 1106190 : root : nginx
image : samsung_s7_mobile_1.jpg.webp : 4032 : 3024 : 92 : False : 8 : 214324 : root : nginx
image : webp-study-source-firebreathing.png : 1024 : 752 : 92 : False : 8 : 1206431 : root : nginx
image : webp-study-source-firebreathing.png.webp : 1024 : 752 : 92 : False : 8 : 71860 : root : nginx

------------------------------------------------------------------------------
Original or Existing Images:
------------------------------------------------------------------------------
| Avg width | Avg height | Avg quality | Avg size   | Total size (Bytes) | Total size (KB) |
| --------- | ---------- | ----------- | --------   | ------------------ | --------------- |
| 2238      | 1954       | 87          | 1295535    | 10364281           | 10121           |

------------------------------------------------------------------------------
Optimised WebP Images:
------------------------------------------------------------------------------
| Avg width | Avg height | Avg quality | Avg size   | Total size (Bytes) | Total size (KB) |
| --------- | ---------- | ----------- | --------   | ------------------ | --------------- |
| 2238      | 1954       | 92          | 393337     | 3146696            | 3073            |