Posted 4 min to read

When implementing WebP on a website, the main problem is to provide support for browsers that do not support it. I will show you how to quickly and sensibly add WebP support on the webserver level (NGINX and Apache) without any modification of the front- and back-end code which, depending of its complexity, could be very time-consuming.

In December 2019, as we can see at caniuse.com, many browsers still do not support WebP format (including all versions of Internet Explorer and Safari):

WebP support in browsers in December 2019

For this reason, we need to make sure that WebP images are only sent to browsers that support it and we will achieve this at the webserver level.

Install WebP

On a Debian/Ubuntu system execute the following command:

sudo apt-get install cwebp

You can also download the libwebp-1.0.3.tar.gz package from here and compile it from sources:

tar zxf libwebp-1.0.3.tar.gz
cd libwebp-1.0.3
./configure
make
sudo make install

This will install cwebp which will allow you to convert graphic files.

Convert the files

On a typical website, new graphics are constantly arriving (uploaded by CMS, automatically generated thumbnails, etc.), so it would be wise to prepare a bash script, that will be executed by the cron daemon and will recursively search and convert all newly created .jpg/.jpeg/.png files to .webp in all subdirectories and place them next to the original ones.

Create a file called convert_to_webp.sh with the following content:

#!/bin/bash

OLD_IFS=$IFS
IFS=$(echo -en "\n\b")

for FILE in `find /var/www/example.com/public/ -iregex '.*\.\(jpg\|gif\|png\|jpeg\)$'`
    do
        if [ ! -f "${FILE}.webp" ]; then
            cwebp -quiet -q 80 ${FILE} -o ${FILE}.webp;
            echo "Converted ${FILE} to ${FILE}.webp"
        fi
done

IFS=$OLD_IFS

Adjust the path /var/www/example.com/public/ to point to the directory, where the files to be converted are located, then give the script execution rights:

chmod +x convert_to_webp.sh

and run:

./convert_to_webp.sh

If everything was done correctly and there were files to convert in the directory, you will see a similar result:

/var/www/example.com/public/photo.jpg has been converted to /var/www/example.com/public/photo.jpg.webp
/var/www/example.com/public/logo.png has been converted to /var/www/example.com/public/logo.png.webp
/var/www/example.com/public/subfolder/background.png has been converted to /var/www/example.com/public/subfolder/background.png.webp

Now the WebP equivalents of the original files are saved next to them. It is worth mentioning, that re-running the script will not convert the same files again. So even with a large number of images you can add the script to cron to convert a new files when they appear without any fear.

To set up a cron job to run the script every 30 minutes, execute the crontab -e command and add the following line:

*/30 * * * * /path/to/the/script/convert_to_webp.sh

How to configure NGINX to serve WebP images

In the main config file (which is usually located in /etc/nginx/nginx.conf) add the following lines in the http {} section:

map $http_accept $webp_suffix {
    default   "";
    "~*webp"  ".webp";
}

Then in the server {} section of your vhost add the following directive:

location ~* ^.+\.(png|jpe?g) {
    add_header Cache-Control "public, no-transform";
    add_header Vary "Accept,Accept-Encoding";
    try_files $uri$webp_suffix $uri =404;
    expires 30d;
}

This will cause any browser that sends image/webp MIME type in the Accept header along with the HTTP request, to fetch the WebP version of the image if it exists.

Finally, restart NGINX to apply the changes:

/etc/init.d/nginx restart

Configure Apache to serve WebP images

Add following directives to your .htaccess file:

<IfModule mod_rewrite.c>
  RewriteEngine On

  # Check if request is for an image
  RewriteCond %{REQUEST_URI} \.(png|jpe?g)$

  # Check if browser supports WebP
  RewriteCond %{HTTP_ACCEPT} image/webp

  # Check if .webp file exists
  RewriteCond %{DOCUMENT_ROOT}/$1.webp -f

  # Return .webp file
  RewriteRule (.+)$ $1.webp [T=image/webp]
</IfModule>

<IfModule mod_setenvif.c>
  # Create REQUEST_IS_IMAGE variable for image files
  SetEnvIf Request_URI "\.(png|jpe?g)$" REQUEST_IS_IMAGE
</IfModule>

<IfModule mod_headers.c>
  # Add the Vary header to the response of the image request,
  # saying that the content at this URL may differ depending
  # on whether browser supports WebP or not
  Header set Vary "Accept,Accept-Encoding" env=REQUEST_IS_IMAGE
</IfModule>

<IfModule mod_mime.c>
  AddType image/webp .webp
</IfModule>

Verify that it works

You can use Chrome DevTools for this. If everything has been done correctly, you will see the Content-Type header with the image/webp value in the "Response Headers" section:

MIME type verification in the returned Content-Type header