Introduction
Especially if you have a website that has been around for a while, you will have accumulated a lot of image files throughout the years. If your site is hosted on a dedicated server with shell access, you can use ImageMagick on the command line to turn all your existing images into WebP-versions. However, you’ll still need a .htaccess rewrite directive in order to actually serve those converted files to browsers that support the format. The approach I am taking is to direct all requests for PNG- and JPEG- files and redirect them to a PHP script that uses the PHP-extension Imagick to convert and serve WebP-files automatically. Most modern shared hosters have the PHP extension Imagick installed.
All examples shown in this article are tailored to a WordPress installation with all images of the website being in the default location “/wp-content/wuploads/*”. Make sure to adapt all file-paths provided in this article if you intend to use this method with a different CMS.
PHP Script
The PHP script itself is very straight-forward. In a standard WordPress installation the PHP script needs to be located at “/wp-content/uploads.webp.php” and will be called by a .htaccess rewrite rule that I’ll cover later. Whatever image file is passed to the script through the “file=” GET parameter will be converted to a WebP image version if possible. Since the script could technically be called from unauthorized, external users, it is absolutely mandatory to sanitize and validate the file path. If the passed file name is missing or invalid, the script will throw a “400 bad request” error. Assuming the path is valid and the file exists, an Imagick object is created, metadata is stripped, the compression quality set to 75 % and the resulting WebP image is sent to the browser. The image conversion function is neatly packaged inside a try-catch-block. In case something goes wrong, the script will return the appropriate “HTTP/1.1 500 Internal Server Error” response code.
<?php
// Sanitize and validate the file path
$filePath = isset($_GET['file']) ? $_GET['file'] : null;
if (!$filePath || !is_file($filePath)) {
header('HTTP/1.1 400 Bad Request');
echo 'Invalid or missing file parameter.';
exit;
}
try {
// Initialize Imagick
$im = new Imagick();
$im->readImage($filePath);
$im->stripImage(); // Remove unnecessary metadata
// Set image format and compression
$im->setImageFormat('webp');
// Use a constant for quality if desired
$im->setImageCompressionQuality(75);
// Output the image as a WebP format
header('Content-Type: image/webp');
echo $im->getImagesBlob();
} catch (Exception $e) {
// Handle errors gracefully
header('HTTP/1.1 500 Internal Server Error');
echo 'Error processing image: ' . $e->getMessage();
} finally {
// Clean up
if (isset($im)) {
$im->clear();
}
}
?>
.htaccess Directives
The job of the .htaccess directives is to direct appropriate requests to the PHP script. Whether or not a request is appropriate for redirection is measured through a few conditions. The first rule ensures that the requesting browser (or other client) actually supports the WebP format. I’ll be honest and say that on my websites this condition is completely missing from the .htaccess files: It’s 2025, if your browser doesn’t support WebP, you’re SOL on my websites. Get with the times! The next condition is that the file either ends in .jpg, .jpeg or .png. Feel free to also add .gif or any other Imagick supported image format if you like. Imagick actually also handles animated GIFs quite well. Lastly, the conditions verify that the requested file matching all previous conditions is actually an existing file. If all those conditions are met, the script is internally rewriting the path to “/wp-content/uploads/webp.php?file=<filename>” where <filename> is of course the name of the image file requested originally.
RewriteBase /wp-content/uploads/
<IfModule mod_rewrite.c>
# Check if the browser accepts WEBP
RewriteCond %{HTTP_ACCEPT} image/webp
# Check if the request is for an existing image file (jpg, png, jpeg)
RewriteCond %{REQUEST_FILENAME} \.(jpe?g|png)$ [NC]
RewriteCond %{REQUEST_FILENAME} -f
# Redirect the request to the webp.php script for conversion
RewriteRule ^(.*)\.(jpe?g|png)$ /wp-content/uploads/webp.php?file=$1.$2 [NC,L]
</IfModule>
Note that this is an internal redirect so the browser does not see a 301 redirect of any kind. Your old image file paths remain valid, they’ll only get served the WebP version instead.
Store WebP-Versions Permanently
So far the PHP script renders a WebP version of an image at runtime whenever the image is requested. If the same image is requested 10 times in a row, the PHP script will do the same job 10 times in a row. Even if you do have the necessary horsepower on your server, rendering an image on request will always take longer than simply serving an existing file without any processing. By adding just one line to the code (shown in bold), the PHP script can be directed to store the WebP image permanently alongside the original image. It’ll simply add “.webp” to the xisting file name. So if you have an image located in “/wp-content/uploads/2030/13/image.png”, a converted WebP version will be stored as “/wp-content/uploads/2030/13/image.png.webp”.
echo $im->getImagesBlob();
$im->writeImage($filePath.".webp");
By adding another .htaccess directive to run before the directive shown above, any request to an image that has previously been converted to a WebP image will be redirected to supporting browsers immediately, bypassing the PHP script completely.
<IfModule mod_rewrite.c>
# Check if the browser accepts WEBP
RewriteCond %{HTTP_ACCEPT} image/webp
# Only apply the rule if the requested file is an existing jpg, png, or jpeg
RewriteCond %{REQUEST_FILENAME} \.(jpe?g|png)$ [NC]
RewriteCond %{REQUEST_FILENAME}.webp -f
# Rewrite the request to the corresponding .webp file
RewriteRule ^(.*)\.(jpe?g|png)$ $1.$2.webp [L,T=image/webp]
</IfModule>
You can see the script in action on this site. For instance, if you click in on the title image of this post you’ll notice that despite the file ending in .png, it is actually served as a WebP image. You might also notice that the image has a watermark in the bottom right corner. The watermark is also automatically inserted by my version of the PHP script.
The files can be downloaded from my WebP GitHub repository.
Westerhold, S. (2025), "WebP-Images without Plugin". Baltic Lab High Frequency Projects Blog. ISSN (Online): 2751-8140., https://baltic-lab.com/2025/01/webp-images-without-plugin/, (accessed: January 14, 2025).
- WebP-Images without Plugin - January 14, 2025
- Firewall Rules with (dynamic) DNS Hostname - January 14, 2025
- Restoring proxied visitor IPs from Cloudflare - December 26, 2024