Skip to main content
ModPageSpeed 2.0: AVIF, WebP, and critical CSS — up to 69% less page weight on the live demo

The nginx image optimization module: automatic WebP without a build step

By Otto van der Schaaf

nginx image-optimization webp

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 Accept header 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.com builds 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.

Like this kind of writeup?

We write about how mod_pagespeed and ModPageSpeed actually work, and what we learn shipping them. Get the next post by email.

Read next