The nginx image optimization module: automatic WebP without a build step
If you want nginx itself to convert images to WebP, your options are narrow. You can write a transcoding pipeline yourself with libvips or ImageMagick, you can compile ngx_pagespeed against your nginx, or you can install a packaged module. This post is about the last option: a native nginx module that does the conversion in-process, picks the right format per request, and caches what it produces.
mod_pagespeed 1.15 is that module. It runs inside nginx (and Apache) as a native module, with no sidecar or separate service in the request path. It installs from a signed apt or yum repository. The rest of this post covers what a server-layer image module actually does, how to install it, and where it fits against two alternatives: a do-it-yourself libvips script, and the RHEL-only packages from GetPageSpeed.
What a server-layer image module does
Serving a smaller image involves more than the encode step. A module that lives inside nginx handles four things on your behalf.
Transcode on serve
When a browser requests /images/hero.jpg, the module decodes the original and re-encodes it to WebP. A JPEG becomes a smaller WebP; a PNG with a flat palette becomes a much smaller WebP. WebP beats the original JPEG or PNG substantially at the same visual quality. The output is generated from your existing image URLs, so nothing in your templates or CMS changes. (For AVIF, which compresses smaller still, see ModPageSpeed 2.0 at the end of this post.)
Decode once, encode what is needed
Decoding is the expensive part. A 10-megapixel JPEG expands to roughly 40 MB of raw pixel data in memory. A naive setup decodes the source once per output, which means decoding the same image again for every format or size it produces. The module decodes once, then encodes the outputs it needs — WebP and an optimized version of the original — from that single pixel buffer. You pay the decode cost one time and amortize it across the variants you serve.
Cache the variant
Each generated variant is written to a disk cache keyed by the source URL and the client capabilities it was built for. The next visitor whose browser matches that capability gets a cache hit — the module serves the stored file directly, with no re-encoding. Transcoding happens once per variant, not once per request. For how ModPageSpeed 2.0 extends this to AVIF and a wider variant matrix, see Automatic WebP/AVIF on nginx: one decode, 37 variants.
Content-negotiate on Accept
Browsers announce what they can decode in the Accept header. A request carrying image/webp gets WebP; an older client that announces no modern format gets an optimized version of the original. WebP support is effectively universal across current browsers, so nearly every visitor gets the smaller file, with automatic fallback for the rare client that cannot take it — all from one set of URLs. For AVIF, which sits near 94% global support as of 2026 and compresses smaller still, ModPageSpeed 2.0 adds it to the same negotiation.
How to install it
The module ships as a prebuilt, signed package from packages.modpagespeed.com. No compiler, no nginx source tree, no matching the module ABI to your nginx build by hand. Debian 11/12/13, Ubuntu 22.04/24.04, and Enterprise Linux 9 are covered, on both amd64 and arm64.
On Debian or Ubuntu:
# Add the signed apt repository
curl -fsSL https://packages.modpagespeed.com/apt/pubkey.gpg \
| sudo gpg --dearmor -o /usr/share/keyrings/modpagespeed.gpg
echo "deb [signed-by=/usr/share/keyrings/modpagespeed.gpg] \
https://packages.modpagespeed.com/apt $(lsb_release -cs) main" \
| sudo tee /etc/apt/sources.list.d/modpagespeed.list
sudo apt update
sudo apt install nginx-module-pagespeed
On Enterprise Linux 9 (AlmaLinux, Rocky, RHEL):
sudo tee /etc/yum.repos.d/modpagespeed.repo <<'EOF'
[modpagespeed]
name=ModPageSpeed
baseurl=https://packages.modpagespeed.com/yum/el9/$basearch
enabled=1
gpgcheck=1
gpgkey=https://packages.modpagespeed.com/yum/pubkey.gpg
EOF
sudo dnf install nginx-module-pagespeed
Load the module and turn it on in nginx.conf:
load_module modules/ngx_pagespeed.so;
pagespeed on;
pagespeed FileCachePath /var/cache/pagespeed;
Reload nginx and image requests start coming back as WebP, matched to each client. The full repository setup, GPG details, and the Apache package live on the apt/yum install page. The signed-package upgrade path is the direct answer to the problem in ngx_pagespeed won’t build against modern nginx: there is no build to fail.
Versus a DIY libvips or ImageMagick script
libvips is excellent, and so is ImageMagick. If you wire one of them into a build step or a request handler, you can produce WebP too. The difference is everything around the encoder.
With a DIY pipeline, you own:
- The cache. You decide where variants live, when they expire, how they are keyed, and how eviction works when the disk fills.
- The format logic. You read the
Acceptheader yourself, decide the fallback order, and serve the right file for every image on every route. - The decode discipline. Without care, you decode the same source once per format and once per size. Getting back to one-decode-many-variants is work you do by hand.
- The integration. A script in a build step optimizes images you know about at build time. Images uploaded to a CMS, generated by users, or referenced by third-party CSS are not in that set unless you extend the pipeline to reach them.
None of this is hard in isolation. It adds up to a small service that you maintain, monitor, and debug. The module folds all four into one package that updates through your existing apt or yum channel. You trade the flexibility of a hand-built pipeline for not having to build, run, or patch one.
Versus GetPageSpeed’s packaging
GetPageSpeed is the other place you can get a packaged ngx_pagespeed for nginx. They are a packaging vendor: they build ngx_pagespeed and a large catalog of other nginx modules as RPMs and serve them from their own repository, primarily for RHEL and its derivatives on x86_64. For a CentOS, AlmaLinux, or Rocky operator who wants prebuilt binaries instead of compiling, that repository has served that need for a long time.
The differences are factual, not a verdict:
- Maintained core. mod_pagespeed 1.15 ships from us, the team that develops the optimization code, with current image handling and an ongoing CVE-patch stream. GetPageSpeed packages the upstream ngx_pagespeed core as it stands.
- Newer-formats path. The 1.15 module transcodes WebP. If you want AVIF, ModPageSpeed 2.0 runs as an nginx reverse-proxy worker with AVIF output — from the same vendor, through the same signed channel.
- Architecture coverage.
packages.modpagespeed.combuilds for both amd64 and arm64. GetPageSpeed’s pagespeed packages are x86_64. - Distro coverage. We package for Debian and Ubuntu via apt plus Enterprise Linux 9 via yum. GetPageSpeed’s repository targets RHEL-family distributions.
If you are on RHEL x86_64 and already standardized on the GetPageSpeed repository for a dozen other modules, their packaging is a reasonable fit. If you want a maintained module with arm64 builds and Debian/Ubuntu coverage from one signed source, that is what packages.modpagespeed.com is for — and ModPageSpeed 2.0 if you need AVIF.
Scope: this optimizes the origin, not the edge
One thing this module is not: a CDN. It optimizes images at your origin server. There are no global points of presence, no edge nodes in other regions, no anycast network shortening the round trip for a visitor on the far side of the world. A CDN-based image service distributes the optimized bytes close to the user. This module distributes nothing. It makes the bytes smaller where they already live.
For a lot of sites that is the right trade: the images get dramatically smaller, your content never leaves your infrastructure, and the cost is flat instead of metered per transformation. If global edge latency is your primary constraint, the two approaches compose: run the module at the origin to shrink the bytes, and put a CDN in front to distribute them.
The server-layer module gives you automatic WebP, content-negotiated and cached, installed from a signed package with no build step. For AVIF, SVG auto-vectorization, and a wider variant matrix, ModPageSpeed 2.0 runs the same lineage as an nginx reverse-proxy worker. See the 1.1 product page for the supported servers and the feature list for what else the module rewrites beyond images.
Read next
-
AVIF vs WebP in 2026: which to serve, and how to serve both
AVIF is smaller than WebP but slower to encode. In 2026 you can serve AVIF, WebP, or the original per request off the Accept header instead of choosing one format for everyone. Here is how, and why doing it by hand goes wrong at scale.
-
ngx_pagespeed won't build on modern nginx? Here's why, and the prebuilt fix
Why upstream ngx_pagespeed fails to compile against nginx 1.25 and later (sys_siglist, glibc, and toolchain drift), and how to install the maintained, prebuilt nginx-module-pagespeed from packages.modpagespeed.com instead.
-
Fix INP on nginx: a CMS-agnostic plan
How to fix INP on nginx: a CMS-agnostic guide. Server-layer JS minification helps if your JS is bloated; if your stack is already lean, the wins are upstream.