nginx PageSpeed module
A maintained ngx_pagespeed
The original ngx_pagespeed repository is archived and read-only, and the
source no longer builds against current nginx and glibc. mod_pagespeed 1.15 is the
maintained continuation: the same directives and filters, shipped as a prebuilt, signed
nginx-module-pagespeed package for your distro. Nothing to compile.
Why ngx_pagespeed won't build
The archived ngx_pagespeed carries a 2020-vintage build. It compiled
through a gyp harness driven by Python 2, pinned to old gcc and an old
glibc, vendoring its dependencies the way Chromium did in 2012. Point it at a current
nginx, a current glibc, and a current compiler and the build falls over without
substantial patching. The image and TLS dependencies it vendors are also years behind,
including the libwebp version carrying CVE-2023-4863.
mod_pagespeed 1.15 fixed the root cause. The build was migrated off gyp
onto Bazel and a current C++ standard, the vendored decoders and TLS were pulled forward
(libwebp 1.1.0 to 1.5.0, clearing CVE-2023-4863; curl to 8.x), and each package is built
inside its target distro's own container so the glibc and libstdc++ floors match. The
module loads the same way ngx_pagespeed always did:
# /etc/nginx/nginx.conf — same directives as ngx_pagespeed
load_module modules/ngx_pagespeed.so;
pagespeed on;
pagespeed FileCachePath /var/cache/ngx_pagespeed;
pagespeed RewriteLevel CoreFilters;
The directive names and filter set are unchanged from the open-source module, so an
existing pagespeed block carries over as written.
Install it, don't build it
nginx-module-pagespeed is published as signed apt and yum packages at
packages.modpagespeed.com. Add the
repository, install the module against the nginx your distro already ships, and load it.
# Debian / Ubuntu
curl -fsSL https://packages.modpagespeed.com/pubkey.gpg \
| sudo gpg --dearmor -o /usr/share/keyrings/modpagespeed.gpg
# (add the apt source line for your distro — see the repo docs)
sudo apt-get update
sudo apt-get install nginx-module-pagespeed
# AlmaLinux / RHEL 9
sudo dnf install nginx-module-pagespeed
The package drops ngx_pagespeed.so into the nginx modules directory; the
load_module line above does the rest. The packages cover Debian 11/12/13 and
Ubuntu 22.04/24.04 on amd64 and arm64, plus AlmaLinux/RHEL 9 over yum (x86_64). Both
lines are signed with the repository GPG key, so apt and dnf
verify them before install.
| Distribution | Stock nginx | Channel |
|---|---|---|
| Debian 11 / 12 / 13 | distro stock |
apt (amd64 + arm64) |
| Ubuntu 22.04 | distro stock |
apt (amd64 + arm64) |
| Ubuntu 24.04 | 1.24.0 |
apt (amd64 + arm64) |
| AlmaLinux / RHEL 9 | distro stock |
yum (x86_64) |
Why the version pin is tight, and why it matters
An nginx dynamic module is compiled against the binary layout of nginx's internal
structures. Load it into a different nginx and those offsets can be wrong. nginx's
--with-compat flag relaxes some module-compatibility checks, but it does not
pad or freeze the layout of the request structs the module reads. So each package
declares a tight dependency on the distro's stock nginx: on Ubuntu 24.04 that is nginx
1.24.0, and the package refuses to install against a different one rather than load and
misbehave.
The CVE-2026-49975 ABI break, and the build mistake it exposed
On 2026-06-05 an Ubuntu security update for CVE-2026-49975 inserted a new field into the middle of nginx's internal request-headers struct, which is embedded by value in the main request struct. Every field after the insertion shifted by 8 bytes. Our module — built against a vanilla upstream nginx checkout rather than Ubuntu's own patched source — then read the wrong offsets and spun the worker process on the first request it tried to optimize.
The failure was quiet. nginx -t reported a valid configuration, and
--with-compat did not help, because the struct layout still moved
underneath the module. The decisive test was that the exact same .so file
worked on the prior package revision and hung on the patched one. Debian was
unaffected, because it relocated the new counter into a separate module instead of into
the shared struct.
Two defenses, both shipped
- Built against the distro's own nginx source. Each Ubuntu package is now compiled against that distro's patched nginx source instead of the upstream checkout that caused the break, so its struct layout matches the binary the distro ships, including this security patch.
- A runtime ABI guard, because refuse-to-load is impossible. nginx exposes neither its struct layout nor its distro revision at load time, so a module cannot refuse to load on a mismatch. Instead, on the first request the module checks that its phase handler indexes its own slot. If it sees a layout it does not recognize, it logs one alert and degrades to pass-through, serving the origin response as-is. A silent hang becomes one log line and a site that still serves traffic.
What the module does on a page
The filter set is the one ngx_pagespeed always had. It recompresses and resizes images (WebP alongside JPEG, PNG, and GIF), rewrites and minifies CSS and JavaScript, extends cache lifetimes on static assets, and inlines critical CSS. Those rewrites cut byte weight and render-blocking work and change how images are delivered, which is the part of a page that drives Largest Contentful Paint. It is a byte-weight and delivery optimizer, not a front-end refactor: it does not touch your application's JavaScript, so interaction latency (INP) and most layout-shift (CLS) sit outside what it changes.
- Image recompression and resizing, with WebP variants
- CSS and JavaScript rewriting and minification
- Critical CSS inlining and small-CSS inlining
- Combining CSS files and cache extension on static assets
- Lazy-loading images and subresource preload hints
Worked before/after pages for individual filters, and a live demo serving a real page through the module, are at modpagespeed.com/docs/.
In-process module or reverse proxy
There are two ways to run a maintained PageSpeed on nginx. mod_pagespeed 1.1 is the in-process module above: it loads into your existing nginx, keeps the ngx_pagespeed configuration surface, and is the drop-in path for an existing ngx_pagespeed install.
ModPageSpeed 2.0 is a separate, from-scratch C++23 rebuild. It runs as a thin nginx reverse proxy in front of your origin with a separate worker doing optimization out of the request path, so a runaway transcode can no longer stall the traffic-serving process. It adds AVIF, multiple variants per image selected by viewport, pixel density, and Save-Data, and a memory-mapped cache served with no copy on a hit. The trade-off is that the first request for a URL returns the original while the worker optimizes it; later requests get the optimized variant.
Pick the in-process module to keep your current nginx and config; pick 2.0 for a reverse-proxy or container deployment with AVIF and variant-aware caching.
Who maintains this
We-Amp B.V. helped maintain mod_pagespeed and ngx_pagespeed and prepared the project's last releases through both the Google era and the Apache Incubator era, contributing as external maintainers rather than as Google staff. After the Incubator was archived in 2025, We-Amp resumed active development: the dependency CVEs are patched, the build runs on a current toolchain, and the nginx packages are kept working through distro changes like the CVE-2026-49975 break above.
The maintained product, its docs, and the package repository live at modpagespeed.com; this site points there.
Replace ngx_pagespeed
Add the repository and install the signed module against your distro's stock nginx. It
optimizes immediately, licensed or not — unlicensed installs set an
X-PageSpeed-Warn header rather than refusing to optimize.
Production use requires a commercial license, but the software never locks you out.
Common questions
- Is ngx_pagespeed still maintained, and will it build on modern nginx?
- The original ngx_pagespeed repository is archived and read-only and takes no security patches, and its source no longer builds against current nginx and glibc. mod_pagespeed 1.15 is the maintained continuation: the same directives, the same filter names, the same nginx configuration, with dependency CVEs patched and the module shipped as signed per-distro packages. ModPageSpeed 2.0 is a separate from-scratch rebuild that runs as an nginx reverse proxy in front of your origin.
- Why won't ngx_pagespeed build?
- The archived ngx_pagespeed builds with a 2020-vintage toolchain: a gyp build harness driven by Python 2, pinned to old gcc and an old glibc, with dependencies vendored the way Chromium did in 2012. Current nginx, current glibc, and current compilers no longer match those assumptions, so the build fails without heavy patching. mod_pagespeed 1.15 was migrated off gyp to Bazel and a current C++ standard, which is what makes it buildable again. You do not have to build it yourself — we ship signed binaries.
- Is there a prebuilt ngx_pagespeed package?
- Yes. We publish nginx-module-pagespeed as signed apt and yum packages at packages.modpagespeed.com, for Debian 11/12/13, Ubuntu 22.04 and 24.04 (amd64 and arm64), and AlmaLinux/RHEL 9 via yum (x86_64). Install it with apt or dnf against your distro's stock nginx; there is nothing to compile.
- Why is the nginx module pinned to one exact nginx version?
- An nginx dynamic module compiled against one nginx version does not reliably load into a different one. nginx's --with-compat flag relaxes some module-compatibility checks but not the binary layout of internal request structures, so a module built against a different nginx can read the wrong memory. Each package therefore declares a tight version dependency on the distro's stock nginx (for example nginx 1.24.0 on Ubuntu 24.04). Upgrading nginx past the stock version needs a matching rebuild; contact us when your distro moves.
- Why did a routine apt upgrade break my stock-built PageSpeed module?
- A 2026-06-05 Ubuntu security update for CVE-2026-49975 inserted a new field into the middle of nginx's internal request-headers struct, which shifted every field after it by 8 bytes. A module compiled against the previous nginx then read the wrong offsets and spun the worker on the first request it tried to optimize. nginx -t still passed, and --with-compat did not pad the struct. The decisive test was that the exact same module file worked on the prior package revision and hung on the new one. Our packages avoid this because each one is built against that distro's own patched nginx source, and a runtime guard degrades to pass-through instead of hanging if it ever sees a layout it does not recognize.
nginx is a registered trademark of F5, Inc. mod_pagespeed and other product names are trademarks of their respective owners. References are nominative and for evaluation; We-Amp B.V. is not affiliated with or endorsed by F5, Google, or the Apache Software Foundation.