Skip to main content
mod_pagespeed 1.15 — Cyclone Cache, modernized optimizations, IIS + .NET support, and six years of security updates
2.0 1.15

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 — NumRewriteThreads for HTML, CSS and JavaScript, NumExpensiveRewriteThreads for 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 / ThreadsPerChild on worker and event, MaxRequestWorkers on prefork, capped by ServerLimit in 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 event allows 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 NumRewriteThreads and NumExpensiveRewriteThreads explicitly — 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. NumRewriteThreads and NumExpensiveRewriteThreads remain the opt-out and still override the computed value entirely; auto (or 0) 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 NumRewriteThreads or NumExpensiveRewriteThreads. 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 to auto first. 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 default LogLevel and never reached the log at all. (The same line is emitted on the other ports, but nginx’s compiled-in default error_log level is error, so on nginx it still takes error_log ... warn to 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_failed counts 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 existing ipro_recorder_not_cacheable, ipro_recorder_dropped_due_to_load, and ipro_recorder_dropped_due_to_size). Each recorder outcome is also logged for diagnosis.

  • A stray ; after a rule inside an @media block — 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 by prioritize_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_subresources again hints <script type="module"> subresources, now using rel=modulepreload in the Link response header it emits (this filter adds no markup to the page). Module scripts stopped being hinted in r20: the older rel=preload; as=script hint does not match how a browser fetches a module, so it could cost an extra fetch rather than save one. rel=modulepreload matches the module fetch, so the hint is usable. Modules carrying integrity or crossorigin="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 Accept request 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: with in_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 enable convert_to_webp_animated. Sites running in_place_optimize_for_browser additionally 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. UseExperimentalJsMinifier is 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 set UseExperimentalJsMinifier explicitly re-optimize their JavaScript once after upgrading; with on the output and the rewritten URLs are identical and only that one background pass is new, while with off the 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 running off: <script type="module"> is now minified, and include_js_source_maps now produces source maps. If you generated your own ModPagespeedLibrary signatures for canonicalize_javascript_libraries against 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_images and 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 that curl and wget are 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 WebBotAuthBotDetection directive (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. Requires WebBotAuth, 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_bot nginx variable, which you can log or pass to your own configuration, and the opt-in verified-request statistics — exactly as in r20.

  • defer_javascript no longer sends deferred markup to automated clients. When defer_javascript (or disable_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 the support_noscript fallback they share, so such a client gets clean markup rather than clean markup plus a stray <noscript> redirect banner. Browsers are unaffected, and defer_javascript remains 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_images has 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-line await {…} or for (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 — x on 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, including await or yield) 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 =>, so a = x followed by => y on the next line is already a syntax error. The minifier dropped that line break and emitted a=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 0 and a following property access is now kept. 0 .toString() minified to 0.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 bare 0; 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 the crossorigin, integrity and template attributes 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); } became h1 {}). 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() with var() 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 id could 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 @import rules).
  • 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_images or any rewrite level, by design. AV1 encoding costs substantially more CPU than WebP, so folding it into rewrite_images would 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 AvifTimeoutMs is lowered — a long animated encode is bounded by the abort applied between frames rather than by the budget. Account for this before enabling convert_to_avif_animated over 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: sets fetchpriority="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-supplied fetchpriority always wins, and it backs off on Save-Data requests, 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, or no-store responses, 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 usable crossorigin preconnect. 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_subresources now emits font preload hints (rel=preload; as=font; crossorigin, up to four per page) harvested from @font-face rules 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 whose unicode-range covers printable ASCII. Fonts referenced only from @imported stylesheets are collected once flatten_css_imports is 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 PerformanceObserver and sent in a single sendBeacon POST when the page is hidden. This replaces the legacy on-load image GET and the beforeunload beacon; the beforeunload handler 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. ReportUnloadTime is deprecated to a no-op.

  • The tokenizer-based JavaScript minifier (UseExperimentalJsMinifier) now handles modern syntax — ??, ??=, ?., optional catch binding, destructuring declarations, super, dynamic import()/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_javascript minifies 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 @container blocks, 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 @layer meant the entire file. These constructs now parse: such stylesheets minify, images referenced inside the blocks are rewritten, inlined, and cache-extended like any others, and prioritize_critical_css collects and inlines critical selectors inside them while preserving @layer cascade order. Sheets that still fail to parse are served byte-for-byte unchanged, as before.

  • insert_dns_prefetch now 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-only rel=prefetch variant 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_images and native-mode lazyload_images now handle images that use srcset without a src attribute: the beacon reports the candidate the browser actually displays, beacon-critical candidates get fetchpriority="high", and the rest are lazy-loaded under the same first-image LCP protection as src images, honoring author loading/fetchpriority/decoding attributes.

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(), and clamp() — after any nested function such as var() (e.g. calc(var(--x) - 0px) became calc(var(--x) - 0)), and throughout min()/max()/clamp()/ -webkit-calc themselves — producing invalid CSS that browsers drop. Math-function context is now recognized for all of these functions and preserved across nesting.
  • combine_javascript now requires the Content-Security-Policy to permit inline scripts before combining, not just unsafe-eval. The filter replaces script tags with small inline bootstrap scripts; under policies such as unsafe-eval without unsafe-inline (or strict-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 Vary header merge defect in in-place optimization: when a response already carried one Vary token (such as Accept), another needed token (such as User-Agent or Save-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 an import/export declaration, a plain let/var declaration, 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>*/x was minified to return 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-Policy base-uri directive 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. Any base-uri value 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_javascript no longer inlines external scripts carrying async or defer: 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 the for/event attribute 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-Data request header now carry Vary: Save-Data, so a downstream cache cannot serve the data-saver variant to a full-data client or vice versa. Existing Vary tokens are preserved.
  • CSS minification now recognizes the math functions calc(), min(), max(), and clamp() 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 @import rule 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 @charset rule 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 @charset now 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 @IMPORT case-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_cache defect 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_javascript that ran deferred inline scripts through a data: URL (for a Firefox bug fixed in 2013). Content-Security-Policy rules that permit inline scripts do not permit data: script URLs, so under such policies every deferred inline script was blocked on Firefox and the page broke.
  • cache_partial_html no longer triggers the no-script redirect machinery for clients without JavaScript: like defer_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_attributes now also elides values matching current-HTML defaults in HTML5 documents — loading=eager, decoding=auto, and fetchpriority=auto on images, media=all on stylesheet links, and fetchpriority=auto on links and scripts — and strips values from the modern boolean attributes open (dialog), disabled (fieldset), allowfullscreen (iframe), and playsinline (video). Entries for long-dead markup (command, keygen, seamless, and similar) were removed and no longer alter such elements.
  • remove_quotes now also strips quotes from attribute values containing /, so most URL-valued attributes (such as href="/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_quotes now 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_converted now reflects recognized-but-unconverted AdSense snippets (it previously always reported 0).
  • num_js_inlined no 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_ms average) 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_import counts stylesheets for which import-flattening was declined because an @import rule uses syntax the CSS parser does not understand (such as cascade layers or other unrecognized import syntax).
  • The image_ongoing_rewrites gauge 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 -5000 no-data placeholder when a histogram has too few samples to estimate them. The cells are now rendered empty instead — for the raw /histograms output as well as the admin console, which previously hid the placeholder for itself only. Anything scraping /histograms should 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 Date headers 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), and AvifTimeoutMs (5000). They take effect only when one of the AVIF filters is enabled; see the AVIF entry under Features.

  • IIS: pagespeed.config now 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):

    1. %ProgramData%\We-Amp\PageSpeed\pagespeed.config — machine-global base.
    2. %ProgramData%\We-Amp\IISWebSpeed\pagespeed.config — legacy fallback, for upgrades from IISpeed.
    3. <site physical path>\pagespeed.config — per-site override, authoritative when present.

    Within each location pagespeed.config is still preferred over the legacy iiswebspeed.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_ga or setting AnalyticsID logs a deprecation warning at configuration load (disabling stays silent), and make_google_analytics_async is now a no-op whose name still parses, so existing configurations keep loading. Experiments no longer auto-enable insert_ga: the A/B framework keeps variant assignment (the PageSpeedExperiment cookie) and the experiment id in the instrumentation beacon, reporting is bring-your-own-analytics, and an experiment spec can still opt in explicitly with enable=insert_ga.

  • defer_iframe is deprecated: the filter name was accepted but had no effect on its own (iframe deferral is built into defer_javascript and disable_javascript). The name still parses for compatibility, logs a deprecation warning at configuration load, and no longer triggers the no-script redirect machinery. Configurations using defer_iframe can 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, and ExperimentalPopularityContestMaxQueueSize options 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, and controller_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 using ShardDomain can simply remove the directive.

  • The long-inert filters div_structure, explicit_close_tags, mobilize_precompute, split_html, split_html_helper, and flush_subresources are deprecated: the names still parse for compatibility and log a deprecation warning at configuration load but have no effect. (flush_subresources also no longer adds head-section domains to the insert_dns_prefetch hint list.) Configurations using them can simply remove them.

  • ForbidFilters now 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_evictions statistic 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 on and ModPagespeedCycloneZeroCopyServe on
    • IIS: pagespeed CycloneZeroCopy on and pagespeed CycloneZeroCopyServe on
  • A new zerocopy_serve_ineligible statistic counts requests that fell back to copied serving, and a one-time log message explains the first fallback (on Apache this message needs LogLevel 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-Proto header is honored), the server’s internal fetches for a page’s own sub-resources combined the page’s https scheme 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 UseExperimentalJsMinifier is 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 retrieve https:// resources directly (see HTTPS configuration). Enabling it requires a resolver directive 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 0 to disable.
  • The cache’s RAM tier can now be sized independently of LRUCacheKbPerProcess via the new CycloneRamCacheKb directive (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 AddResourceHeader limit 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 UseExperimentalJsMinifier is 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_css and other script-injecting filters now honor a restrictive Content-Security-Policy when HonorCsp is 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/@keyframes rules correctly.
  • The critical-CSS beacon is viewport-aware, and beacon truncation is now observable in statistics instead of silently starving extraction.
  • lazyload_images gains a native mode that emits loading="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_pagespeed reliably 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 StrictAdminAccess directive 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 reload no 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 AutoCreateLogDir directive), initialization failures surface as specific X-Pagespeed-Init-Status values with per-failure error pages, and the MSI grants the required ACLs.
  • Upgrading from IISpeed: an existing iiswebspeed.config is picked up automatically when no pagespeed.config is 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_pagespeed RPMs.
  • The +rN package-revision scheme is established (mapping to the RPM Release field 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: FileCachePath keeps 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-pagespeed package.

Apache

  • A single mod_pagespeed.so for 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.config and falls back to an existing iiswebspeed.config; admin pages live at the standard /pagespeed_admin paths.
  • 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, .htaccess support on Apache, and the X-Mod-Pagespeed / X-Page-Speed response 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.