Release Notes
mod_pagespeed 1.15 release notes — the maintained successor to the final open-source release (1.14.36.1, the last from the Apache incubator). Full version history, security and reliability updates.
On this page
mod_pagespeed 1.15 is the maintained, drop-in continuation of the Apache-lineage mod_pagespeed. The current release is 1.15.0 (package v1.15.0+r21). It is the direct successor to the final open-source release, 1.14.36.1 (August 2020) — the last release made under the Apache incubator — and is actively maintained and security-patched.
How releases work
Updates ship as package revisions of the current release; the latest is v1.15.0+r21. Each revision rolls up security, reliability, and feature work with no configuration change required, so update to the latest revision when it is available. Packages and install commands are on the Downloads page.
Two notes on version numbers:
- The maintained line first shipped as 1.1.0 (GA on 2026-05-15, revisions r1–r24). On 2026-06-01 it was renumbered to 1.15.0 so that version comparisons against the open-source 1.14.x line sort correctly in apt, dnf, and control panels. Same product, same packages — only the number changed.
- Security entries below describe the impact class and recommend updating; we keep exploit specifics out of public notes by policy. CVE identifiers are given where they concern bundled third-party components.
- Many of the bug and security fixes below repair defects inherited from the open-source line — code that shipped in 1.14.36.1 or the historical ngx_pagespeed and was never fixed upstream. Entries note this where it is the case, so it is clear what was repaired relative to the last open-source release.
1.15.0 releases
Most recent first.
v1.15.0+r21 — 2026-08-01
Highlights
-
Apache: optimization thread counts are now sized from the machine. The two worker pools that do optimization work —
NumRewriteThreadsfor HTML, CSS and JavaScript,NumExpensiveRewriteThreadsfor image transcoding — were meant to be sized from the server’s threading model, but the MPM was asked about it before httpd had processed its configuration. On a distribution package, where the MPM is a loadable module, it answered with zeroes, the MPM read as non-threaded, and the server ran one thread in each pool whatever its hardware or configuration. On an httpd built from source with the MPM linked in, the same question was answered differently on the second configuration pass, and the server ran four threads in each pool per child process — never a chosen number, and never visible, because the line reporting it was emitted below the level the log was open at.Both directives now default to
auto. On Apache each pool is then sized at half the CPUs the process is actually permitted to use, divided by the child-process ceiling httpd reports —MaxRequestWorkers / ThreadsPerChildon worker and event,MaxRequestWorkerson prefork, capped byServerLimitin each case — and never drops below one thread. The number comes from httpd itself, so a configuration httpd resolves differently to the arithmetic above is the one that counts.What changes for you: on Apache, the counts now depend on your cores and your configured child-process ceiling, not on your hardware alone. Because that ceiling is high by default — stock
eventallows 16 child processes — most Apache servers resolve to one thread per pool.If you run a distribution package, that is what you were already running and nothing changes. If you built httpd from source, this is a reduction: four threads per pool per child becomes one per pool for a default configuration, because four per child across sixteen children was more optimization threads than such a machine has cores. If you want the old concurrency back, set
NumRewriteThreadsandNumExpensiveRewriteThreadsexplicitly — but size them against your whole server rather than one child. A server configured with few children on a many-core machine gets more than before, which is the case a fixed default could never serve. Expect somewhat higher CPU use while a cold cache warms where the counts went up.NumRewriteThreadsandNumExpensiveRewriteThreadsremain the opt-out and still override the computed value entirely;auto(or0) asks for the automatic sizing explicitly.On nginx, Envoy and IIS the thread counts do not change. Those ports do not yet report how many worker processes share the machine, and rather than guess a divisor and risk oversubscribing the host, the policy takes its minimum: one thread per pool. On nginx and Envoy that is exactly what they ran before. IIS is unchanged for a different reason — the IIS module sizes its own optimization pools and this release does not touch that code, so IIS keeps the counts it has always used. The directive validation and the startup log line below do apply to every port.
Before upgrading, check for a negative
NumRewriteThreadsorNumExpensiveRewriteThreads. A negative value used to be accepted and then crash the server process at startup; it is now rejected when the configuration is read, with a message naming the directive. On Apache an invalid directive value is a fatal configuration error, so a negative value left in place will stop httpd from starting after the upgrade. Change it toautofirst. An implausibly large positive value is clamped rather than rejected, with a warning naming both the requested and the resolved count.Two related fixes come with it. The CPU budget now comes from what the process may actually use — the CPU affinity mask, and the CPU quota on the process’s own cgroup and its ancestors, which covers a container, a Kubernetes pod and a systemd unit with
CPUQuota=alike — instead of the host’s core count. And the resolved counts, along with the CPU budget and child count they were derived from, are written to the error log at startup; previously the line was emitted below Apache’s defaultLogLeveland never reached the log at all. (The same line is emitted on the other ports, but nginx’s compiled-in defaulterror_loglevel iserror, so on nginx it still takeserror_log ... warnto see it.) -
The source tarball no longer contains the
html/documentation archive. Those 82 files are the mod_pagespeed 1.0 documentation, published as the/1.0/archive on modpagespeed.com; shipping them inside a 1.15 source tree placed documentation for a different release next to code it does not describe. Nothing else changes: the archive is still published at/1.0/, and current documentation is at modpagespeed.com — 2.0 under/docs/, 1.15 under/1.1/docs/. -
The AVIF still-image encode budget is now configurable on Apache as
ModPagespeedAvifTimeoutMs(server configuration; also accepted inside a<VirtualHost>). In r20 this setting was only reachable on nginx; on Apache it stayed at its 5000 ms default with no way to change it. The tunable itself is unchanged: as in r20, a larger budget admits more images to AVIF and never selects a lower-quality encoder speed than configured. It only becomes settable on Apache now. -
Data-only
<script>blocks (JSON-LD, plain JSON data, import maps, speculation rules, and templates) no longer emit a spurious “Unrecognized script” info message. These blocks are deliberate, non-executable markup, so the diagnostic was noise; genuinely unrecognized script types still log. -
IPRO recorder statistics are now accurate.
ipro_recorder_failedcounts genuine recording failures only (a write/inflate error or a truncated response). Previously it also absorbed expected outcomes — non-rewritable content types, error (4xx/5xx) and not-modified (304/206) responses, empty responses, and load- or size-limited recordings — which each now have their own counter (ipro_recorder_dropped_content_type,ipro_recorder_error_status,ipro_recorder_skipped_transient,ipro_recorder_empty, alongside the existingipro_recorder_not_cacheable,ipro_recorder_dropped_due_to_load, andipro_recorder_dropped_due_to_size). Each recorder outcome is also logged for diagnosis. -
A stray
;after a rule inside an@mediablock — a common hand-authoring artifact, as in@media screen { .a { color: red }; }— no longer fails the stylesheet. Such sheets previously passed through whole: unminified, excluded from CSS combining, and skipped byprioritize_critical_css. They are now handled like any other stylesheet. Sheets that still fail to parse are served byte-for-byte unchanged, as before. -
hint_preload_subresourcesagain hints<script type="module">subresources, now usingrel=modulepreloadin theLinkresponse header it emits (this filter adds no markup to the page). Module scripts stopped being hinted in r20: the olderrel=preload; as=scripthint does not match how a browser fetches a module, so it could cost an extra fetch rather than save one.rel=modulepreloadmatches the module fetch, so the hint is usable. Modules carryingintegrityorcrossorigin="use-credentials"are left unhinted, because a hint for those cannot be matched reliably. Browsers that do not act on the hint are unaffected. Servers running mixed versions against a shared cache degrade cleanly: older versions skip the new cache entries rather than misread them. -
WebP support is now determined from the browser’s
Acceptrequest header alone. Which flavours of WebP a browser could handle — lossy, lossless, transparent, animated — used to be decided from hand-maintained lists of browser version strings. Those lists had to be updated as browsers shipped and had gone stale: browsers whose major version number reached three digits (current Chrome, Edge and Opera, and Chrome on iOS) were read as incapable of animated WebP, and Chrome on iOS also lost lossless and alpha WebP. A browser that advertises WebP is now taken to support all of it, matching how AVIF has always been handled, so that class of staleness cannot return. Browsers that do not advertise WebP are unaffected. Beyond the browsers named above, the main beneficiary is Safari, which advertises WebP on image requests but was never on the old lists: within_place_optimize_for_browser, Safari can now receive transparent and lossless WebP where those filters are enabled and it previously received PNG. What a given visitor gets still depends on the request headers, and some CDN and proxy configurations hold responses to lossy WebP. Upgrade note: the first start after upgrading to r21 re-optimizes some images once, because which WebP features a browser supports forms part of the image optimization cache key, and this release changes that determination. On a default configuration the browsers named above are the ones affected — a minority of visitors, or most current-browser traffic if you enableconvert_to_webp_animated. Sites runningin_place_optimize_for_browseradditionally see it for the share of their traffic that the old lists never covered. Pages keep being served normally and re-optimization proceeds in the background, as with any cold cache; while the pass completes, affected images are served in their original form, so expect a brief rise in page weight. No purge or manual invalidation of any downstream proxy or CDN is required: where an image’s optimized output changes, it is published under a new rewritten URL and the HTML is updated to point at it, while previously rewritten URLs keep resolving and age out normally. -
The legacy JavaScript minifier has been removed. The tokenizer-based minifier — the default since 1.10.33.0, and the only one that understands modern JavaScript — is now the only JavaScript minifier.
UseExperimentalJsMinifieris deprecated and ignored: configurations that still set it start normally and log a warning naming the directive, which can simply be deleted. Default configurations are unaffected — they were already using this minifier. Upgrade note: sites that setUseExperimentalJsMinifierexplicitly re-optimize their JavaScript once after upgrading; withonthe output and the rewritten URLs are identical and only that one background pass is new, while withoffthe minified output itself changes. Pages keep being served normally and re-optimization proceeds in the background, as with any cold cache; while the pass completes, affected scripts are served in their original form, so expect a brief rise in page weight. No purge or manual invalidation of any downstream proxy or CDN is required: where a script’s optimized output changes, it is published under a new rewritten URL and the HTML is updated to point at it, while previously rewritten URLs keep resolving and age out normally. Two things also begin working on sites that were runningoff:<script type="module">is now minified, andinclude_js_source_mapsnow produces source maps. If you generated your ownModPagespeedLibrarysignatures forcanonicalize_javascript_librariesagainst the legacy JavaScript minifier, regenerate them; until then those libraries are minified normally instead of canonicalized. -
Automated clients are recognised far more reliably, so the measurement data that drives optimization is collected from real browsers only. The list of known non-rendering clients had not been updated since 2013 and missed the entire current generation: AI assistant fetchers that retrieve a page on a person’s behalf, agent infrastructure, and the HTTP client libraries and command-line tools written since. Traditional crawlers were already recognised. These clients no longer run the instrumentation, critical-image and critical-CSS beacons, so the data those beacons collect — which
prioritize_critical_css,inline_preview_imagesand image prioritization optimize from — reflects what actual visitors render rather than what a non-rendering client reported. Matching is exact and case-sensitive against the client identifier, so ordinary browsers are unaffected. Applies to all supported servers. Note thatcurlandwgetare now classified as automated clients: a page fetched with either for a spot check will not contain the beacon scripts, and lazy-loaded images will be served eagerly. -
nginx: a Web Bot Auth signature can now inform that decision, behind the new
WebBotAuthBotDetectiondirective (server configuration, default off). With it on, a request carrying a cryptographically valid Web Bot Auth signature (RFC 9421) is treated as an automated client whatever identifier it presents — so an agent that identifies honestly is classified correctly even when it sends a browser’s user-agent string, which no identifier list can detect. Only a signature that verifies counts; an absent or failed signature changes nothing. RequiresWebBotAuth, the existing directive that turns signature verification on. Off by default, so Web Bot Auth stays observe-only for every existing deployment: with the new directive off, a verification result still only labels the request — it populates the$x_verified_botnginx variable, which you can log or pass to your own configuration, and the opt-in verified-request statistics — exactly as in r20. -
defer_javascriptno longer sends deferred markup to automated clients. Whendefer_javascript(ordisable_javascript) is enabled,<script>elements are rewritten into a form only PageSpeed’s client-side runtime can execute. A client that does not run that runtime received a page whose scripts never ran and whose external JavaScript was never even requested — script-dead markup it had no way to act on. Automated clients are now served the page’s normal, unmodified script markup instead. This covers the filters that share the same gate:defer_javascript,disable_javascript,defer_iframe,fix_reflow, and thesupport_noscriptfallback they share, so such a client gets clean markup rather than clean markup plus a stray<noscript>redirect banner. Browsers are unaffected, anddefer_javascriptremains off by default.This deliberately includes search-engine crawlers, which previously received the deferred form. They now receive the page exactly as it is authored — normal markup, not a degraded version of it, and without the serialized script execution the deferral runtime imposes.
lazyload_imageshas behaved this way for automated clients for years. No configuration change is required. As with the client recognition above, an automated client that presents a browser’s exact user-agent string is still served the deferred form unless a verified Web Bot Auth signature identifies it. -
JavaScript minification: generators that yield object literals are minified again. A file containing
yield {…}— or a same-lineawait {…}orfor (x of {…})— was served in its original, unminified form: the minifier could not rule out that the braces opened a block rather than the operand, and declined the whole file. On a single line the braces can only be the operand, so such files are now fully minified. The genuinely ambiguous form — a line break between the keyword and the brace, where the two readings differ — is still declined and served unmodified, as before. -
JavaScript minification: a class with a bare field directly before a generator method is no longer broken by minification. The line break after a bare field —
xon its own line, followed by*gen() {…}— is what ends the field declaration; the minifier removed it, fusing the field and the generator method into one invalid declaration, so the minified script failed to parse where the original ran. The line break is now preserved. Static (static x), computed-name ([expr]), and private (#x) bare fields were affected the same way and are covered by the same fix. -
JavaScript minification: an object literal whose generator method is followed by further members is minified again. A file containing
{ *gen() {…}, b: 2 }— a generator method (named anything, includingawaitoryield) with a comma and another member after it — was served in its original, unminified form: the minifier mis-modeled the separator after the completed method body and declined the whole file. Such files are now fully minified. -
JavaScript minification: a line break before an arrow’s
=>is now preserved. JavaScript forbids a line break between an arrow head and its=>, soa = xfollowed by=> yon the next line is already a syntax error. The minifier dropped that line break and emitteda=x=>y— turning broken input into valid but different code, masking the authoring error. The line break is now kept, so invalid input is served as it was written. -
JavaScript minification: a division operator no longer merges into a retained IE conditional-compilation comment. When minification removed the space between a division
/and a retained/*@ ... @*/comment, the/and the comment’s opening/*fused into//— a line comment that swallowed the rest of the line and silently changed what the script computes, with both forms valid so nothing failed loudly. A separating space is now kept whenever the two would otherwise join, and the equivalent hazard after such a comment is guarded the same way. -
JavaScript minification: the space between a bare
0and a following property access is now kept.0 .toString()minified to0.toString(), where the period is absorbed as the literal’s decimal point, turning valid code into a script that fails to parse. The space is now preserved after a bare0; other numeric literals are unaffected. -
JavaScript minification: the line break after a postfix
++/--is no longer removed when it is load-bearing. A line break separating a completed postfix++/--expression from a next statement that begins with an opening parenthesis, or with a leading-dot number such as.5, is what keeps the two statements apart: without it the code re-parses as a call or member access on the value just incremented, which the browser rejects as a syntax error — but the minifier removed it and reported success, so the script was served broken with nothing logged. Such line breaks are now preserved (including when carried inside a comment). Line breaks that a following binary operator genuinely continues are still removed, and already-correct minified output is byte-for-byte unchanged. -
HTML parsing: the text of merged character runs is no longer held until end of parse. When the parser coalesces adjacent character tokens into one — a routine step before the rewrite filters run — the token it merged away kept its copy of the text alive for the rest of the parse, so a text-heavy page held more peak memory than it needed to. The merged-away token now releases its text the moment it is retired. What is served is unchanged.
-
The HTML parser is hardened against malformed markup, cross-porting the robustness fixes ModPageSpeed 2.0 accumulated for the same code. Certain malformed HTML could crash the worker process or trip undefined behaviour while a page was being parsed for rewriting. Such markup is now handled safely and the page is served. Update recommended.
Two pieces of modern markup are now recognised where they were previously unknown. A page whose doctype is
<!DOCTYPE html SYSTEM "about:legacy-compat">— the long form the HTML standard reserves for generators that cannot emit the short<!DOCTYPE html>, such as XSLT output — was classified as having an unknown doctype; it is now treated as HTML5 (XHTML5 for XML content types), like any other HTML5 page. And thecrossorigin,integrityandtemplateattributes and elements are now known keywords rather than unrecognised names, which is groundwork only: parsing and rewriting of pages that use them is unchanged in this release. -
CSS: selectors with functional pseudo-classes are no longer mangled. CSS minification could not represent the parenthesized arguments of
:where(),:is(),:not(),:has(),:nth-child()and friends — common in Tailwind v4 and modern CSS resets — so it reported a selector error and silently dropped the argument text:.prose :where(h2)minified to.prose :where, a selector no browser matches. The parser now captures the balanced argument text verbatim and re-emits it on serialization, so these selectors round-trip intact. As a side effect, rulesets that were previously passed through byte-for-byte as opaque regions are now fully parsed, so their declarations are minified and their URLs rewritten like any other ruleset. -
CSS: declarations with a spaced
+addition operator are no longer dropped. CSS minification silently discarded any declaration whose value contained a spaced+—calc(1px + 2px)or a custom property such as--x: 1px + 2px— because the CSS parser treated a+not directly attached to a number as a number-parsing error (e.g.h1 { width: calc(1px + 2px); }becameh1 {}). The parser now lexes such a+as an operator value, so these declarations parse and round-trip; a+directly attached to a number still parses as a signed number. The CSS parser’s regression coverage for modern constructs was expanded alongside this fix (ported from the 2.0 test suite):calc()withvar()operands, calc-operand custom properties, and unicode-range lexing shapes.
v1.15.0+r20 — 2026-07-24
Overview
Security and correctness release for the 1.15 line, hardening output escaping, input validation, and rewrite correctness across the HTML rewriter filters. Update recommended.
The largest addition is AVIF image support, bringing the image path to parity with the WebP support it has shipped for years: opt-in AVIF encoding on Apache, nginx, and IIS, served only to browsers that advertise the format.
The release also ships a filter modernization batch: new opt-in filters for critical images and speculation rules, revived Google Fonts CSS inlining, Core Web Vitals reporting from the instrumentation beacon, and support for modern JavaScript and CSS constructs that previously passed through unoptimized.
Provenance. Most of the issues addressed here are long-standing defects that originate in the upstream mod_pagespeed codebase (originally developed by Google as open source) on which the 1.15 line is built; each was verified against the published upstream source. A few are gaps in functionality added more recently (Content-Security-Policy handling and stylesheet charset fidelity). All are now fixed.
Security
- The nginx bundled with the NuGet sidecar package and its container image is updated to 1.30.4, picking up the July 2026 upstream nginx security fixes — including CVE-2026-42533, a request-processing memory-safety defect that the upstream nginx advisory reports as exploited in the wild. Update recommended for sidecar deployments. If you build the nginx module against your own nginx, build against 1.30.4 or later.
- Fixed a cross-site scripting issue where crafted CSS could break out of an
inlined
<style>element when CSS optimization was enabled. - Fixed a cross-site scripting issue in local-storage cache inlining where a crafted image attribute could inject script on repeat page views.
- Fixed a cross-site scripting issue where a crafted image
idcould inject script during inline-image deduplication. - Fixed a cross-site scripting issue where a crafted image URL could be reflected unescaped into inline image-preview JavaScript.
- Hardened image optimization against crafted image dimensions that could bypass the resolution limit and trigger excessive memory use (denial-of-service).
- Fixed a CSS dependency-parsing defect that could misread stylesheet contents
(out-of-bounds read / dropped
@importrules). - Fixed a crash on the image-spriting path that could be triggered by a malformed image file declaring invalid dimensions (denial-of-service). Such inputs are now rejected and the page is served with the original images; sprite sets mixing an unusable image with valid ones now sprite the valid subset. A related defensive guard covers the inline image-preview path, which is not reachable from end-user input.
- Hardened the JavaScript minifier against crafted scripts that could drive unbounded memory growth during parsing, exhausting server memory (denial-of-service). Parsing depth is now bounded; a script that exceeds the bound is passed through byte-for-byte unminified, which is the minifier’s existing behavior for any input it declines to process. Ordinary JavaScript — including large bundles and heavily nested framework output — is unaffected. Affects all 1.15 releases up to and including r19; update recommended for any deployment that optimizes JavaScript it does not control.
- Hardened the HTML parser against crafted documents that could drive memory use to the size of the input regardless of configuration (denial-of-service). A hard ceiling now applies to how much a single HTML token may accumulate, independent of the configurable parse-size limit, whose semantics are unchanged. Documents that trip the ceiling fall back to being passed through rather than rewritten. Affects all 1.15 releases up to and including r19; update recommended for any deployment that rewrites HTML it does not control.
- Fixed a content-integrity defect where an out-of-range numeric HTML character reference decoded to an arbitrary, unrelated character instead of being rejected. Out-of-range references are now reported as a decoding error and the original escaped text is kept verbatim. References within the valid Unicode range are unaffected.
- Defense-in-depth output-escaping consistency across several rewriter filters (these paths are not reachable from end-user input; no action required).
- Defense-in-depth division-by-zero guard in the responsive-image sizing path (this path is not reachable from end-user input; no action required).
- Defense-in-depth bounds guard on a JavaScript string- and regular-expression-scanning path (no out-of-range access was reachable; the affected inputs already ended in the existing graceful error).
- Defense-in-depth null guard on an HTML tag-close path (not reachable on this release; the guard protects the invariant against future drift).
- Fixed a startup race in HTML keyword-table initialization where concurrent first-time initialization could construct the shared table twice and publish it without synchronization. Initialization is now thread-safe.
Features
-
AVIF image support. Images can now be optimized to AVIF, alongside the existing WebP path, on Apache, nginx, and IIS. Four new filters, all opt-in, cover the same ground WebP does:
convert_jpeg_to_avif— convert JPEG sources to AVIF.convert_to_avif_lossless— prefer lossless AVIF where it wins (also the path for images with alpha).convert_to_avif_animated— convert animated images to animated AVIF.recompress_avif— re-encode images that are already AVIF.
Behavior worth knowing before you enable them:
- AVIF is not part of
rewrite_imagesor any rewrite level, by design. AV1 encoding costs substantially more CPU than WebP, so folding it intorewrite_imageswould be a silent cost increase for every existing deployment on upgrade. Enable the filters you want explicitly. - AVIF is served only to browsers that advertise it (
Accept: image/avif). There is no user-agent allowlist: the request header alone decides. Clients that do not advertise AVIF keep getting the WebP or original-format result exactly as before. - The encoder picks the smaller of the AVIF, WebP, and original outputs per image, so enabling AVIF cannot make an image larger; if AVIF encoding fails or times out, the rewrite falls back through WebP to the original format rather than failing the image.
- EXIF, ICC color profiles, and XMP are carried across AVIF re-encoding under the existing metadata-retention options. Images carrying a C2PA content-provenance manifest are skipped, never stripped — such images are served as authored.
- Because AV1 encoding is slow relative to WebP, every still-image encode is
admitted against a time budget (
AvifTimeoutMs, default 5000 ms) before it starts, and the encoder speed is derived from that budget and the image’s pixel count: a larger budget admits more images and never selects a lower-quality speed than configured. Images that cannot fit the budget even at the fastest speed are left to the WebP/original path. An absolute 100-megapixel ceiling applies regardless of budget. - Known limitation, animated AVIF: that budget-derived speed selection
applies to still images only. An animated sequence always encodes at
the configured encoder speed, so animated AVIF encodes cost considerably
more per image than stills and do not get faster when
AvifTimeoutMsis lowered — a long animated encode is bounded by the abort applied between frames rather than by the budget. Account for this before enablingconvert_to_avif_animatedover a large animated-image inventory. A future release is expected to extend budget-derived speed selection to animated sequences. - The module now links the AV1 encoder and decoder, so the installed module is appreciably larger than in r19. Plan package and disk footprint accordingly.
- A full family of
image_avif_*statistics (rewrites, per-source-format timeouts, budget overruns, and success/failure timings) is registered automatically, so encode failures and timeouts are visible on the statistics page.
-
New opt-in filter
prioritize_critical_images: setsfetchpriority="high"on the first two images the critical-images beacon has reported above the fold, so the browser front-loads the fetches that determine Largest Contentful Paint. The filter is a strict no-op without beacon data (a wrong guess would prioritize a below-the-fold image at the LCP image’s expense), an author-suppliedfetchpriorityalways wins, and it backs off onSave-Datarequests, AMP documents, and disallowed URLs. It rewrites attributes only and injects no scripts; enabling it also turns on critical-images beaconing. It is not part of any rewrite level’s filter set — enable it explicitly. -
New opt-in filter
insert_speculation_rules: injects a same-origin prefetch ruleset (<script type="speculationrules">) so supporting browsers prefetch a link as the user starts interacting with it; other browsers ignore the tag. The filter backs off when the page already carries its own ruleset, when a Content-Security-Policy forbids inline scripts, on non-200, cookie-setting, orno-storeresponses, and on AMP documents. It is not part of any rewrite level’s filter set — enable it explicitly. -
Google Fonts CSS inlining is revived: the default size cap (
GoogleFontCssInlineMaxBytes) rises from 3 KiB to 48 KiB. Real Font Service responses run ~6–15 KiB, so the old cap rejected essentially every one and the filter never fired. A scheme-qualified<link rel="preconnect" href="…://fonts.gstatic.com" crossorigin>hint is now emitted ahead of the first recognized font stylesheet, whether the loader CSS ends up inlined or not, unless the author already warms that host with a usablecrossoriginpreconnect. Upgrade note: “not inlined” verdicts cached under the old cap keep applying until they expire (up to a day), so inlining ramps up as the cache re-warms. -
hint_preload_subresourcesnow emits font preload hints (rel=preload; as=font; crossorigin, up to four per page) harvested from@font-facerules in the page’s collected CSS. Fonts are discovered two hops late (HTML, then CSS, then the font file), so a hint saves the longest fetch chain. Harvesting is deliberately conservative: woff2 sources only, only faces gated to media needed to render, and only faces whoseunicode-rangecovers printable ASCII. Fonts referenced only from@imported stylesheets are collected onceflatten_css_importsis enabled. Fleets running mixed versions against a shared cache degrade cleanly: older binaries skip the new cache entries. -
The instrumentation beacon now reports Core Web Vitals — LCP, CLS, and INP — plus navigation timing, collected with
PerformanceObserverand sent in a singlesendBeaconPOST when the page is hidden. This replaces the legacy on-load image GET and thebeforeunloadbeacon; thebeforeunloadhandler disabled the browser’s back/forward cache, so instrumented pages are eligible for it again, and a visit restored from it is measured and beaconed as its own page view. Four new histograms (LCP, CLS, INP, TTFB) appear on the admin console automatically, and beacons sent by pages cached before the upgrade are still accepted.ReportUnloadTimeis deprecated to a no-op. -
The tokenizer-based JavaScript minifier (
UseExperimentalJsMinifier) now handles modern syntax —??,??=,?., optional catch binding, destructuring declarations,super, dynamicimport()/import.meta, and module statement forms — where it previously rejected most ES2015+ input and silently passed modern bundles through unminified. Input it still cannot model keeps its original bytes, as before. -
<script type="module">is now a first-class script kind; previously every JavaScript filter skipped module scripts.rewrite_javascriptminifies them (tokenizer-based minifier only), preserving import specifiers and the resource directory so relative imports keep resolving. Combining treats a module as a barrier — scripts on either side still combine among themselves — and inlining, outlining, and disabling leave modules alone, since those rewrites would change import resolution or execution timing. Modules are never relocated to another host by rewriting or cache extension (their fetches are CORS-mode) and are never substituted by library canonicalization. -
CSS inside
@supports,@layer, and@containerblocks, and media queries using level-4 range syntax such as(width >= 768px), previously failed to parse — so everything inside them passed through unminified and unoptimized, which for framework bundles that wrap the whole stylesheet in@layermeant the entire file. These constructs now parse: such stylesheets minify, images referenced inside the blocks are rewritten, inlined, and cache-extended like any others, andprioritize_critical_csscollects and inlines critical selectors inside them while preserving@layercascade order. Sheets that still fail to parse are served byte-for-byte unchanged, as before. -
insert_dns_prefetchnow emits<link rel="preconnect">for the first two domains of its stable-domain list (dns-prefetch for the rest): preconnect warms the whole connection (DNS + TCP + TLS) where dns-prefetch only resolves the name. Preconnect hints are scheme-qualified and keep non-default ports, and the filter no longer emits hints an author already provides. The legacy IE9-onlyrel=prefetchvariant is removed. Rollout note for mixed-version fleets sharing a cache: until the affected property-cache entries expire, an older binary reading entries written by this version can emit malformed preconnect hrefs of the form//https://host, which browsers ignore. -
prioritize_critical_imagesand native-modelazyload_imagesnow handle images that usesrcsetwithout asrcattribute: the beacon reports the candidate the browser actually displays, beacon-critical candidates getfetchpriority="high", and the rest are lazy-loaded under the same first-image LCP protection assrcimages, honoring authorloading/fetchpriority/decodingattributes.
Correctness
- Fixed a Google Analytics snippet-detection bug where analytics markup could be misidentified across requests, leading to missing analytics on some pages.
- Under a strict Content-Security-Policy that permits inline styles but not inline scripts, critical-CSS prioritization could render pages with the non-critical styles missing; such pages are now left unchanged. Update recommended for sites served with a Content-Security-Policy.
- Fixed a text-integrity issue where an external stylesheet with no declared character set and non-ASCII content could be inlined with garbled bytes; such stylesheets are now left unchanged.
- Fixed an input-handling defect where images with extreme author-specified dimensions could produce an invalid responsive-image candidate.
- CSS minification could incorrectly strip units from zero terms inside the
math functions
calc(),-webkit-calc,min(),max(), andclamp()— after any nested function such asvar()(e.g.calc(var(--x) - 0px)becamecalc(var(--x) - 0)), and throughoutmin()/max()/clamp()/-webkit-calcthemselves — producing invalid CSS that browsers drop. Math-function context is now recognized for all of these functions and preserved across nesting. combine_javascriptnow requires the Content-Security-Policy to permit inline scripts before combining, not justunsafe-eval. The filter replaces script tags with small inline bootstrap scripts; under policies such asunsafe-evalwithoutunsafe-inline(orstrict-dynamic/nonce-based policies) the combined scripts loaded but never executed. Affected pages now keep their original, working script tags. Update recommended for sites served with a Content-Security-Policy.- Fixed a
Varyheader merge defect in in-place optimization: when a response already carried oneVarytoken (such asAccept), another needed token (such asUser-AgentorSave-Data) was not added, so a downstream cache could serve the wrong variant of a resource. Tokens are now merged individually; existing tokens are never removed or duplicated. - Fixed a class of defects in the tokenizer-based JavaScript minifier
(
UseExperimentalJsMinifier) that fused valid statements into a syntax error: a line opening with(or a regular-expression literal following animport/exportdeclaration, a plainlet/vardeclaration, or a block-bodied arrow function could be joined onto the previous line when the source relied on automatic semicolon insertion. These declaration boundaries are now modeled; input the minifier cannot model is still passed through byte-for-byte. - Both JavaScript minifiers now treat a block comment containing a line break
as a line break, as the language specification requires. Previously such a
comment was collapsed to a plain space, so a statement boundary that relied
on it disappeared: code of the shape
return/*<newline>*/xwas minified toreturn x, silently changing what the script returned, and comparable inputs were fused into outright syntax errors. Conditional-compilation comments are still retained verbatim. - Pages that combine a
<base>element with a Content-Security-Policybase-uridirective are no longer excluded from optimization outright. Where the policy provably neutralizes every<base>element (base-uri 'none'or an empty source list), the browser ignores the tag, so it cannot affect relative-URL resolution and rewriting now proceeds. Anybase-urivalue that could still match a<base>—'self', host or scheme lists,*— backs off exactly as before, as do pages with no such directive. Strict policies of this shape previously paid a rewriting penalty for being strict. - IIS: fixed a use-after-free in the server’s internal fetcher when a request completed synchronously, which could crash the worker process. Deletion of the completed fetch is now deferred until the originating call has fully returned.
- Admin console: license management controls are no longer hidden when the global admin console is served at a renamed path. The console now trusts the authoritative flag the server already returns instead of inferring the answer from the URL. Server-side enforcement was never affected — control visibility was the only thing wrong.
- Scripts carrying Subresource Integrity (
integrity=) are now left untouched by JavaScript rewriting and minification (rewrite_javascript), by combining (an integrity-bearing script acts as a barrier; scripts on either side still combine among themselves), by outlining of inline scripts, and — stylesheets included — by cache extension when it would relocate the resource to another host (domain sharding or mapping). Previously such a rewrite changed or moved the bytes so the hash no longer matched, and the browser blocked a resource that was valid as authored. inline_javascriptno longer inlines external scripts carryingasyncordefer: those attributes are ignored on inline scripts, so inlining silently turned a deferred script into a parser-blocking one that ran mid-parse, out of order. Scripts carrying only one of thefor/eventattribute pair — which per HTML5 never execute — are likewise left alone by inlining and combining, where previously the rewrite made them run.- HTML responses whose bytes depend on the
Save-Datarequest header now carryVary: Save-Data, so a downstream cache cannot serve the data-saver variant to a full-data client or vice versa. ExistingVarytokens are preserved. - CSS minification now recognizes the math functions
calc(),min(),max(), andclamp()case-insensitively, as the specification requires; uppercase or mixed-case forms (e.g.CALC(100% - 0px)) previously had units incorrectly stripped from zero terms, producing invalid CSS that browsers drop. - Stylesheets containing an
@importrule that uses syntax the CSS parser does not understand — such as cascade layers (@import url(x) layer(base);) or other unrecognized import syntax — are no longer import-flattened, a transformation that could drop or reorder rules. Other optimizations (minification, image rewriting) still apply to such stylesheets. - A stylesheet whose only encoding declaration is a leading
@charsetrule could lose that declaration when import-flattening was enabled but no imports were inlined (including results served from the flatten cache), leaving the browser to guess the encoding of non-ASCII content. The declaration is now preserved in that case; stylesheets that do have imports inlined keep the standards-required behavior of dropping it. Note that an already-minified stylesheet with no imports that carries@charsetnow serializes byte-identically, so under the default configuration its rewrite is dropped as a no-op — including any in-CSS image rewrites it previously kept alive. - CSS scanning now recognizes
URL()and@IMPORTcase-insensitively, as the specification requires. Uppercase references were previously invisible to URL rebasing when stylesheets were combined, inlined, or outlined, and to URL rewriting inside style attributes, which could leave stale or unexpectedly relative URLs in place. - Fixed a
local_storage_cachedefect where unavailable or failing browser storage (for example in some private-browsing modes) made an inlined resource disappear from the page entirely — and could break every inlined resource on the page — instead of falling back to the network. Inlining now degrades gracefully. The cookie that records which resources a browser already holds in local storage is now scoped to the whole site (path=/) instead of the current page’s directory, so the server recognizes them site-wide. - Removed an obsolete Firefox workaround from
defer_javascriptthat ran deferred inline scripts through adata:URL (for a Firefox bug fixed in 2013). Content-Security-Policy rules that permit inline scripts do not permitdata:script URLs, so under such policies every deferred inline script was blocked on Firefox and the page broke. cache_partial_htmlno longer triggers the no-script redirect machinery for clients without JavaScript: likedefer_iframe, the filter name is still accepted for configuration compatibility but has no rewriting effect, so it must not mark pages as requiring script execution.elide_attributesnow also elides values matching current-HTML defaults in HTML5 documents —loading=eager,decoding=auto, andfetchpriority=autoon images,media=allon stylesheet links, andfetchpriority=autoon links and scripts — and strips values from the modern boolean attributesopen(dialog),disabled(fieldset),allowfullscreen(iframe), andplaysinline(video). Entries for long-dead markup (command,keygen,seamless, and similar) were removed and no longer alter such elements.remove_quotesnow also strips quotes from attribute values containing/, so most URL-valued attributes (such ashref="/foo/bar") are emitted unquoted. Values ending in/are kept unambiguous by the output writer’s existing guard.- The canonical link inserted by the no-script redirect handling is now built
as a real element. Output is unchanged under the default configuration;
attribute-level filters such as
remove_quotesnow apply to it consistently, as they do to all other elements. - Fixed a diagnostic message in low-resolution image resizing that printed the target width twice instead of width×height.
Statistics
show_ads_snippets_not_convertednow reflects recognized-but-unconverted AdSense snippets (it previously always reported 0).num_js_inlinedno longer over-counts scripts that are intentionally left external in XHTML documents.- Fixed a rare corruption of the image byte-savings counter that could occur under a non-default configuration where an optimized image ended up larger than its input.
- The reported page-load time (the Beacon Reported Load Time histogram and the
total_page_load_msaverage) is now measured from navigation start, so it additionally includes DNS, connection setup, and time to first byte. The histogram steps up once at rollout; this is a measurement change, not a site regression. - Telemetry for scripts skipped by administrator configuration no longer
records them as author opt-outs (
has_pagespeed_no_defer), matching the module-script and CSP-backoff paths. flatten_imports_unparseable_importcounts stylesheets for which import-flattening was declined because an@importrule uses syntax the CSS parser does not understand (such as cascade layers or other unrecognized import syntax).- The
image_ongoing_rewritesgauge no longer leaks a count on a failure path that could not be triggered in practice (defensive fix). - Histogram percentiles (median, 90th, 95th, 99th) are no longer reported as a
-5000no-data placeholder when a histogram has too few samples to estimate them. The cells are now rendered empty instead — for the raw/histogramsoutput as well as the admin console, which previously hid the placeholder for itself only. Anything scraping/histogramsshould expect an empty value rather than-5000. - Latency and duration statistics (HTTP cache lookup and insert, cache hit and
insert, fetch, and backend first-byte latency) are now measured against a
monotonic clock. A backward step of the system wall clock — from a time
sync, a hypervisor, or a misbehaving time daemon — previously produced
negative recorded durations. Absolute timestamps used for HTTP
Dateheaders and for cache freshness are deliberately still taken from the wall clock and are unaffected. - New
image_avif_*statistics cover AVIF rewrites, per-source-format encode timeouts, budget overruns, and success/failure timings.
Configuration
-
New AVIF options, mirroring their WebP counterparts:
AvifRecompressionQuality(default 60),AvifRecompressionQualityForSmallScreens(50),AvifAnimatedRecompressionQuality(50),AvifQualityForSaveData(45), andAvifTimeoutMs(5000). They take effect only when one of the AVIF filters is enabled; see the AVIF entry under Features. -
IIS:
pagespeed.confignow has a single, documented resolution order. The module previously probed the configuration locations independently from several code paths, so when the installed copies diverged, editing one of them could appear to have no effect at all. All paths now use one resolver, with this precedence (lowest first; the last file that defines a setting wins):%ProgramData%\We-Amp\PageSpeed\pagespeed.config— machine-global base.%ProgramData%\We-Amp\IISWebSpeed\pagespeed.config— legacy fallback, for upgrades from IISpeed.<site physical path>\pagespeed.config— per-site override, authoritative when present.
Within each location
pagespeed.configis still preferred over the legacyiiswebspeed.config. For a standard single-site installation, behavior is unchanged — this matches what request handling already did. On startup the module now logs which configuration is in effect and warns when several configuration files exist with differing contents, which is the diagnostic for the “my edit did nothing” case. -
New option
AsyncMetadataL2Writes(default off). When enabled, writes to the disk-backed second tier of the metadata cache are deferred to a background worker instead of running on the rewrite-completion path, so slow disk write latency no longer amplifies into serving-throughput loss and tail-latency spikes during a cache-populating miss storm. Reads stay synchronous. With the option off, behavior is identical to previous releases. Enabling it is safe by construction — served content is content-hash-addressed, so a deferred write can at worst cost a re-optimization, never wrong or stale bytes — but it is default-off this release while it accrues production soak. -
The Universal Analytics filters are deprecated — the service stopped processing hits in July 2023, so the trackers these filters inject or rewrite reported to a discontinued service. Enabling
insert_gaor settingAnalyticsIDlogs a deprecation warning at configuration load (disabling stays silent), andmake_google_analytics_asyncis now a no-op whose name still parses, so existing configurations keep loading. Experiments no longer auto-enableinsert_ga: the A/B framework keeps variant assignment (thePageSpeedExperimentcookie) and the experiment id in the instrumentation beacon, reporting is bring-your-own-analytics, and an experiment spec can still opt in explicitly withenable=insert_ga. -
defer_iframeis deprecated: the filter name was accepted but had no effect on its own (iframe deferral is built intodefer_javascriptanddisable_javascript). The name still parses for compatibility, logs a deprecation warning at configuration load, and no longer triggers the no-script redirect machinery. Configurations usingdefer_iframecan simply remove it. -
The legacy JavaScript minifier is deprecated: it remains available this release via
UseExperimentalJsMinifier off, but explicitly selecting it logs a deprecation warning at configuration load, and it will be removed in a future release. The tokenizer-based minifier (the default since 1.10.33.0) now minifies modern JavaScript it previously passed through unoptimized, backed by a stress corpus of real-world, bundled, and synthetic JavaScript: full coverage, with zero parse, semantic, or idempotence failures. -
The experimental gRPC “central controller” was removed, along with its dedicated controller process and the gRPC build dependency. The
ExperimentalCentralControllerPort,ExperimentalPopularityContestMaxInFlight, andExperimentalPopularityContestMaxQueueSizeoptions are deprecated: they still parse for compatibility but are ignored, and log a deprecation warning when set. There is no behavior change for configurations that did not set these options — the default work-bound expensive-operation throttling and named-lock rewrite scheduling are unchanged. The controller’s experimental-only statistics counters (num_rewrites_requested,num_rewrites_succeeded,num_rewrites_failed, the popularity-contest and queued-controller gauges, andcontroller_reconnect_time) are no longer registered; they read as zero under default configurations, so dashboards scraping them will see them go missing rather than zero. -
Domain sharding (
ShardDomain) is deprecated: it is an HTTP/1-era workaround that hurts performance with HTTP/2 and HTTP/3, which multiplex over a single connection. The directive still works for compatibility but logs a deprecation warning, deduplicated to at most once per process per shard declaration. Configurations usingShardDomaincan simply remove the directive. -
The long-inert filters
div_structure,explicit_close_tags,mobilize_precompute,split_html,split_html_helper, andflush_subresourcesare deprecated: the names still parse for compatibility and log a deprecation warning at configuration load but have no effect. (flush_subresourcesalso no longer adds head-section domains to theinsert_dns_prefetchhint list.) Configurations using them can simply remove them. -
ForbidFiltersnow also gates previously-rewritten URLs carrying the shared cache-extension (.pagespeed.ce.) and image (.pagespeed.ic.) markers: such URLs are no longer served once every filter that can produce them is forbidden. Previously, forbidding those filters stopped new rewrites but could not stop already-rewritten URLs from being served.
v1.15.0+r19 — 2026-07-17
Cache upgrade-safety release, plus zero-copy serving corrections. Update recommended.
Caching: version-safe cache files
-
The cache now lives in a file fingerprinted by the bundled cache library’s on-disk format version — not the mod_pagespeed release version. Format changes are anticipated to be infrequent, so most future upgrades will keep the cache warm. When the format does change (as it does in this release), old and new worker processes never open the same file, which removes a class of cache-corruption risk during upgrades, when both could briefly overlap on one cache file.
-
Upgrade note: the first start after upgrading to r19 begins with a cold cache. Pages keep being served normally and re-optimization proceeds in the background, as with any cold cache.
-
The previous version’s cache file is left on disk untouched, so rolling back to the previous package is warm — it finds its cache exactly as it left it. Once you are confident you will not roll back, you can delete the older files in the cache directory to reclaim disk. Nothing is deleted automatically. (Cache files are sparse; apparent size overstates actual disk use.)
-
Fixed: when a cache-directory hash bucket filled up entirely with current-version entries, new writes to that bucket were silently dropped (the entry was simply never cached, so affected resources were re-optimized on every request). Writes now land by evicting an existing entry, and a new
bucket_full_evictionsstatistic makes the condition observable. The effect was most likely during upgrade-day write storms into a cold cache.
Zero-copy serving (opt-in, now available on all three platforms)
- Cached resources can be served directly from the memory-mapped cache without copying the payload — available on nginx, Apache, and IIS. How it works and what it wins: The memory-mapped cache: zero-copy serving and Cyclone vs. the file cache: benchmarking a memory-mapped page cache.
- A correction to the r18 notes: they described zero-copy serving as on by default on nginx, but common configurations silently made every request ineligible, so it rarely engaged. That defect is fixed — and with r19 the feature is uniformly opt-in on every platform while it accrues production soak. On-by-default is planned for a future revision.
- To enable it:
- nginx:
pagespeed CycloneZeroCopy on; - Apache:
ModPagespeedCycloneZeroCopy onandModPagespeedCycloneZeroCopyServe on - IIS:
pagespeed CycloneZeroCopy onandpagespeed CycloneZeroCopyServe on
- nginx:
- A new
zerocopy_serve_ineligiblestatistic counts requests that fell back to copied serving, and a one-time log message explains the first fallback (on Apache this message needsLogLevel info; the statistic is always on).
Performance and reliability
-
The bundled cache library gains a lock-free read path — cache hits no longer take a lock — and no longer syncs to disk on every cache write (durability is periodic, and the power-loss window stays bounded), plus a hardening batch covering crash recovery and startup edge cases. Under write-heavy load, removing the per-write disk sync measured an order of magnitude higher sustained throughput in internal testing.
-
Memory-mapped cache reads are verified before being promoted into the in-memory tier, hardening the serving path against torn or damaged entries.
-
IIS: fixed a defect where optimization of a site’s own sub-resources (CSS, JavaScript, images) could fail to converge on machines whose name resolution prefers the IPv6 loopback — pages then kept serving their original resources for minutes at a time. The server’s internal fetches now pin the loopback address family explicitly and fall back to the other family automatically.
-
Windows/IIS: cache-invalidation updates (purge requests) after the first one were silently discarded — the on-disk purge state never advanced, so later purges did not take effect across restarts or between worker processes. Atomic file replacement on Windows now works as intended and purges apply reliably.
-
Windows/IIS: a cross-process guard now prevents one worker process from resetting a shared cache file while another process still has it mapped, closing a corruption window in multi-worker setups; two new statistics (
resets_gate_verified,resets_under_degraded_gate) make gate health observable. -
IIS: fixed a race in the server’s internal fetcher where a sub-resource fetch could be spuriously canceled just after its response had arrived. Because a failed internal fetch is remembered for several minutes, a single spurious abort could stall re-optimization of the affected resource well beyond the moment of failure, surfacing as intermittent optimization stalls.
-
Fixed on all three platforms: behind a TLS-terminating proxy (when the
X-Forwarded-Protoheader is honored), the server’s internal fetches for a page’s own sub-resources combined the page’shttpsscheme with the plain-HTTP listener port — a connection that could never succeed — so the affected CSS, JavaScript, and images were repeatedly re-fetched and never optimized. Internal fetches now use the protocol the listener actually speaks. -
Cache write-failure warnings are now rate-limited, so a persistent storage condition cannot flood the error log.
-
The legacy JavaScript minifier (used when
UseExperimentalJsMinifieris off) now minifies files containing ES2015 template literals; r18 passed such files through unmodified, r19 optimizes them.
Experimental
- The native fetcher (
UseNativeFetcher, nginx) remains off by default. Native HTTPS support has been introduced, so the fetcher can now retrievehttps://resources directly (see HTTPS configuration). Enabling it requires aresolverdirective in the nginx configuration.
v1.15.0+r18 — 2026-07-11
Performance, caching, and correctness release, with security hardening across input validation and output escaping. Update recommended.
Performance
- Cached resources are served with far less copying. Cache hits are now
served from the memory-mapped cache by reference (
CycloneZeroCopyServe) — on by default on nginx, and available as an experimental opt-in on Apache and IIS. A fully zero-copy direct-serve mode (CycloneZeroCopy) is also available as an experimental option (off by default). - Apache streams optimized resource responses instead of double-buffering them; nginx serves cached responses on HTTP/2 and HTTP/3 through a bounded copy ring, keeping per-request memory use bounded even for large responses.
Caching
- The default file cache size is raised to 1 GB (was 100 MB). Cache files are sparse, so the larger default raises the ceiling, not the baseline footprint.
- Optimization metadata and page properties are now stored in a dedicated
small-object area on file caches of roughly 256 MB or larger, so heavy
image traffic can no longer evict them — restarts stay fast even under
load. Upgrade note: on caches at or above that size, the first restart
after upgrading rebuilds the payload cache once (re-optimization proceeds
normally; metadata is unaffected). Set
FileCacheSmallTierPercent 0to disable. - The cache’s RAM tier can now be sized independently of
LRUCacheKbPerProcessvia the newCycloneRamCacheKbdirective (default 0: disabled — memory-mapped cache hits are already served from the OS page cache). - The bundled cache library receives a reliability batch: deterministic shutdown, a fix for a rare hash-collision case that could silently drop a cache entry, a tighter power-loss window (including on Windows), automatic recovery when a crash interrupts cache creation, and a lower memory footprint per cache stripe.
- New cache observability counters on the admin console’s caches page.
Correctness and configuration
Configuration validation is stricter in this release; previously-accepted invalid configurations may now fail to load, which is intentional:
- An invalid filter name in
?PageSpeedFilters=now consistently rejects the whole query — rejection was previously position-dependent, a quirk inherited from the open-source line. - Out-of-range values for bounded options now fail configuration load instead
of being silently accepted (the open-source line never range-checked
these): image qualities (-1..100), progressive JPEG scans (-1..10),
RewriteRandomDropPercentage(0..100),HttpCacheCompressionLevel(-1..9),CentralControllerPort(1..65535). - The
AddResourceHeaderlimit of 20 headers is enforced exactly. - Directive and option-scope matching is now case-consistent across all server platforms, so scope enforcement can no longer be sidestepped by casing (on Apache and nginx this behavior dates back to the open-source releases).
- The legacy JavaScript minifier (used when
UseExperimentalJsMinifieris off) now passes files containing template literals through unmodified instead of corrupting them — the minifier predates ES2015 template literals, and the corruption affected every open-source release.
Critical CSS and Content-Security-Policy
prioritize_critical_cssand other script-injecting filters now honor a restrictive Content-Security-Policy whenHonorCspis enabled, backing off instead of injecting scripts the policy would block; the CSP policy engine received a set of correctness fixes.- Inlined critical CSS preserves stylesheet charset fidelity and link
attributes, and handles
@import/@keyframesrules correctly. - The critical-CSS beacon is viewport-aware, and beacon truncation is now observable in statistics instead of silently starving extraction.
lazyload_imagesgains a native mode that emitsloading="lazy"on below-the-fold images instead of injecting the JavaScript loader.
IIS
- Fixed a defect in the loopback fetcher where a sub-resource fetch that completed asynchronously could be treated as an empty response, suppressing optimization of the parent page for five minutes at a time. Update recommended for IIS deployments.
- Configuration parsing is hardened: a malformed configuration line can no longer crash the module at startup, unknown options are reported instead of silently ignored, and option scoping is enforced on IIS as on the other platforms.
Security
- Hardened input validation and output escaping across the rewriter, beacon handling, and configuration parsing. The underlying gaps date back to the open-source line (1.14.36.1 and earlier). No exploitation is known; update recommended.
- The bundled HTTPS fetch library is updated to curl 8.21.0, which addresses a batch of recently published curl vulnerabilities.
Reliability
- Fixed a worker-pool defect where a work sequence could be recycled while
work was still queued (present since the Google-era code); nginx scheduler
alarms are now driven from the event loop; the experimental native fetcher
(
UseNativeFetcher) gains native TLS support.
v1.15.0+r17 — 2026-07-05
Caching, reliability, and security maintenance. Update recommended.
- Cache persistence across restarts. Optimization metadata and page properties are written through to the disk cache and survive server restarts, so a deploy or restart no longer triggers a re-optimization spike. (The open-source line kept these caches warm only via periodic snapshots, with a loss window.)
- Reliability. Shutdown-ordering fixes on nginx and IIS eliminate a class of rare crash-on-exit conditions; an IIS initialization race is fixed.
- Security. A memory-safety hardening fix in nginx request handling (a defect inherited from the open-source ngx_pagespeed), and the ASP.NET Core sidecar’s bundled nginx moves to 1.30.3 (CVE-2026-42055, CVE-2026-48142).
- Experimental. The opt-in AI-crawl counter for Web Bot Auth reaches parity with ModPageSpeed 2.0, including verification of real-world signers.
v1.15.0+r16 — 2026-06-30
- Reliability. Fixed a crash-on-exit condition on Apache (a shutdown-order problem between worker threads and static destruction).
- Licensing. The admin console now shows a notice when active instances exceed the licensed count. Optimization is unaffected.
- Dependencies. Defense-in-depth security updates to bundled third-party code.
v1.15.0+r15 — 2026-06-24
Security-hardening release. Update recommended.
- Binary hardening of the shipped modules — full RELRO, immediate binding, stack protector, and fortified libc calls — now asserted at release time.
- Fixed a robustness issue in CSS parsing of malformed input (a defect dating back to the Google-era CSS parser).
- Reduced attack surface: several legacy, unmaintained third-party components were removed from the build, and public-suffix handling moved to the actively maintained libpsl.
- Bundled native dependencies are continuously monitored for published vulnerabilities.
v1.15.0+r11 – r14 — 2026-06-21/22
Feature, security, and packaging roll-up (r12–r14 contain no additional product changes).
- Verified AI-crawler controls (experimental, off by default). Web Bot Auth (RFC 9421) signature verification with an observe-only mode, and optional capability-token (RSL-CAP) enforcement — matching ModPageSpeed 2.0.
- Content Credentials (C2PA) preservation (experimental, off by default). Image optimization can carry provenance metadata through instead of stripping it.
- Security. Two validation gaps in the crawler-signature verifier were closed (it now fails closed); TLS enforcement on IIS internal HTTPS sub-resource fetches is hardened (update recommended for IIS); new bounds on request header count and HTML nesting depth (the Google-era parsers had no limits); admin-console dependency updates.
- Reliability. IIS null-pointer fixes in response handling and cache-policy writes.
- Licensing. License-status diagnostics now distinguish an unreadable license file from a missing one.
- Packaging. New RHEL / AlmaLinux / Rocky 10 channel (x86_64), and an aarch64 nginx-module RPM for EL9.
v1.15.0+r10 — 2026-06-11
- Licensing. The admin console purchase flow moves to per-site licensing.
v1.15.0+r9 — 2026-06-10
- IIS reliability. Fixed a memory-safety defect and a resource leak on module unload (app-pool recycle). Update recommended for IIS deployments.
v1.15.0+r7, r8 — 2026-06-08/09
nginx packaging quality. Ubuntu modules are rebuilt against Ubuntu’s own patched nginx source — fixing load failures after Ubuntu’s security update for CVE-2026-49975 changed the nginx ABI — and the module now carries a runtime ABI guard that reports a mismatched nginx binary instead of failing unpredictably. EL9 gains an nginx-module RPM built against the distro nginx.
v1.15.0+r3 – r6 — 2026-06-04/08
Packaging expansion (r5–r6 contain no additional product changes).
- Prebuilt, signed nginx module packages for Debian 11/12/13 and Ubuntu 22.04 via the apt/yum repositories.
- ASP.NET Core sidecar ships as a NuGet package (WeAmp.PageSpeed, preview).
- EL8 / CloudLinux 8 EasyApache 4 support restored, alongside EL9.
- Build/test baseline nginx moves to 1.30.2 (CVE-2026-9256); shipped binaries are leaner (unused components are no longer compiled in).
- Install and configuration guides were corrected and expanded.
v1.15.0+r2 — 2026-06-01
- cPanel / EasyApache 4. Package versioning fix so the We-Amp
ea-apache24-mod_pagespeedreliably takes precedence over same-named packages from other sources.
v1.15.0 — 2026-06-01
The renumber release. The product version becomes 1.15.0 — the direct
successor to the final open-source 1.14.36.1 — so that package managers
and control panels order it after the open-source line. Package names,
repositories, and configuration are unchanged; the X-Mod-Pagespeed response
header reports 1.15.0.0.
One functional change: the trial-license endpoint was removed. Licensing had already moved to the always-functional model (see 1.1.0+r23 below), which makes a separate trial gate unnecessary — an unlicensed install simply keeps working.
1.1.0 releases (before the renumber)
The same product line, shipped under its original number. The original 1.1.0 download paths remain frozen and valid.
v1.1.0+r23, r24 — 2026-05-31 / 2026-06-01
- Always-functional licensing. The engine now always optimizes regardless of license state. An unlicensed install signals its state with a response header on optimized pages and an amber console notice — it is never functionally degraded. Licensed installs never flash the notice across worker restarts or renewals (a 72-hour renewal grace applies). Console copy updated to match.
v1.1.0+r22 — 2026-05-29
Security and reliability hardening release, the result of a dedicated audit pass. Update recommended.
- Fixed a remotely triggerable crash condition (denial-of-service class) on nginx — a defect inherited from the open-source ngx_pagespeed, which remains unfixed there.
- New opt-in
StrictAdminAccessdirective on Apache and nginx: admin, statistics, and console access is decided on the validated client IP and never on client-controlled headers. (IIS already gates admin access to loopback.) - Safer license-file handling on all platforms.
- ASP.NET Core sidecar hardening: admin endpoints bind to loopback, tighter configuration-file permissions, and tokens are kept out of logs.
- IIS: two lock-handling fixes and correct IPv6 address reporting.
- Redis: fixed a crash at startup when a database index is configured, and reduced per-command overhead (both defects date to the open-source line, 2017).
v1.1.0+r18 – r21 — 2026-05-25/27
- Reliability. Cache-library update: Apache graceful restarts no longer
leak scoreboard slots, and
nginx -s reloadno longer hangs — both were process-lifecycle defects in cache teardown. - Security. Admin-console dependency updates clearing 13 published advisories (4 high, 9 medium).
- Packaging. The signed apt/yum repositories for the nginx module go GA, with per-distro builds pinned to each distro’s stock nginx and blocking load, symbol, and optimization smoke gates. The EasyApache 4 configuration now degrades gracefully if the module file is absent instead of breaking Apache startup.
v1.1.0+r9 – r17 — 2026-05-21/22
IIS quality wave (r12–r17 contain no product changes). Update recommended for IIS deployments.
- Fixed crashes in the IIS URL fetcher and in lock handling.
- Admin endpoints are default-deny in the shipped sample configurations, and the module logs a warning when an admin handler receives a non-loopback request.
- Smoother installation: per-site cache and log directories are auto-created
with safe permissions (new
AutoCreateLogDirdirective), initialization failures surface as specificX-Pagespeed-Init-Statusvalues with per-failure error pages, and the MSI grants the required ACLs. - Upgrading from IISpeed: an existing
iiswebspeed.configis picked up automatically when nopagespeed.configis present.
v1.1.0+r1 – r8 — 2026-05-19/21
Packaging and distribution (most of these revisions contain no product changes).
- First cPanel / EasyApache 4 channel:
ea-apache24-mod_pagespeedRPMs. - The
+rNpackage-revision scheme is established (mapping to the RPMReleasefield and the deb revision).
v1.1.0 — GA — 2026-05-15
The first We-Amp release of the maintained line: the direct successor to the final open-source release, 1.14.36.1 (August 2020). Everything below is relative to that baseline — in particular, the security and reliability fixes repair defects that were present in the final open-source release, not regressions in this line. See What’s new in 1.15 for the narrative version.
Security
- All bundled image codecs and network libraries updated across six years of upstream security work: libwebp 1.5.0 (includes the fix for CVE-2023-4863, the widely exploited WebP heap overflow), libjpeg-turbo 3.1.x, libpng 1.6.58, and a modern TLS stack. The HTTPS fetcher was replaced with libcurl at a current release, clearing a 12-CVE backlog in the process, and nginx module builds moved to a 1.30.x baseline covering the May 2026 nginx CVE cluster.
- Memory-safety fixes and hardening throughout image processing.
- Admin-surface hardening throughout: constant-time token comparison, strict validation of cache-purge request parameters, CSRF protection and security headers on the admin and licensing endpoints, and mutating endpoints restricted to the global admin.
- Bounded resource consumption on untrusted input: response-size and header-size caps in the fetcher, an HTML parser that stops at its size limit instead of accumulating without bound, and graceful error handling where malformed input could previously abort the process.
- On IIS, internal HTTPS fetches validate certificates by default.
- Release integrity: all packages are GPG-signed with a published key, and the Windows binaries (DLL and MSI) carry timestamped Authenticode signatures.
- Development is backed by continuous sanitizer testing (AddressSanitizer, ThreadSanitizer, and Application Verifier on Windows).
Reliability
- Fixes for long-standing crash and data-race conditions in the asynchronous rewrite lifecycle, the proxy fetch path, and cache write-ordering (a large-cache outage could previously produce persistent misses).
- Graceful degradation replaces hard process aborts on recoverable conditions.
- Many platform-specific stability fixes; see the per-platform notes below.
Cyclone Cache — the new disk cache
- The original file-based cache is replaced by Cyclone Cache: a fixed-size, memory-mapped, scan-resistant cache shared safely across processes, with no periodic cleanup passes and a persistent index, so restarts start warm.
- Fully configuration-compatible:
FileCachePathkeeps working and now locates the Cyclone cache; obsolete tuning options are accepted as deprecated no-ops with a warning. - The rest of the cache stack is unchanged: LRU cache, shared-memory metadata cache, Redis, and Memcached all remain.
Modernization
- The build moved from GYP to Bazel, with the core on modern C++ (C++20/23) and all dependencies pinned and vendored; the source tarball builds fully offline.
- Prebuilt, signed packages replace compile-it-yourself: deb and RPM for Apache, a prebuilt dynamic module for stock nginx, and an MSI for IIS.
- arm64/aarch64 support added on Linux; 32-bit x86 dropped; Apache 2.2
dropped (2.4+ only); the Google-era stable/beta/unstable channel split
collapsed into a single
mod-pagespeedpackage.
Apache
- A single
mod_pagespeed.sofor Apache 2.4+, installed the same way as before (a2enmod pagespeed, same configuration layout). Bundled TLS symbols are hidden from the host process to prevent library clashes.
nginx
- ngx_pagespeed is now maintained in-tree and shipped as a prebuilt dynamic
module loadable into stock nginx 1.26/1.27 with
load_module— no more compiling nginx from source.
IIS — the IISpeed successor
- A new native IIS module (
pagespeed_iis.dll) for IIS 10+ replaces IISpeed, built on the IISpeed codebase and brought to parity with the Linux platforms (streaming HTML rewriting, in-place resource optimization, per-site configuration, the shared admin console). - Existing IISpeed installs migrate in place: the module reads
pagespeed.configand falls back to an existingiiswebspeed.config; admin pages live at the standard/pagespeed_adminpaths. - Ships as a signed MSI with clean upgrade and uninstall support.
Licensing
- 1.1.0 introduced commercial licensing with in-console trial, activation, and renewal flows and a 72-hour renewal grace period. (Since 1.1.0+r23, licensing is always-functional: an unlicensed install keeps optimizing and is signaled, never blocked.)
What stayed the same
- All configuration directives, the full filter set (40+ filters),
.pagespeed.resource URLs, in-place resource optimization, the admin and statistics endpoints,.htaccesssupport on Apache, and theX-Mod-Pagespeed/X-Page-Speedresponse headers. Obsolete Google-era options (update channels, distributed rewriting) are accepted as no-ops with a warning rather than breaking startup.
Security updates
mod_pagespeed 1.15 is actively security-maintained. All known CVEs from the open-source mod_pagespeed project have been resolved, and the maintained line has had substantial additional security and robustness work since it took over from the final open-source 2020 release. We recommend running the latest release.
At a high level, that work spans:
- Memory-safety hardening across the engine and request-handling paths, backed by continuous sanitizer testing on every platform.
- Robustness against malformed or oversized input — untrusted content and abnormal conditions are handled gracefully instead of crashing or exhausting resources.
- Admin-surface hardening — stricter authentication, safer defaults, and improved resilience of administrative and reporting endpoints to malformed or unauthorized external requests.
- Binary hardening of the shipped modules (RELRO, BIND_NOW, stack-protector, FORTIFY_SOURCE) and a reduced attack surface.
- Ongoing dependency updates, including image codecs, the bundled HTTPS fetch library, and tracking upstream nginx security releases, gated by continuous CVE scanning.
We keep these descriptions general by design: security entries state the impact class and the recommendation, not the mechanism. If your servers run an older open-source mod_pagespeed build, moving to 1.15 is the supported way to get all of the above.
Reporting a security issue
Found a security problem? Please email info@we-amp.com so we can investigate and ship a fix.