Tutorial

How To Create and Serve WebP Images to Speed Up Your Website

Published on April 13, 2018
English
How To Create and Serve WebP Images to Speed Up Your Website

The author selected the Apache Software Foundation to receive a donation as part of the Write for DOnations program.

Introduction

WebP is an open image format developed by Google in 2010 based on the VP8 video format. Since then, the number of websites and mobile applications using the WebP format has grown at a fast pace. Both Google Chrome and Opera support the WebP format natively, and since these browsers account for about 74% of web traffic, users can access websites faster if these sites use WebP images. There are also plans for implementing WebP in Firefox.

The WebP format supports both lossy and lossless image compression, including animation. Its main advantage over other image formats used on the web is its much smaller file size, which makes web pages load faster and reduces bandwidth usage. Using WebP images can lead to sizeable increases in page speed. If your application or website is experiencing performance issues or increased traffic, converting your images may help optimize page performance.

In this tutorial, you will use the command-line tool cwebp to convert images to WebP format, creating scripts that will watch and convert images in a specific directory. Finally, you’ll explore two ways to serve WebP images to your visitors.

Prerequisites

Working with WebP images does not require a particular distribution, but we will demonstrate how to work with relevant software on Ubuntu 16.04 and CentOS 7. To follow this tutorial you will need:

Step 1 — Installing cwebp and Preparing the Images Directory

In this section, we will install software to convert images and create a directory with images as a testing measure.

On Ubuntu 16.04, you can install cwebp, a utility that compresses images to the .webp format, by typing:

  1. sudo apt-get update
  2. sudo apt-get install webp

On CentOS 7, type:

  1. sudo yum install libwebp-tools

To create a new images directory called webp in the Apache web root (located by default at /var/www/html) type:

  1. sudo mkdir /var/www/html/webp

Change the ownership of this directory to your non-root user sammy:

  1. sudo chown sammy: /var/www/html/webp

To test commands, you can download free JPEG and PNG images using wget. This tool is installed by default on Ubuntu 16.04; if you are using CentOS 7, you can install it by typing:

  1. sudo yum install wget

Next, download the test images using the following commands:

  1. wget -c "https://upload.wikimedia.org/wikipedia/commons/2/24/Junonia_orithya-Thekkady-2016-12-03-001.jpg?download" -O /var/www/html/webp/image1.jpg
  2. wget -c "https://upload.wikimedia.org/wikipedia/commons/5/54/Mycalesis_junonia-Thekkady.jpg" -O /var/www/html/webp/image2.jpg
  3. wget -c "https://cdn.pixabay.com/photo/2017/07/18/15/39/dental-care-2516133_640.png" -O /var/www/html/webp/logo.png

Note: These images are available for use and redistribution under the Creative Commons Attribution-ShareAlike license and the Public Domain Dedication.

Most of your work in the next step will be in the /var/www/html/webp directory, which you can move to by typing:

  1. cd /var/www/html/webp

With the test images in place, and the Apache web server, mod_rewrite, and cwebp installed, you are ready to move on to converting images.

Step 2 — Compressing Image Files with cwebp

Serving .webp images to site visitors requires .webp versions of image files. In this step, you will convert JPEG and PNG images to the .webp format using cwebp. The general syntax of the command looks like this:

  1. cwebp image.jpg -o image.webp

The -o option specifies the path to the WebP file.

Since you are still in the /var/www/html/webp directory, you can run the following command to convert image1.jpg to image1.webp and image2.jpg to image2.webp:

  1. cwebp -q 100 image1.jpg -o image1.webp
  2. cwebp -q 100 image2.jpg -o image2.webp

Setting the quality factor -q to 100 retains 100% of the image quality; if not specified, the default value is 75.

Next, inspect the size of the JPEG and WebP images using the ls command. The -l option will show the long listing format, which includes the size of the file, and the -h option will make sure that ls prints human readable sizes:

  1. ls -lh image1.jpg image1.webp image2.jpg image2.webp
Output
-rw-r--r-- 1 sammy sammy 7.4M Oct 28 23:36 image1.jpg -rw-r--r-- 1 sammy sammy 3.9M Feb 18 16:46 image1.webp -rw-r--r-- 1 sammy sammy 16M Dec 18 2016 image2.jpg -rw-r--r-- 1 sammy sammy 7.0M Feb 18 16:59 image2.webp

The output of the ls command shows that the size of image1.jpg is 7.4M, while the size of image1.webp is 3.9M. The same goes for image2.jpg (16M) and image2.webp (7M). These files are almost half of their original size!

To save the complete, original data of images during compression, you can use the -lossless option in place of -q. This is the best option to maintain the quality of PNG images. To convert the downloaded PNG image from step 1, type:

  1. cwebp -lossless logo.png -o logo.webp

The following command shows that the lossless WebP image size (60K) is approximately half the size of the original PNG image (116K):

  1. ls -lh logo.png logo.webp
Output
-rw-r--r-- 1 sammy sammy 116K Jul 18 2017 logo.png -rw-r--r-- 1 sammy sammy 60K Feb 18 16:42 logo.webp

The converted WebP images in the /var/www/html/webp directory are about 50% smaller than their JPEG and PNG counterparts. In practice, compression rates can differ depending on certain factors: the compression rate of the original image, the file format, the type of conversion (lossy or lossless), the quality percentage, and your operating system. As you convert more images, you may see variations in conversion rates related to these factors.

Step 3 — Converting JPEG and PNG Images in a Directory

Writing a script will simplify the conversion process by eliminating the work of manual conversion. We will now write a conversion script that finds JPEG files and converts them to WebP format with 90% quality, while also converting PNG files to lossless WebP images.

Using nano or your favorite editor, create the webp-convert.sh script in your user’s home directory:

  1. nano ~/webp-convert.sh

The first line of the script will look like this:

~/webp-convert.sh
find $1 -type f -and \( -iname "*.jpg" -o -iname "*.jpeg" \)

This line has the following components:

  • find: This command will search for files within a specified directory.
  • $1: This positional parameter specifies the path of the images directory, taken from the command line. Ultimately, it makes the location of the directory less dependent on the location of the script.
  • -type f: This option tells find to look for regular files only.
  • -iname: This test matches filenames against a specified pattern. The case-insensitive -iname test tells find to look for any filename that ends with .jpg (*.jpg) or .jpeg (*.jpeg).
  • -o: This logical operator instructs the find command to list files that match the first -iname test (-iname "*.jpg") or the second (-iname "*.jpeg").
  • (): Parentheses around these tests, along with the -and operator, ensure that the first test (i.e. -type f) is always executed.

The second line of the script will convert the images to WebP using the -exec parameter. The general syntax of this parameter is -exec command {} \;. The string {} is replaced by each file that the command iterates through, while the ; tells find where the command ends:

~/webp-convert.sh
find $1 -type f -and \( -iname "*.jpg" -o -iname "*.jpeg" \) \
-exec bash -c 'commands' {} \;

In this case, the -exec parameter will require more than one command to search for and convert images:

  • bash: This command will execute a small script that will make the .webp version of the file if it doesn’t exist. This script will get passed to bash as a string thanks to the -c option.
  • 'commands': This placeholder is the script that will make .webp versions of your files.

The script inside 'commands' will do the following things:

  • Create a webp_path variable.
  • Test whether or not the .webp version of the file exists.
  • Make the file if it does not exist.

The smaller script looks like this:

~/webp-convert.sh
...
webp_path=$(sed 's/\.[^.]*$/.webp/' <<< "$0");
if [ ! -f "$webp_path" ]; then 
  cwebp -quiet -q 90 "$0" -o "$webp_path";
fi;

The elements in this smaller script include:

  • webp_path: This variable will be generated using sed and the matched file name from the bash command, denoted by the positional parameter $0. A here string (<<<) will pass this name to sed.
  • if [ ! -f "$webp_path" ]: This test will establish whether or not a file named "$webp_path" already exists, using the logical not operator (!).
  • cwebp: This command will create the file if it doesn’t exist, using the -q option so as not to print output.

With this smaller script in place of the 'commands' placeholder, the full script to convert JPEG images will now look like this:

~/webp-convert.sh
# converting JPEG images
find $1 -type f -and \( -iname "*.jpg" -o -iname "*.jpeg" \) \
-exec bash -c '
webp_path=$(sed 's/\.[^.]*$/.webp/' <<< "$0");
if [ ! -f "$webp_path" ]; then 
  cwebp -quiet -q 90 "$0" -o "$webp_path";
fi;' {} \;

To convert PNG images to WebP, we’ll take the same approach, with two differences: First, the -iname pattern in the find command will be "*.png". Second, the conversion command will use the -lossless option instead of the quality -q option.

The completed script looks like this:

~/webp-convert.sh
#!/bin/bash

# converting JPEG images
find $1 -type f -and \( -iname "*.jpg" -o -iname "*.jpeg" \) \
-exec bash -c '
webp_path=$(sed 's/\.[^.]*$/.webp/' <<< "$0");
if [ ! -f "$webp_path" ]; then 
  cwebp -quiet -q 90 "$0" -o "$webp_path";
fi;' {} \;

# converting PNG images
find $1 -type f -and -iname "*.png" \
-exec bash -c '
webp_path=$(sed 's/\.[^.]*$/.webp/' <<< "$0");
if [ ! -f "$webp_path" ]; then 
  cwebp -quiet -lossless "$0" -o "$webp_path";
fi;' {} \;

Save the file and exit the editor.

Next, let’s put the webp-convert.sh script into practice using the files in the /var/www/html/webp directory. Make sure that the script file is executable by running the following command:

  1. chmod a+x ~/webp-convert.sh

Run the script on the images directory:

  1. ./webp-convert.sh /var/www/html/webp

Nothing happened! That’s because we already converted these images in step 2. Moving forward, the webp-convert script will convert images when we add new files or remove the .webp versions. To see how this works, delete the .webp files we created in step 2:

  1. rm /var/www/html/webp/*.webp

After deleting all of the .webp images, run the script again to make sure it works:

  1. ./webp-convert.sh /var/www/html/webp

The ls command will confirm that the script has converted the images successfully:

  1. ls -lh /var/www/html/webp
Output
-rw-r--r-- 1 sammy sammy 7.4M Oct 28 23:36 image1.jpg -rw-r--r-- 1 sammy sammy 3.9M Feb 18 16:46 image1.webp -rw-r--r-- 1 sammy sammy 16M Dec 18 2016 image2.jpg -rw-r--r-- 1 sammy sammy 7.0M Feb 18 16:59 image2.webp -rw-r--r-- 1 sammy sammy 116K Jul 18 2017 logo.png -rw-r--r-- 1 sammy sammy 60K Feb 18 16:42 logo.webp

The script in this step is the foundation of using WebP images in your site, as you will need a working version of all images in WebP format to serve to visitors. The next step will cover how to automate the conversion of new images.

Step 4 — Watching Image Files in a Directory

In this step, we will create a new script to watch our images directory for changes and automatically convert newly created images.

Creating a script that watches our images directory can address certain issues with the webp-convert.sh script as it’s written. For example, this script will not identify if we have renamed an image. If we had an image called foo.jpg, ran webp-convert.sh, renamed that file bar.jpg, and then ran webp-convert.sh again, we would have duplicate .webp files (foo.webp and bar.webp). To solve this issue, and to avoid running the script manually, we will add watchers to another script. Watchers watch specified files or directories for changes and run commands in response to those changes.

The inotifywait command will set up watchers in our script. This command is part of the inotify-tools package, a set of command line tools that provide a simple interface to the inotify kernel subsystem. To install it on Ubuntu 16.04 type:

  1. sudo apt-get install inotify-tools

With CentOS 7, the inotify-tools package is available on the EPEL repository. Install the EPEL repository and inotify-tools package using the following commands:

  1. sudo yum install epel-release
  2. sudo yum install inotify-tools

Next, create the webp-watchers.sh script in your user’s home directory using nano:

  1. nano ~/webp-watchers.sh

The first line in the script will look like this:

~/webp-watchers.sh
inotifywait -q -m -r --format '%e %w%f' -e close_write -e moved_from -e moved_to -e delete $1

This line includes the following elements:

  • inotifywait: This command watches for changes to a certain directory.
  • -q: This option will tell inotifywait to be quiet and not produce a lot of output.
  • -m: This option will tell inotifywait to run indefinitely and not exit after receiving a single event.
  • -r: This option will set up watchers recursively, watching a specified directory and all its sub-directories.
  • --format: This option tells inotifywait to monitor changes using the event name followed by the file path. The events we want to monitor are close_write (triggered when a file is created and completely written to the disk), moved_from and moved_to (triggered when a file is moved), and delete (triggered when a file is deleted).
  • $1: This positional parameter holds the path of the changed files.

Next, let’s add a grep command to establish whether or not our files are JPEG or PNG images. The -i option will tell grep to ignore case, -E will specify that grep should use extended regular expressions, and --line-buffered will tell grep to pass the matched lines to a while loop:

~/webp-watchers.sh
inotifywait -q -m -r --format '%e %w%f' -e close_write -e moved_from -e moved_to -e delete $1 | grep -i -E '\.(jpe?g|png)$' --line-buffered

Next, we will build a while loop with the read command. read will process the event inotifywait has detected, assigning it to a variable called $operation and the processed file path to a variable named $path:

~/webp-watchers.sh
...
| while read operation path; do
  # commands
done;

Let’s combine this loop with the rest of our script:

~/webp-watchers.sh
inotifywait -q -m -r --format '%e %w%f' -e close_write -e moved_from -e moved_to -e delete $1 \
| grep -i -E '\.(jpe?g|png)$' --line-buffered \
| while read operation path; do
  # commands
done;

After the while loop has checked the event, the commands inside the loop will take the following actions, depending on the result:

  • Create a new WebP file if a new image file was created or moved to the target directory.
  • Delete the WebP file if the associated image file was deleted or moved from the target directory.

There are three main sections inside the loop. A variable called webp_path will hold the path to the .webp version of the subject image:

~/webp-watchers.sh
...
webp_path="$(sed 's/\.[^.]*$/.webp/' <<< "$path")";

Next, the script will test which event has happened:

~/webp-watchers.sh
...
if [ $operation = "MOVED_FROM" ] || [ $operation = "DELETE" ]; then
  # commands to be executed if the file is moved or deleted
elif [ $operation = "CLOSE_WRITE,CLOSE" ] || [ $operation = "MOVED_TO" ]; then
  # commands to be executed if a new file is created
fi;

If the file has been moved or deleted, the script will check if the .webp version exists. If it does, the script will remove it using rm:

~/webp-watchers.sh
...
if [ -f "$webp_path" ]; then
  $(rm -f "$webp_path");
fi;

For newly created files, compression will happen as follows:

  • If the matched file is a PNG image, the script will use lossless compression.
  • If it’s not, the script will use lossy compression with the -quality option.

Let’s add the cwebp commands that will do this work to the script:

~/webp-watchers.sh
...
if [ $(grep -i '\.png$' <<< "$path") ]; then
  $(cwebp -quiet -lossless "$path" -o "$webp_path");
else
  $(cwebp -quiet -q 90 "$path" -o "$webp_path");
fi;

In full, the webp-watchers.sh file will look like this:

~/webp-watchers.sh
#!/bin/bash
echo "Setting up watches.";

# watch for any created, moved, or deleted image files
inotifywait -q -m -r --format '%e %w%f' -e close_write -e moved_from -e moved_to -e delete $1 \
| grep -i -E '\.(jpe?g|png)$' --line-buffered \
| while read operation path; do
  webp_path="$(sed 's/\.[^.]*$/.webp/' <<< "$path")";
  if [ $operation = "MOVED_FROM" ] || [ $operation = "DELETE" ]; then # if the file is moved or deleted
    if [ -f "$webp_path" ]; then
      $(rm -f "$webp_path");
    fi;
  elif [ $operation = "CLOSE_WRITE,CLOSE" ] || [ $operation = "MOVED_TO" ]; then  # if new file is created
     if [ $(grep -i '\.png$' <<< "$path") ]; then
       $(cwebp -quiet -lossless "$path" -o "$webp_path");
     else
       $(cwebp -quiet -q 90 "$path" -o "$webp_path");
     fi;
  fi;
done;

Save and close the file. Do not forget to make it executable:

  1. chmod a+x ~/webp-watchers.sh

Let’s run this script on the /var/www/html/webp directory in the background, using &. Let’s also redirect standard output and standard error to an ~/output.log, to store output in an readily available location:

  1. ./webp-watchers.sh /var/www/html/webp > output.log 2>&1 &

At this point, you have converted the JPEG and PNG files in /var/www/html/webp to the WebP format, and set up watchers to do this work using the webp-watchers.sh script. It is now time to explore options to deliver WebP images to your website visitors.

Step 5 — Serving WebP Images to Visitors Using HTML Elements

In this step, we will explain how to serve WebP images with HTML elements. At this point there should be .webp versions of each of the test JPEG and PNG images in the /var/www/html/webp directory. We can now serve them to supporting browsers using either HTML5 elements (<picture>) or the mod_rewrite Apache module. We’ll use HTML elements in this step.

The <picture> element allows you to include images directly in your web pages and to define more than one image source. If your browser supports the WebP format, it will download the .webp version of the file instead of the original one, resulting in web pages being served faster. It is worth mentioning that the <picture> element is well-supported in modern browsers that support the WebP format.

The <picture> element is a container with <source> and <img> elements that point to particular files. If we use <source> to point to a .webp image, the browser will see if it can handle it; otherwise, it will fall back to the image file specified in the src attribute in the <img> element.

Let’s use the logo.png file from our /var/www/html/webp directory, which we converted to logo.webp, as an example with <source>. We can use the following HTML code to display logo.webp to any browser that supports WebP format, and logo.png to any browser that does not support WebP or the <picture> element.

Create an HTML file located at /var/www/html/webp/picture.html:

  1. nano /var/www/html/webp/picture.html

Add the following code to the web page to display logo.webp to supporting browsers using the <picture> element:

/var/www/html/webp/picture.html
<picture>
  <source srcset="logo.webp" type="image/webp">
  <img src="logo.png" alt="Site Logo">
</picture>

Save and close the file.

To test that everything is working, navigate to http://your_server_ip/webp/picture.html. You should see the test PNG image.

Now that you know how to serve .webp images directly from HTML code, let’s look at how to automate this process using Apache’s mod_rewrite module.

Step 6 — Serving WebP Images Using mod_rewrite

If we want to optimize the speed of our site, but have a large number of pages or too little time to edit HTML code, then Apache’s mod_rewrite module can help us automate the process of serving .webp images to supporting browsers.

First, create an .htaccess file in the /var/www/html/webp directory using the following command:

  1. nano /var/www/html/webp/.htaccess

The ifModule directive will test if mod_rewrite is available; if it is, it can be activated by using RewriteEngine On. Add these directives to the .htaccess:

/var/www/html/webp/.htaccess
<ifModule mod_rewrite.c>
  RewriteEngine On 
  # further directives
</IfModule>

The web server will make several tests to establish when to serve .webp images to the user. When a browser makes a request, it includes a header to indicate to the server what the browser is capable of handling. In the case of WebP, the browser will send an Accept header containing image/webp. We will check if the browser sent that header using RewriteCond, which specifies the criteria that should be matched in order to carry out the RewriteRule:

/var/www/html/webp/.htaccess
...
RewriteCond %{HTTP_ACCEPT} image/webp

Everything should be filtered out but JPEG and PNG images. Using RewriteCond again, add a regular expression (similar to what we used in the previous sections) to match the requested URI:

/var/www/html/webp/.htaccess
...
RewriteCond %{REQUEST_URI}  (?i)(.*)(\.jpe?g|\.png)$ 

The (?i) modifier will make the match case-insensitive.

To check if the .webp version of the file exists, use RewriteCond again as follows:

/var/www/html/webp/.htaccess
...
RewriteCond %{DOCUMENT_ROOT}%1.webp -f

Finally, if all previous conditions were met, RewriteRule will redirect the requested JPEG or PNG file to its associated WebP file. Notice that this will redirect using the -R flag, rather than rewrite the URI. The difference between rewriting and redirecting is that the server will serve the rewritten URI without telling the browser. For example, the URI will show that the file extension is .png, but it will actually be a .webp file. Add RewriteRule to the file:

/var/www/html/webp/.htaccess
...
RewriteRule (?i)(.*)(\.jpe?g|\.png)$ %1\.webp [L,T=image/webp,R] 

At this point, the mod_rewrite section in the .htaccess file is complete. But what will happen if there is an intermediate caching server between your server and the client? It could serve the wrong version to the end user. That is why it is worth checking to see if mod_headers is enabled, in order to send the Vary: Accept header. The Vary header indicates to caching servers (like proxy servers) that the content type of the document varies depending on the capabilities of the browser which requests the document. Moreover, the response will be generated based on the Accept header in the request. A request with a different Accept header might get a different response. This header is important because it prevents cached WebP images from being served to non-supporting browsers:

/var/www/html/webp/.htaccess
...
<IfModule mod_headers.c>
  Header append Vary Accept env=REDIRECT_accept
</IfModule>

Finally, at the end of the .htaccess file, set the MIME type of the .webp images to image/webp by using the AddType directive. This will serve the images using the right MIME type:

/var/www/html/webp/.htaccess
...
AddType image/webp .webp

This is the final version of our .htaccess file:

/var/www/html/webp/.htaccess
<ifModule mod_rewrite.c>
  RewriteEngine On 
  RewriteCond %{HTTP_ACCEPT} image/webp
  RewriteCond %{REQUEST_URI}  (?i)(.*)(\.jpe?g|\.png)$ 
  RewriteCond %{DOCUMENT_ROOT}%1.webp -f
  RewriteRule (?i)(.*)(\.jpe?g|\.png)$ %1\.webp [L,T=image/webp,R] 
</IfModule>

<IfModule mod_headers.c>
  Header append Vary Accept env=REDIRECT_accept
</IfModule>

AddType image/webp .webp

Note: You can merge this .htaccess with another .htaccess file, if it exists. If you are using WordPress, for instance, you should copy this .htaccess file and paste it at the top of the existing file.

Let’s put what we have done in this step into practice. If you have followed the instructions in the previous steps, you should have logo.png and logo.webp images in /var/www/html/webp. Let’s use a simple <img> tag to include logo.png in our web page. Create a new HTML file to test the setup:

  1. nano /var/www/html/webp/img.html

Enter the following HTML code in the file:

/var/www/html/webp/img.html
<img src="logo.png" alt="Site Logo">

Save and close the file.

When you visit the web page using Chrome by visiting http://your_server_ip/webp/img.html, you will notice that the served image is the .webp version (try opening the image in a new tab). If you use Firefox, you will get a .png image automatically.

Conclusion

In this tutorial, we have covered basic techniques for working with WebP images. We have explained how to use cwebp to convert files, as well as two options to serve these images to users: HTML5’s <picture> element and Apache’s mod_rewrite.

In order to customize the scripts from this tutorial, you can look at some of these resources:

  • To learn more about the features of the WebP format and how to use the conversion tools, see the WebP documentation.
  • To see more details about the usage of <picture> element, see its documentation on MDN.
  • To fully understand how to use mod_rewrite, see its documentation.

Using the WebP format for your images will reduce file sizes by a considerable amount. This can lower bandwidth usage and make page loads faster, particularly if your web site uses a lot of images.

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about our products

About the authors


Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
10 Comments


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

This comment has been deleted

    Hello,

    Is it possible to follow this procedure on the nginx webserver? is there a tutorial about it?

    thank you very much

    The .htaccess code contains a logical error.

    The Vary header is only set when the Client sends HTTP_ACCEPT image/webp.

    But the Vary header should also be sent if the client does not accept image/webp because if not, the caching system will store png/jpg in the cache and overwrite the webp version even for the clients that have webp enabled…

    You can observe the error when you have caching enabled on your server and follow these steps:

    Step 1. Call website: Webp images are served

    Step 2. Deactivate webp in Firefox (about:config, image.http.accept to “/”) and reload wesite: Webp images are not served (as expected)

    Step 3. Reactivate webp in Firefox (about:config, image.http.accept to “image/webp,/”) and reload website: webp images are still not served

    The Vary header should be sent for all images, not only when the client accepts webp:

    <IfModule mod_headers.c>
      <FilesMatch "\.(jpe?g|png|gif)$">
        Header append Vary Accept
     </FilesMatch>
    </IfModule>
    

    Thank you! How can I convert image.jpg to image.jpg.webp?

    This does not work for my use case, Apache2 2.4.41 running on Ubuntu 18.04.4 (64-bit) with PHP 7.4.4, when aliased directories are used. I really do not know if it did. First time trying it, and it does not work.

    Yes, I emptied my cache first. Yes, I restarted the server first. Still always getting the PNG or JPEG JFIF file, instead of the WebP files. Yes, both browsers understand image/webp. I can load a WebP file directly. Yes, I did check the files served, (size and nature), and they are the big JPEG JFIF or PNG files, and not the smaller WebP files. I even had a different image with the same filename, ( testimg.jpg and testimg.webp, but of different actual images), so that I can tell them apart. (Both images are fuchsia text on a dark blue/green gradient. In one, the text says, “This is a JPEG JFIF image testimg.jpg”, (with the text box on a blue background), while the other says, “This is a WebP image testimg.webp”, (with the text box on a transparent bg).

    Indeed,

    <picture>
      <source media="(min-width: 650px)" type="image/webp" srcset="image/K3PX5071.webp"/>
      <source media="(min-width: 465px)" type="image/webp" srcset="image/K-3.TheStache-1721.webp"/>
      <source media="(min-width: 650px)" srcset="image/K3PX5071.jpg"/>
      <source media="(min-width: 465px)" srcset="image/K-3.TheStache-1723.jpg"/>
      <source media="(min-width: 1px)" srcset="image/K3P10635.webp"/>
      <img src="image/K3P10635.jpg" alt="Flowers"/>
    </picture>
    
    

    works with both browsers, to serve the WebP files specified. If the two first lines are removed, I get the JPEG files.

    After intense reading, the issue has become clear. This solution only works with images within the document root, and sub-directories. On my server, I have several sites, and many of my images are served from a common image directory, in an aliased location.

    [BEGIN unnecessary detail for clarity’s sake ]

    So my server root, ($SR), is at [ /var/www ], with my main document root, ($DR), [ {http}://Phenobuntu/ ], at [ /var/www/html ], and I have other sites at [ $SR/wp ], [ $SR/drpl ], [ $SR/Projects/GanttProject ]. To top it off, the last three sites are Aliased as, [ {http}://wpbuntu/ ], [ {http}://drplbuntu/ ], and [ {http}://gnttbuntu/ ] respectively. In addition to this, I have my common CSS, PHP scripts, and images, in different directories, as, [ $SR/src/css ], [ $SR/src/theme ], and [ $SR/src/image ], respectively, (so as to maintain a consistent look across sites). These are aliased as, [ /css ], [ /theme ], and, [ / image], respectively, for each site.

    Therefore, [ {http}://Phenobuntu/image/testimg.jpg ], will reference precisely the same image as, [ {http}://wpbuntu/image/testimg.jpg ], [ {http}://drplbuntu/image/testimg.jpg ], and [ {http}://gnttbuntu/image/testimg.jpg ] but, [ {http}://phenobuntu/index.html ], [ {http}://wpbuntu/index.html ], [ {http}://drplbuntu/index.html ], and [ {http}://gnttbuntu/index.html ], are all different files.

    This solution, (as with all other solutions I have tried), all assume that all images are being served from the same directory —or sub-directory— as ${DOCUMENT_ROOT}. In my case, this is not so. It will work for files in the [ wpbuntu/wp-content/uploads/ ] directories and subs, or at, [ phenobuntu/iso/image/ ] but not for images at [ wpbuntu/image/ ] nor, [ phenobuntu/image/ ] directories and subs, et al.

    [/END unnecessary detail for clarity’s sake ]

    So I needed to come up with a solution which works globally, for aliased directories. That is why the solution was not working for me. The solution was incomplete for all use cases.

    This was the solution I figured out for Apache2 version ≥2.4.35, where aliases are being used. The code has intensive internal documentation.

    
    # BEGIN WebP replacement
    
    #   Backward compatibility ruleset for
    #   rewriting image.jpeg, image.jpg, or image.png to image.webp
    #   when and only when image.webp exists.
    #   This particular script IS case-sensitive, and will not work
    #   for image.JPEG, image.JPG, image.PNG, or iMaGe.JpEg.
    #   It will work, however, to replace, iMaGe.jpeg, with iMaGe.webp.
    #   In other words, the filename MUST be a case-sensitive match, AND
    #   the file suffix MUST be lower-case.
    #   (This assumes that your server is case- sensitive. Some servers
    #   may be set to case-insensitive mode. In that case, the rule will
    #   still work.
    
        Options Indexes FollowSymLinks
        Require all granted
    
        # We can possibly assume that mod_rewrite is installed and enabled.
        # This can be a dangerous assumption, and ought to be tested
        # with <ifModule mod_rewrite.c> conditional clause.
        # I was testing, and certain options do not work within in <if> statement. 
        # I live on the edge! (I know my server).
        # Please un-comment this line. If it does not work, AND you are certain
        # that mod_rewrite is installed, comment it.
       <IfModule mod_rewrite.c>
    
        # Turn on rewrite engine.
        RewriteEngine on
    
        # Ensure this matches your document root. (Usually optional).
        RewriteBase /
    
        # Do we care if the JPEG JFIF or PNG file exists? (I do not)!
        # Many CMS themes only allow a JPEG or PNG file in certain places.
        # This ensures that if you cannot tell the theme to use a WebP file, 
        # use a JPEG file in the theme, and the WebP file will be served.
        # Besides, if the original file did not exist, a 404 error would 
        # have been returned. May as well get an image instead.
        # If we care that the JPEG/PNG exists, 
        # (althoug it is not needed since we have a WebP replacement),
        # then uncomment the next line.
    #    RewriteCond %{CONTEXT_DOCUMENT_ROOT}/$1.$2 -f
    
        # We re-direct only if the browser accepts WebP images.
        RewriteCond %{HTTP_ACCEPT} image/webp
    
        # We re-direct only if the WebP equivalent exists.
        # If one has neglected to upload the replacement file, 
        # no substitution will be made.
        RewriteCond %{CONTEXT_DOCUMENT_ROOT}/$1.webp -f
    
        # The first rule replaces the [ image.jpe?g|png ] with [ image.webp ]
        # in the browser interface, and requires another server request.
        # The second rule will leave the original request in the browser,
        # but still serve the WebP file.
        # Un-comment only one rewrite rule!
        RewriteRule ^(.*)\.(jpe?g|png)$ %{CONTEXT_PREFIX}/$1.webp [R,L,T=image/webp]
    #    RewriteRule ^(.*)\.(jpe?g|png)$ %{CONTEXT_PREFIX}/$1.webp [L,T=image/webp]
    
        # End the <ifModule…> statement, if it was invoked.
        # Comment this line if the conditional was not used.
       </IfModule>
    
    # %{CONTEXT_PREFIX} and %{CONTEXT_DOCUMENT_ROOT} are new Apache2
    # version ≥ 2.4.35 variables, which may not exists, or be the same thing,
    # on older/other HTTP servers. These variables somewhat take into consideration
    # the use of Aliases, and other types of server-config/site-config
    # directory re-directs. 
    
    # END WebP replacement
    
    

    This .htaccess file has to go in all document root directories involved. That is, in my case, I need to put it in [ $SR/html ], [ $SR/wp ], [ $SR/drpl ], [$SR/joomla ], [ $SR/Projects/GanntProject ], and [ SR$/src/image ].

    For existing redirects in existing .htaccess files, place the snippet at the top of the file, to occur before other redirects. E.g., for Wordpress, place this snippet above the line marked, “# BEGIN WordPress” in the .htaccess file.

    Hi,

    Nice and helpful article, thank you!

    What will happen if we don’t convert previous images and we just want to use it for new images, so if a previous jpg image doesn’t have the any related webp image, will this code will still server the jpg image?

    Thank you!

    To make the Vary header work on LiteSpeed too, do this:

    <IfModule mod_headers.c>
        # Apache appends "REDIRECT_" in front of the environment variables, but LiteSpeed does not.
        # These next line is for Apache, in order to set environment variables without "REDIRECT_"
        SetEnvIf REDIRECT_accept 1 accept=1
    
        # Make CDN caching possible.
        # The effect is that the CDN will cache both the webp image and the jpeg/png image and return the proper
        # image to the proper clients (for this to work, make sure to set up CDN to forward the "Accept" header)
        Header append Vary Accept env=accept
    </IfModule>
    

    Hi, Very helpful article - Thank you! Can you add all the steps to achieve the same redirect (.jpg to .webp) with Nginx? As you know it is not that easy to convert an .htaccess file to nginx syntax. regards Nikolay Kabaivanov

    This comment has been deleted

      Try DigitalOcean for free

      Click below to sign up and get $200 of credit to try our products over 60 days!

      Sign up

      Join the Tech Talk
      Success! Thank you! Please check your email for further details.

      Please complete your information!

      Become a contributor for community

      Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

      DigitalOcean Documentation

      Full documentation for every DigitalOcean product.

      Resources for startups and SMBs

      The Wave has everything you need to know about building a business, from raising funding to marketing your product.

      Get our newsletter

      Stay up to date by signing up for DigitalOcean’s Infrastructure as a Newsletter.

      New accounts only. By submitting your email you agree to our Privacy Policy

      The developer cloud

      Scale up as you grow — whether you're running one virtual machine or ten thousand.

      Get started for free

      Sign up and get $200 in credit for your first 60 days with DigitalOcean.*

      *This promotional offer applies to new accounts only.