2.0 1.1

mod_pagespeed configuration: nginx directives and worker flags

Reference for ModPageSpeed 2.0 nginx directives, worker flags, and cache tuning. Looking for the mod_pagespeed 1.1 reference? See /1.1/docs/configuration/.

ModPageSpeed 2.0 has a minimal configuration surface: two nginx directives, a shared config file, and worker command-line flags. This page covers all of them, plus cache tuning and the capability mask format.

New to ModPageSpeed?This is the configuration reference. If you are just getting oriented, start with the product overview or Getting Started , then come back here once it is installed. To install first, see Install with Docker or Install the nginx module .

Nginx Directives

These directives are available in http , server , and location contexts. Values set at a higher level are inherited by nested blocks.

pagespeed

Enables or disables the PageSpeed module.

   pagespeed 
 on 
 ; 
 # Enable 
 
  pagespeed 
 off 
 ; 
 # Disable (default) 
 
 

Context: http , server , location Default: off

When enabled, the module intercepts responses, checks the cache for optimized variants, and adds the X-PageSpeed response header ( HIT or MISS ).

pagespeed_cache_mode

Controls how Cache-Control headers are assembled on optimized responses.

   pagespeed_cache_mode 
 safe; 
 # default — short TTLs, must-revalidate 
 
  pagespeed_cache_mode 
 aggressive; 
 # long TTLs, public, stale-if-error 
 
 

Context: http , server , location Default: safe

In safe mode, must-revalidate is added to all non-HTML responses, SWR synthesis is suppressed, and immutable is stripped from transformed content. In aggressive mode, public and stale-if-error=86400 are added, and SWR synthesis is allowed. HTML always gets no-cache in both modes.

The per-type max-age defaults change based on the active mode: CSS/JS defaults to 300s (safe) or 86400s (aggressive); images default to 1800s (safe) or 86400s (aggressive).

See Cache Modes for the full reference.

pagespeed_cache_path

Path to the Cyclone cache volume file.

   pagespeed_cache_path 
 /var/lib/pagespeed/cache.vol; 
 
 

Context: http , server , location Default:(empty — caching disabled if not set)

This must point to the same file used by the worker’s --cache-path flag. Both processes open the file with memory-mapped directory sharing enabled, so writes from either process are immediately visible to the other.

The cache file is created automatically if it doesn’t exist. For cross-process sharing to work, the file must be readable and writable by both nginx worker processes and the worker (typically chmod 666 ).

pagespeed_disallow

Exclude URLs from caching and optimization. Multiple directives allowed; first match wins. Supports prefix, suffix, and substring patterns.

   pagespeed_disallow 
 /api/; 
 # Prefix match 
 
  pagespeed_disallow 
 *.woff2; 
 # Suffix match 
 
  pagespeed_disallow 
 admin; 
 # Substring match 
 
 

Context: http , server , location

pagespeed_hot_threshold

Number of fallback-hits before a URL is considered “hot” and triggers a warmup notification to the worker. Only relevant when --enable-warmup is set on the worker.

   pagespeed_hot_threshold 
 5 
 ; 
 # default 
 
 

Context: http , server , location Default: 5

pagespeed_trust_x_forwarded_proto

Tells PageSpeed to read the X-Forwarded-Proto header to determine the request scheme ( http or https ) instead of relying on connection-level SSL detection.

   pagespeed_trust_x_forwarded_proto 
 on 
 ; 
 
 

Context: http , server , location Default: off

Use this when nginx is behind a TLS-terminating reverse proxy, load balancer, or CDN that sets X-Forwarded-Proto . Only lowercase http and https header values are accepted; any other value falls back to connection-level SSL detection.

:::caution Only enable this when the upstream proxy strips and re-sets the X-Forwarded-Proto header. If clients can set this header directly, they can manipulate scheme detection. :::

Example — upstream proxy that terminates TLS:

   # Upstream nginx (TLS termination) 
 
  server 
 { 
 
  listen 
 443 
 ssl; 
 
  location 
 / 
 { 
 
  proxy_set_header 
 X-Forwarded-Proto $scheme; 
 
  proxy_pass 
 http://backend; 
 
  } 
 
  } 
 
  
  # Backend nginx (PageSpeed) 
 
  server 
 { 
 
  listen 
 80 
 ; 
 
  pagespeed 
 on 
 ; 
 
  pagespeed_trust_x_forwarded_proto 
 on 
 ; 
 
  pagespeed_cache_path 
 /var/lib/pagespeed/cache.vol; 
 
  } 
 
 

Cache-Control Directives

The following directives control how the interceptor sets Cache-Control and Age headers on cache-served responses. See the Cache Control guide for detailed behavior.

Directive Default Description
pagespeed_max_age
86400 Cap on origin max-age (seconds)
pagespeed_immutable_max_age
604800 Cap for Cache-Control: immutable content
pagespeed_html_max_age
0 Default max-age for HTML when origin sends no Cache-Control
pagespeed_css_max_age
300 Default max-age for CSS/JS when origin sends no Cache-Control
pagespeed_image_max_age
1800 (safe) / 86400 (aggressive) Default max-age for images when origin sends no Cache-Control. Default changes based on pagespeed_cache_mode .
pagespeed_synthesize_swr
on Synthesize stale-while-revalidate on HIT responses
pagespeed_conditional_revalidation
on Enable conditional revalidation (If-None-Match / If-Modified-Since)
pagespeed_force_refresh_html
on Force revalidation on browser force-refresh (Ctrl+F5) for HTML
pagespeed_force_refresh
off Force revalidation on browser force-refresh (Ctrl+F5) for all non-HTML types

Shared Configuration File

The worker writes a pagespeed-shared.conf file next to the cache file (in the parent directory of --cache-path ). Nginx reads this file automatically using pagespeed_cache_path to locate it. The file is polled every ~1 second for changes.

Format: key=value (one per line)

Fields:

Key Description Worker flag
socket_path
Unix socket path for nginx-to-worker notifications --socket PATH
license_key
License token for nginx license validation --license-key KEY
disable_html
Whether HTML optimization is disabled ( true / false ) --disable-html

The shared config file is written on worker startup and whenever settings change via PATCH /v1/config . This eliminates the need to duplicate configuration between the nginx config and worker flags.

On a cache miss, nginx reads the socket_path from the shared config and sends a fire-and-forget notification to the worker. If the socket is not available (worker not running, socket not yet created), the notification is silently dropped. The original content continues to be served from the cache.

Worker Command-Line Flags

The factory_worker binary accepts these flags:

--cache-path PATH

Path to the Cyclone cache volume file. Required.

   factory_worker 
 --cache-path 
 /var/lib/pagespeed/cache.vol 
 
 

Must match the pagespeed_cache_path nginx directive. The worker reads original content from this file and writes optimized variants back to it.

--socket PATH

Unix socket path for receiving notifications from nginx.

   factory_worker 
 --socket 
 /var/lib/pagespeed/pagespeed.sock 
 
 

Default: /tmp/pagespeed.sock

The socket path is shared with nginx automatically via pagespeed-shared.conf (no nginx directive needed). The socket is created by the worker on startup. Ensure the directory exists and is writable. Use UMask=0000 in the systemd unit (or umask 0000 in Docker) so the socket is world-writable.

--cache-size BYTES

Maximum cache size in bytes.

   factory_worker 
 --cache-size 
 536870912 
 # 512 MB 
 
 

Default: 1073741824 (1 GB)

This controls the total size of the Cyclone cache volume. The cache uses LRU eviction when full — least recently accessed entries are removed to make room for new content.

--help

Print usage information and exit.

--version

Print the ModPageSpeed version and exit.

--max-connections N

Maximum number of simultaneous client connections.

   factory_worker 
 --max-connections 
 256 
 
 

Default: 128

When the limit is reached, new connections are accepted and immediately closed. Monitor the health check endpoint to track connection usage.

--max-buffer-size BYTES

Maximum per-client buffer size for incoming notifications.

   factory_worker 
 --max-buffer-size 
 2097152 
 # 2 MB 
 
 

Default: 1048576 (1 MB)

Connections exceeding this buffer size are disconnected to prevent memory exhaustion from malformed or excessively large messages.

--connection-timeout MS

Idle connection timeout in milliseconds.

   factory_worker 
 --connection-timeout 
 60000 
 # 60 seconds 
 
 

Default: 30000 (30 seconds)

Connections that don’t send data within this timeout are closed.

--shutdown-timeout MS

Graceful shutdown timeout in milliseconds.

   factory_worker 
 --shutdown-timeout 
 10000 
 # 10 seconds 
 
 

Default: 5000 (5 seconds)

On SIGTERM/SIGINT, the worker stops accepting new connections and waits up to this duration for active connections to drain before force-closing them.

--disable-html

Disable HTML optimization (critical CSS injection). HTML notifications from nginx are silently ignored.

--disable-css

Disable CSS minification. CSS notifications are silently ignored.

--disable-js

Disable JavaScript minification. JS notifications are silently ignored.

--disable-image

Disable image transcoding. Image notifications are silently ignored.

--max-url-length BYTES

Maximum URL length accepted in notifications.

Default: 8192

Notifications with URLs longer than this are rejected.

--max-html-size BYTES

Maximum HTML content size for processing.

Default: 5242880 (5 MB)

HTML documents larger than this are skipped.

--max-css-size BYTES

Maximum CSS content size for processing.

Default: 2097152 (2 MB)

--max-js-size BYTES

Maximum JavaScript content size for processing.

Default: 2097152 (2 MB)

--max-image-size BYTES

Maximum image content size for processing.

Default: 10485760 (10 MB)

--log-level LEVEL

Minimum log level. Messages below this level are suppressed.

   factory_worker 
 --log-level 
 warning 
 
 

Default: info

Values: debug , info , warning , error

--log-format FORMAT

Log output format.

   factory_worker 
 --log-format 
 json 
 
 

Default: text

Values:

  • text — Human-readable: [INFO] message text
  • json — Machine-parseable: {"timestamp":"...","level":"INFO","message":"..."}

JSON format is recommended for production environments with log aggregation.

Proactive Variant Generation

When the worker receives an image notification, it can generate multiple variants from a single decode pass. This avoids decoding the same image repeatedly for different client types. Each dimension can be independently enabled or disabled.

--proactive-image-variants

Enable multi-format generation. When set, a single notification generates WebP, AVIF, and an optimized original from one decode pass. Without this flag (the default), the worker produces only the single format requested by the notification (e.g., only WebP).

--no-proactive-viewport-variants

Disable viewport sibling generation. When set, the worker only generates variants for the viewport in the notification (e.g., only Mobile). Without this flag (the default), variants for all three viewport classes (Mobile/Tablet/Desktop) are generated from one decode, resized to each viewport’s target width.

--no-proactive-savedata-variants

Disable Save-Data sibling generation. When set, the worker only generates variants matching the notification’s Save-Data preference. Without this flag (the default), both normal-quality and Save-Data (lower-quality) variants are generated from each decode pass.

Save-Data variants use lower compression quality to reduce bandwidth:

Format Normal Quality Save-Data Quality
JPEG
85 60
WebP
75 50
AVIF
25 35 (higher = lower quality for AVIF)

These quality levels can be overridden at runtime with CLI flags (see Image Quality Flags below).

--no-proactive-density-variants

Disable pixel density sibling generation. When set, the worker only generates variants for the pixel density in the notification (1x or 2x+). Without this flag (the default), variants for both 1x and 2x+ density are generated from each decode pass.

For 2x+ density, the viewport target width is doubled before resizing. For example, a Mobile/2x variant uses a target width of 960px instead of 480px, providing sharper images for Retina displays.

Variant Matrix

With all proactive flags enabled (the default), a single image notification can produce up to:

  • 3 formats (WebP, AVIF, optimized original)
  • 3 viewports (Mobile, Tablet, Desktop)
  • 2 Save-Data states (off, on)
  • 2 pixel densities (1x, 2x+)

That is up to 37 variants(36 raster plus 1 SVG for eligible images) from a single decode pass. In practice, many of these already exist in the cache and are skipped, so the actual number of new writes is typically much smaller.

--enable-warmup

Enable hot URL variant warmup. When nginx detects a URL exceeding the hot threshold ( pagespeed_hot_threshold ), it sends a warmup sentinel to the worker. With warmup enabled, the worker pre-generates all missing variants for that URL. Without this flag (the default), warmup notifications are ignored.

Viewport Resize Widths

Control the target width for viewport-based image resizing. Images wider than the target are downscaled (preserving aspect ratio). Images already smaller than the target are not resized.

   factory_worker 
 --mobile-width 
 480 
 --tablet-width 
 768 
 --desktop-width 
 0 
 
 
Flag Default Description
--mobile-width
480 Target width for mobile viewport (0=disable)
--tablet-width
768 Target width for tablet viewport (0=disable)
--desktop-width
0 Target width for desktop viewport (0=no resize)

URL Normalization

--strip-query-extensions LIST

Strip query strings from URLs with specified file extensions. Comma-separated list. This helps consolidate cache entries for static assets that vary only by cache-busting query parameters.

   factory_worker 
 --strip-query-extensions 
 css,js,png,jpg,webp 
 
 

--strip-query-groups LIST

Strip query strings by group name. Comma-separated list.

--strip-query-params LIST

Strip specific named query parameters from URLs. Comma-separated list.

   factory_worker 
 --strip-query-params 
 utm_source,utm_medium,fbclid 
 
 

--host-alias SRC=DST

Map one hostname to another for cache key purposes. Requests for SRC are treated as DST in cache lookups. Multiple --host-alias flags allowed.

   factory_worker 
 --host-alias 
 www.example.com=example.com 
 
 

--allow-private-urls

Allow capture and waterfall requests for private/loopback URLs. Off by default for security. Enable only in development or when the worker needs to analyze sites on private networks.

Image Quality Flags

Override the default image encoding quality at runtime. These flags apply to all image transcoding performed by the worker.

Normal Quality

Flag Default Range Description
--jpeg-quality
85 1-100 JPEG output quality
--webp-quality
75 0-100 WebP output quality
--avif-quality
25 0-63 AVIF output quality (lower = better)

Save-Data Quality

These are used when the request includes Save-Data: on :

Flag Default Range Description
--savedata-jpeg-quality
60 1-100 Save-Data JPEG quality
--savedata-webp-quality
50 0-100 Save-Data WebP quality
--savedata-avif-quality
35 0-63 Save-Data AVIF quality

Example

   factory_worker 
 --cache-path 
 /data/cache.vol 
 \ 
 
  --jpeg-quality 
 80 
 --webp-quality 
 70 
 --avif-quality 
 30 
 \ 
 
  --savedata-jpeg-quality 
 55 
 --savedata-webp-quality 
 40 
 --savedata-avif-quality 
 40 
 
 

Learned Quality Prediction

Per-format ML models predict the optimal encoder quality parameter for a target SSIMULACRA2 score. The models are trained LightGBM decision trees compiled to C at build time — zero runtime dependencies, ~5 microsecond inference per format.

When enabled, the worker extracts 16 image features (edge density, noise level, color entropy, spatial frequency, luminance, etc.) from the decoded pixels and predicts the encoder quality that hits the configured target_ssimulacra2 score. SSIMULACRA2 verification still runs as a safety net: if the predicted quality produces a score outside the tolerance band, the worker re-encodes.

When disabled or when a model returns an invalid prediction (e.g., the AVIF model is not yet trained), the worker falls back to the existing heuristic quality calculation based on content class and base quality.

Flag Default Description
--no-learned-quality
(enabled) Disable learned quality prediction (all formats)
--no-learned-quality-jpeg
(enabled) Disable for JPEG only
--no-learned-quality-webp
(enabled) Disable for WebP only
--no-learned-quality-avif
(enabled) Disable for AVIF only
--savedata-score-reduction
15 SSIMULACRA2 points to subtract for Save-Data

Example

   # Disable learned quality for AVIF (stub model), keep JPEG and WebP 
 
  factory_worker 
 --cache-path 
 /data/cache.vol 
 --no-learned-quality-avif 
 
  
  # Disable all learned quality, fall back to heuristic 
 
  factory_worker 
 --cache-path 
 /data/cache.vol 
 --no-learned-quality 
 
 

SSIMULACRA2 Quality Tuning

The worker uses SSIMULACRA2 perceptual quality scoring to verify that encoded images meet a target visual quality. These flags control the target score and the tolerance band around it.

Flag Default Range Description
--target-ssimulacra2
70 0-100 Target SSIMULACRA2 score for encoded images
--ssimulacra2-tolerance
5.0 float Acceptable deviation from target
--no-quality-verify
(on) flag Disable SSIMULACRA2 verification entirely

When verification is enabled (the default), the worker encodes at the predicted or configured quality level, then measures the actual SSIMULACRA2 score. If the score falls outside the asymmetric tolerance band [target - 0.6*tolerance, target + 1.6*tolerance] , the worker re-encodes with adjusted quality.

Disabling verification ( --no-quality-verify ) skips the SSIMULACRA2 measurement pass entirely, reducing CPU cost at the risk of inconsistent visual quality.

Content Analysis and Denoising

The worker classifies image content (photo, screenshot, illustration, noisy) and applies content-aware encoding presets. A bilateral filter denoises images that exceed the noise threshold before encoding, improving compression efficiency.

Flag Default Range Description
--no-content-analysis
(on) flag Disable content classification
--denoise-threshold
0.3 0.0-1.0 Noise level threshold (0=disable denoising)
--denoise-sigma-spatial
3.0 0.0-10.0 Bilateral filter spatial sigma
--denoise-sigma-range
25.0 float Bilateral filter intensity range sigma

Content analysis is fast (runs on the already-decoded pixel buffer) and feeds into both the learned quality model and the denoising decision. Disabling it falls back to the Unknown content class for all images.

HTML Optimization Flags

These flags control individual HTML transforms applied by the worker. All are enabled by default.

Flag Default Description
--no-lazy-load-images
(enabled) Disable loading="lazy" injection on images/iframes
--no-image-dimensions
(enabled) Disable width / height injection from cached headers
--no-lcp-preload
(enabled) Disable <link rel="preload"> for LCP images
--no-preconnect-injection
(enabled) Disable preconnect hints for third-party origins
--enable-speculation-rules
(off) Enable speculation rules injection
--no-async-css
(enabled) Disable async CSS loading for render-blocking stylesheets
--no-script-deferral
(enabled) Disable automatic script deferral
--no-css-import-flattening
(enabled) Disable CSS @import inlining during processing

Lazy loading skips the LCP candidate image (which gets fetchpriority="high" instead) and the first 3 body images when an LCP candidate is identified.

LCP preload injects a <link rel="preload" as="image" fetchpriority="high"> tag in the <head> and writes the URL to the Early Hints cache sentinel, so nginx can emit 103 Early Hints or Link response headers on subsequent requests.

Preconnect injection detects third-party origins from external resources in the HTML and writes preconnect: hints to the Early Hints sentinel.

Async CSS loading adds fetchpriority="low" to render-blocking <link rel="stylesheet"> tags, deprioritizing their network fetch while keeping them as normal stylesheets with no JavaScript dependency. This transform only activates when critical CSS has been injected (so the page still renders correctly) and when the optimization policy engine determines it is beneficial based on measured CSS coverage data. Disable with --no-async-css .

Script deferral adds the defer attribute to <script src="..."> tags that browser script analysis has identified as safe to defer. Scripts already marked with async , defer , or type="module" are left unchanged. The worker uses path-boundary suffix matching to map analysis results to script URLs across pages. Disable with --no-script-deferral .

Transforms

Per-transform reference. Each entry names the 2.0 control surface and the matching 1.1 filter(s), so operators migrating from 1.1 can find the new knob and /analyze filter chips can deep-link to a specific transform.

Toggleable transforms expose a factory_worker --no-<name> flag and are enabled by default. Always-on transforms run from the master switch ( pagespeed on; ) and cannot be turned off individually; the only way to suppress them is the coarse --disable-html / --disable-css / --disable-js / --disable-image family or per-location pagespeed off; .

Image pipeline

Decodes source images, applies content-aware quality and denoising, and re-encodes to modern formats (WebP, AVIF). Emits viewport, density, and Save-Data variants. Metadata (EXIF, ICC profiles other than sRGB) is stripped. Always-on baseline under pagespeed on; ; there is no per-transform off switch. Use --disable-image to skip the image pipeline entirely, or pagespeed_disallow to exclude specific URL patterns. The proactive variant family is tuned via the Proactive Variant Generation flags. (1.1 equivalent: rewrite_images , convert_jpeg_to_webp , convert_to_webp_lossless , recompress_jpeg , recompress_png , recompress_webp , resize_images , resize_rendered_image_dimensions , responsive_images , jpeg_sampling , strip_image_meta_data .)

Image dimensions

Adds width and height attributes to <img> tags that are missing them, reserving layout space before the image loads and improving CLS. Dimensions are read from the worker’s cached image headers. Toggleable via --no-image-dimensions . When browser analysis is enabled, rendered dimensions from headless Chrome take precedence over decoded pixel dimensions. (1.1 equivalent: insert_image_dimensions .)

Lazy load images

Adds loading="lazy" to images and iframes that are below the predicted fold. The LCP candidate is excluded (it gets fetchpriority="high" instead) and the first three body images are skipped when an LCP candidate is identified, to avoid demoting above-the-fold imagery. Toggleable via --no-lazy-load-images . (1.1 equivalent: lazyload_images .)

LCP preload

Injects <link rel="preload" as="image" fetchpriority="high"> in <head> for the predicted LCP image and writes the URL to the Early Hints cache sentinel, so nginx can emit 103 Early Hints or Link response headers on subsequent requests. Toggleable via --no-lcp-preload . (1.1 equivalent: hint_preload_subresources .)

Preconnect injection

Detects third-party origins referenced from external resources in the HTML and writes preconnect: hints to the Early Hints sentinel, so browsers can open TCP and TLS connections in parallel with the HTML download. Toggleable via --no-preconnect-injection . (1.1 equivalent: insert_dns_prefetch .)

Critical CSS

Extracts the CSS rules used by above-the-fold content and inlines them in a <style> tag in <head> , so the browser can render the visible viewport without waiting for external stylesheets. Always-on under pagespeed on; (it is part of the HTML optimization pipeline disabled only by --disable-html ). When browser analysis is enabled the critical set is derived from the CSS Coverage API at three viewport sizes; otherwise a heuristic computes it from cached stylesheet structure. Used together with async CSS so the remaining stylesheets stop blocking render. (1.1 equivalent: prioritize_critical_css .)

Async CSS

Adds fetchpriority="low" to render-blocking <link rel="stylesheet"> tags so the browser deprioritizes their network fetch, while leaving them as normal stylesheets (no JavaScript required for application). Activates only when critical CSS has been injected and the policy engine determines it is beneficial based on measured CSS coverage. Toggleable via --no-async-css . (1.1 equivalent: move_css_to_head , move_css_above_scripts .)

CSS import flattening

Inlines @import chains so the browser does not have to discover and fetch each stylesheet sequentially. Flattening happens during CSS processing and the resulting stylesheet is cached and served as a single resource. Toggleable via --no-css-import-flattening . (1.1 equivalent: flatten_css_imports .)

Script deferral

Adds the defer attribute to <script src="..."> tags that script coverage analysis has identified as safe to defer, so they execute after HTML parsing instead of blocking it. Scripts already marked async , defer , or type="module" are left unchanged. Toggleable via --no-script-deferral . (1.1 equivalent: defer_javascript .)

CSS minification

Parses CSS and emits a minified form (whitespace and comment removal, shorthand collapsing where safe). The output is content-hashed and served via cache extension . Always-on under pagespeed on; ; use --disable-css to skip CSS rewriting entirely. (1.1 equivalent: rewrite_css .)

JS minification

Parses JavaScript and emits a minified form (whitespace, identifier shortening within safe scopes, dead-code removal where statically provable). The output is content-hashed and served via cache extension . Always-on under pagespeed on; ; use --disable-js to skip JS rewriting entirely. (1.1 equivalent: rewrite_javascript .)

Cache extension

Rewrites references to static assets (CSS, JS, images, fonts) so they point at content-hashed URLs the module serves with a long Cache-Control: max-age . Because the URL changes when the byte content changes, the long TTL is safe: a new deploy invalidates the old URL by producing a new hash, and there is no need to purge intermediate caches. Always-on under pagespeed on; . The TTL cap is controlled by pagespeed_max_age and the per-type pagespeed_*_max_age directives. (1.1 equivalent: extend_cache , extend_cache_css , extend_cache_images , extend_cache_scripts , extend_cache_pdfs .)

HTML caching

Caches HTML responses in the Cyclone shared-memory cache and serves them zero-copy from the memory-mapped file on cache HIT. This masks slow origin TTFB on repeat visits. The first request to a URL still hits origin, and the origin’s Cache-Control headers decide whether HTML is eligible. no-store is always respected (the response is not cached at all). For origins that send no Cache-Control on HTML, set pagespeed_html_max_age N; to opt that origin in; for origins that do send Cache-Control: public, max-age=N , HTML caching honours the origin’s max-age (capped by pagespeed_max_age ).

The Cache-Control header on the optimized HTML response is always no-cache regardless of cache mode, so downstream browsers and CDNs revalidate on every navigation. Conditional revalidation ( pagespeed_conditional_revalidation on , the default) keeps that cheap by answering with 304 Not Modified when the cached body still matches. HTML caching is unique to 2.0; 1.1 always passes HTML through to the origin and rewrites in flight.

Browser Analysis

Browser analysis uses headless Chrome to generate per-template optimization profiles. It replaces heuristic-based critical CSS extraction with browser-validated results from the CSS Coverage API, and produces accurate above-the-fold detection, LCP identification, and image dimension data.

Browser analysis is disabled by default. Enable it with --enable-browser-analysis . Chrome (or chrome-headless-shell ) must be available at the configured binary path.

Browser Analysis Flags

Flag Default Description
--enable-browser-analysis
(off) Enable headless Chrome analysis
--chrome-binary
/usr/bin/chrome-headless-shell Path to Chrome or chrome-headless-shell binary
--chrome-recycle-interval
100 Pages processed before recycling Chrome (memory control)
--chrome-page-timeout
60000 Per-page CDP timeout in milliseconds
--chrome-max-memory
512 Chrome RSS threshold in MB (auto-kill above this)
--chrome-startup-timeout
10000 Chrome startup timeout in milliseconds
--no-browser-critical-css
(enabled) Disable browser-validated critical CSS (use heuristic)
--no-browser-lazy-loading
(enabled) Disable browser fold detection for lazy loading
--no-browser-lcp-preload
(enabled) Disable browser-based LCP detection
--no-browser-image-sizing
(enabled) Disable browser-based image dimension detection
--no-browser-script-analysis
(enabled) Disable browser-based script coverage analysis
--browser-queue-size
1000 Maximum pending analysis queue items
--browser-profile-ttl
86400 Profile cache expiry in seconds (24 hours default)

How It Works

When browser analysis is enabled, the worker spawns headless Chrome with --remote-debugging-pipe and communicates over CDP (Chrome DevTools Protocol). For each unique HTML template (identified by DOM structure hash), the worker:

  1. Inlines cached stylesheets into the HTML (so Chrome can compute real CSS coverage)
  2. Runs CSS Coverage API at three viewport sizes (Mobile 375x667, Tablet 768x1024, Desktop 1440x900)
  3. Extracts above-the-fold detection, LCP candidates, and rendered image dimensions
  4. Stores the result as an optimization profile in the cache

Subsequent HTML processing for pages matching the same template structure uses the cached profile instead of heuristics. Profiles expire after --browser-profile-ttl seconds.

Chrome Memory Management

Chrome is recycled after --chrome-recycle-interval pages to prevent memory growth. On Linux, the worker monitors Chrome’s RSS via /proc/pid/status and kills the process if it exceeds --chrome-max-memory MB. On other platforms, RSS monitoring is not available and only page-count recycling applies.

Fallback Behavior

All browser analysis failures fall back to the heuristic path. If Chrome fails to start, crashes, or times out, the worker logs the error and continues with heuristic-based optimization. Browser analysis is strictly additive — it never blocks or degrades the base optimization pipeline.

Example

   # Enable browser analysis with a custom Chrome path 
 
  factory_worker 
 --cache-path 
 /data/cache.vol 
 \ 
 
  --enable-browser-analysis 
 \ 
 
  --chrome-binary 
 /usr/bin/chromium 
 \ 
 
  --chrome-max-memory 
 768 
 \ 
 
  --chrome-page-timeout 
 30000 
 
 

License Key

ModPageSpeed 2.0 uses Ed25519-signed license tokens for activation. The license is enforced — without a valid key the worker skips optimization and nginx passes traffic through to the origin unmodified. The web server and admin endpoints keep running. The binding legal agreement is the commercial subscription, set out in the Terms of Service .

Flag Default Description
--license-key
(none) License token (or set PAGESPEED_LICENSE_KEY env)
--license-renewal-url
(none) Renewal service URL (or set PAGESPEED_LICENSE_RENEWAL_URL env)

The license key is a base64url-encoded token containing an Ed25519 signature and a JSON payload with subscription ID, expiry, and licensed server count. The worker validates the signature on startup and logs the result.

When --license-renewal-url is configured with a subscription-based (v2) token, the worker checks expiry every 12 hours and automatically renews the license when within 14 days of expiration.

The license key is configured on the worker and shared with nginx automatically via pagespeed-shared.conf .

HTTP Management API

The worker embeds an HTTP/1.1 server for programmatic access and the web console. Enable it with --api-port .

Flag Default Description
--api-port
0 HTTP API listen port (0 = disabled)
--api-bind
127.0.0.1 Bind address for the HTTP API
--api-token
(none) Bearer token for auth (or PAGESPEED_API_TOKEN )
--api-read-open
false Allow unauthenticated GET + WebSocket access
--console-dir
(none) Path to the web console SPA directory

When --api-token is set, all endpoints except /v1/health require an Authorization: Bearer <token> header. Add --api-read-open to allow unauthenticated read access (GET requests and WebSocket streams) while keeping mutating operations behind the token. This is useful for exposing the web console as a public demo dashboard.

The --console-dir flag serves the workbench web console at /console/* .

Example

   factory_worker 
 --cache-path 
 /data/cache.vol 
 \ 
 
  --api-port 
 9880 
 --api-bind 
 0.0.0.0 
 \ 
 
  --api-token 
 "your-secret-token" 
 \ 
 
  --console-dir 
 /opt/pagespeed/console 
 
 

Threading

Flag Default Description
--num-threads
auto Thread pool size for notification processing (2-128)
--ram-cache-size
auto RAM cache size in bytes (default: 6% of cache-size, 16-256 MB)

The default thread count is based on available CPU cores. The RAM cache uses write-around semantics: writes go directly to disk, reads populate the RAM cache on miss. This prevents cross-process staling between nginx and the worker.

Pre-Compressed Text Variants

The worker produces gzip and brotli pre-compressed alternates for all text resources (HTML, CSS, JS) after optimization. This eliminates dynamic compression CPU cost on cache HITs — nginx serves the pre-compressed alternate directly with the correct Content-Encoding header.

Bits 6-7 of the capability mask encode the transfer encoding:

Value Encoding Description
00
Identity Uncompressed (fallback)
01
Gzip Pre-compressed with gzip
10
Brotli Pre-compressed with brotli
11
Reserved Reserved for future use

The default capability mask is 0x08 (Desktop, Identity encoding).

Flag Default Range Description
--gzip-level
6 1-9 Gzip compression level
--brotli-level
6 1-11 Brotli compression level

Images are always stored with identity encoding (compressed image formats do not benefit from additional gzip/brotli compression).

Example

   factory_worker 
 --cache-path 
 /data/cache.vol 
 \ 
 
  --gzip-level 
 6 
 --brotli-level 
 6 
 
 

SVG Auto-Vectorization

Format bits 11 (originally reserved for JPEG XL) are now used for SVG auto-vectorization. Chrome dropped JXL support in Chromium 110 (February 2023); the slot was repurposed because the worker emits SVG but never JXL.

When the worker processes an image notification, it evaluates the raster source for SVG candidacy before running the standard raster transcode loop. Suitable images (simple graphics, logos, icons) are vectorized using VTracer, a Rust-based raster-to-SVG engine integrated via FFI. A size gate ensures the resulting SVG is smaller than the raster original; if it is not, the SVG variant is discarded.

SVG variants are resolution-independent — a single variant serves all viewport classes and pixel densities. The worker stores the SVG at a fixed capability mask ( kSvg , Desktop, 1x, Save-Data off, Identity encoding) and the cache selector gives SVG a universal format bonus during alternate selection.

If a browser sends image/jxl in the Accept header, it maps to Original format (no special handling). The JXL format slot in the capability mask is fully repurposed for SVG and is never set from client headers.

SVG Operational Modes

The --svg-mode flag controls how far the SVG pipeline runs:

Mode Behavior
detect Evaluate candidacy and log scores. No vectorization. (default)
preview Evaluate, vectorize, and store SVG. Not served to clients.
auto Full pipeline: evaluate, vectorize, store, and serve.

Start with detect to see which images qualify, then move to preview for inspection, and finally auto for production serving.

SVG Flags

Flag Default Range Description
--svg-mode
detect see above Operational mode
--svg-candidacy-threshold
50 0-100 Minimum candidacy score for vectorization
--svg-max-pixels
65536 1-16M Max decoded pixel count (width x height)
--svg-max-paths
500 1-100000 Max <path> elements in output SVG
--svg-max-svg-bytes
262144 1K-16M Max uncompressed SVG output size in bytes
--svg-fidelity-threshold
55.0 0-100 Minimum SSIMULACRA2 fidelity score
--svg-exclude-lcp
true bool Skip vectorization for LCP candidate images
--svg-timeout-ms
500 10-60000 Vectorization timeout per image
--svg-preset
1 0-2 VTracer preset (0=bw, 1=poster, 2=photo)
--svg-color-precision
0 0-8 Color quantization (0=adaptive, 1-8=fixed bit depth)
--svg-filter-speckle
4 0-1000 Minimum cluster area in pixels (noise filter)

LCP exclusion ( --svg-exclude-lcp true ) is enabled by default because SVG render cost (path tessellation in the browser) can regress LCP timing for hero images. Override with --svg-exclude-lcp false if your LCP images are simple graphics that benefit from vectorization.

All SVG settings are hot-reloadable via PATCH /v1/config on the HTTP API.

Example

   # Enable full SVG pipeline with stricter candidacy 
 
  factory_worker 
 --cache-path 
 /data/cache.vol 
 \ 
 
  --svg-mode 
 auto 
 --svg-candidacy-threshold 
 65 
 --svg-max-paths 
 300 
 
 

Management Socket

The worker exposes a management socket at {socket_path}.mgmt (e.g., /var/lib/pagespeed/pagespeed.sock.mgmt ). This socket accepts newline-terminated text commands and returns a response before closing the connection.

Connecting to the Management Socket

   # Using socat 
 
  echo 
 "STATS" 
 | 
 socat 
 - 
 UNIX-CONNECT:/var/lib/pagespeed/pagespeed.sock.mgmt 
 
  
  # Using Python 
 
  python3 
 -c 
 " 
 
  import socket 
 
  s = socket.socket(socket.AF_UNIX) 
 
  s.connect('/var/lib/pagespeed/pagespeed.sock.mgmt') 
 
  s.sendall(b'STATS\n') 
 
  print(s.recv(4096).decode()) 
 
  s.close() 
 
  " 
 
 

STATS Command

Returns a JSON object with worker statistics:

   { 
 
  "status" 
 : 
 "ok" 
 , 
 
  "connections" 
 : { 
 "active" 
 : 
 2 
 , 
 "max" 
 : 
 128 
 }, 
 
  "notifications" 
 : { 
 "received" 
 : 
 1542 
 , 
 "skipped_dedup" 
 : 
 380 
 }, 
 
  "variants" 
 : { 
 "written" 
 : 
 986 
 , 
 "proactive" 
 : 
 724 
 }, 
 
  "errors" 
 : 
 3 
 , 
 
  "cache" 
 : { 
 "entries" 
 : 
 2048 
 , 
 "size" 
 : 
 134217728 
 }, 
 
  "by_type" 
 : { 
 
  "html" 
 : { 
 "n" 
 : 
 45 
 , 
 "us" 
 : 
 120000 
 }, 
 
  "css" 
 : { 
 "n" 
 : 
 112 
 , 
 "us" 
 : 
 85000 
 }, 
 
  "js" 
 : { 
 "n" 
 : 
 98 
 , 
 "us" 
 : 
 72000 
 }, 
 
  "images" 
 : { 
 "n" 
 : 
 731 
 , 
 "us" 
 : 
 9500000 
 } 
 
  }, 
 
  "by_format" 
 : { 
 "webp" 
 : 
 320 
 , 
 "avif" 
 : 
 280 
 , 
 "jpeg" 
 : 
 85 
 , 
 "png" 
 : 
 46 
 }, 
 
  "timing_us" 
 : { 
 "total" 
 : 
 9777000 
 } 
 
  } 
 
 

The us fields are cumulative processing time in microseconds. Divide by the count ( n ) to get the average processing time per item.

PURGE Command

Invalidates all cached variants for a URL. The command format is PURGE followed by the hostname and URL:

   echo 
 "PURGE example.com /images/photo.jpg" 
 | 
 socat 
 - 
 UNIX-CONNECT:/var/lib/pagespeed/pagespeed.sock.mgmt 
 
 

Response: OK N entries deleted\n where N is the number of cache entries removed. The PURGE command iterates all possible capability mask combinations for the URL and deletes every matching entry, including sentinel entries used for Early Hints and warmup.

After a PURGE, the next request for that URL will be a cache miss. Nginx will proxy to the origin, re-cache the response, and send a new notification to the worker.

Cache Invalidation

The PURGE command is the primary mechanism for cache invalidation. Common use cases:

  • Content update:When you deploy new CSS, JS, or images, purge the affected URLs so the worker re-optimizes from the updated originals.
  • Debugging:Purge a URL to force a fresh optimization pass and observe the worker’s processing logs.
  • Selective cache clearing:Unlike restarting the worker or deleting the cache file (which loses everything), PURGE targets individual URLs.

For bulk invalidation, send multiple PURGE commands. Each command opens a new connection to the management socket:

   for 
 url 
 in 
 /style.css 
 /app.js 
 /hero.jpg 
 ; 
 do 
 
  echo 
 "PURGE example.com 
 $url 
 " 
 | 
 socat 
 - 
 UNIX-CONNECT:/var/lib/pagespeed/pagespeed.sock.mgmt 
 
  done 
 
 

Capability Mask

ModPageSpeed classifies each request into a 32-bit capability mask based on the client’s capabilities. This mask is used as part of the cache key, allowing different optimized variants to be served to different clients.

Bitmask Layout

Bits Field Values
0-1
Image Format 00 Original, 01 WebP, 10 AVIF, 11 SVG
2-3
Viewport Class 00 Mobile, 01 Tablet, 10 Desktop
4
Pixel Density 0 1x, 1 2x+ (Retina)
5
Save-Data 0 off, 1 on
6-7
Transfer Enc. 00 Identity, 01 Gzip, 10 Brotli, 11 Reserved

How Classification Works

The nginx module determines these values from request headers:

  • Image Format:Parsed from the Accept header. If the client advertises image/webp , WebP variants are preferred. Similarly for image/avif .
  • Viewport Class:Derived from the Sec-CH-Viewport-Width client hint or User-Agent heuristics (mobile vs. desktop).
  • Pixel Density:From Sec-CH-DPR client hint or device heuristics.
  • Save-Data:From the Save-Data: on request header.
  • Transfer Encoding:From the Accept-Encoding header (br > gzip > identity).

Cache Key Format

Variants are stored as alternates within a SHA-256(URL, hostname) cache key. The capability mask determines which alternate a client receives. For example, a WebP-capable desktop client with brotli requesting /style.css gets a different variant than a mobile client with gzip requesting the same URL.

Variant Fallback

When an exact match isn’t found in the cache, nginx tries progressively degraded fallback masks before falling back to mask 0x08 (the default for original content stored at Desktop/Identity). This means a client always gets a response — either the optimized variant, a close variant, or the original.

Content Types

The worker processes these content types when notified by nginx:

Type Optimizations Applied
Images Format transcoding (JPEG/PNG/GIF to WebP/AVIF), lossless PNG reduction, quality-aware compression
CSS Minification (whitespace, comments, redundant syntax removal)
JavaScript Minification (whitespace, comments)
HTML Critical CSS extraction and inline injection

The worker only writes an optimized variant if it is smaller than the original. If minification doesn’t reduce the size, the original is kept and no variant is written.

Cross-Process Cache Sharing

Both nginx and the worker access the same Cyclone cache file. For this to work correctly:

  1. Same file path pagespeed_cache_path and --cache-path must point to the same file.
  2. File permissions— The cache file must be chmod 666 so both processes can read and write. The parent directory should be chmod 777 .
  3. Memory-mapped directory— Both processes open the cache with mmap directory sharing enabled (this is automatic). Without it, each process would have its own in-memory directory and writes would be invisible to the other.

Example: Complete Configuration

nginx.conf

   worker_processes 
 auto; 
 
  error_log 
 /var/log/nginx/error.log 
 warn 
 ; 
 
  pid 
 /var/run/nginx.pid; 
 
  
  load_module 
 /usr/lib/nginx/modules/ngx_pagespeed_module.so; 
 
  
  events 
 { 
 
  worker_connections 
 1024 
 ; 
 
  } 
 
  
  http 
 { 
 
  include 
 /etc/nginx/mime.types; 
 
  default_type 
 application/octet-stream; 
 
  
  server 
 { 
 
  listen 
 80 
 ; 
 
  server_name 
 example.com; 
 
  
  pagespeed 
 on 
 ; 
 
  pagespeed_cache_path 
 /var/lib/pagespeed/cache.vol; 
 
  # Worker socket, license key, and HTML toggle are read 
 
  # automatically from pagespeed-shared.conf 
 
  
  location 
 / 
 { 
 
  proxy_pass 
 http://127.0.0.1:8081; 
 
  proxy_set_header 
 Host $host; 
 
  proxy_set_header 
 X-Real-IP $remote_addr; 
 
  proxy_set_header 
 X-Forwarded-For $proxy_add_x_forwarded_for; 
 
  } 
 
  } 
 
  } 
 
 

Worker systemd unit

   [Unit] 
 
  Description 
 =ModPageSpeed Factory Worker 
 
  After 
 =network.target 
 
  
  [Service] 
 
  Type 
 =simple 
 
  ExecStart 
 =/usr/local/bin/factory_worker \ 
 
  --socket /var/lib/pagespeed/pagespeed.sock \ 
 
  --cache-path /var/lib/pagespeed/cache.vol \ 
 
  --cache-size 536870912 \ 
 
  --log-format json \ 
 
  --log-level info 
 
  UMask 
 =0000 
 
  Restart 
 =on-failure 
 
  RestartSec 
 =5 
 
  
  [Install] 
 
  WantedBy 
 =multi-user.target 
 
 

Response Headers

ModPageSpeed adds the following response header:

Header Value Meaning
X-PageSpeed
HIT Content served from cache (may be original or optimized)
X-PageSpeed
MISS Cache miss — proxied to origin, response cached

A HIT response may contain original content if the worker hasn’t processed it yet. The worker runs asynchronously, so there’s a brief window after the first request where the cache contains the original. Subsequent requests will get the optimized version once the worker has written it.

Sizing the Cache

The cache size depends on your site’s content:

Site Type Recommended Cache Size
Small blog / portfolio 256 MB - 1 GB
Medium site (1,000 pages) 1 - 2 GB
Large site (10,000+ pages) 2 - 4 GB
Image-heavy site 4 - 8 GB

The --cache-size default is 1 GB, which suits most small and medium sites.

The cache stores both original and optimized variants. Image-heavy sites need more space because each image may have multiple variants (WebP, AVIF, different viewport sizes, Save-Data quality levels, and pixel density variants).

With all proactive variant generation enabled, a single image can produce up to 37 distinct optimized variants (36 raster plus 1 SVG for eligible images). The HTTP API’s PURGE command iterates a larger capability-mask space (up to 192 combinations, since several masks — including the transfer-encoding axis — map to the same stored entry); the 37 figure counts the distinct cached outputs. Size the cache generously for image-heavy sites.

Monitor cache effectiveness by checking the ratio of HIT to MISS responses in your nginx access logs, or use the management socket STATS command to inspect cache entry counts and size.

Disabling PageSpeed Per-Location

You can enable PageSpeed at the server level and disable it for specific paths:

   server 
 { 
 
  pagespeed 
 on 
 ; 
 
  pagespeed_cache_path 
 /var/lib/pagespeed/cache.vol; 
 
  
  location 
 / 
 { 
 
  proxy_pass 
 http://127.0.0.1:8081; 
 
  } 
 
  
  # Don't optimize API responses 
 
  location 
 /api/ 
 { 
 
  pagespeed 
 off 
 ; 
 
  proxy_pass 
 http://127.0.0.1:8081; 
 
  } 
 
  
  # Don't optimize admin panel 
 
  location 
 /admin/ 
 { 
 
  pagespeed 
 off 
 ; 
 
  proxy_pass 
 http://127.0.0.1:8081; 
 
  } 
 
  } 
 
 

URL Pattern Exclusions

The pagespeed_disallow directive excludes URL patterns from optimization. Unlike pagespeed off (which disables the entire module for a location block), pagespeed_disallow selectively skips individual URL patterns while keeping the module active.

   server 
 { 
 
  pagespeed 
 on 
 ; 
 
  pagespeed_cache_path 
 /var/lib/pagespeed/cache.vol; 
 
  
  # Skip font files 
 
  pagespeed_disallow 
 *.woff2; 
 
  pagespeed_disallow 
 *.woff; 
 
  
  # Skip API endpoints 
 
  pagespeed_disallow 
 /api/; 
 
  
  # Skip admin panel 
 
  pagespeed_disallow 
 /admin/; 
 
  
  location 
 / 
 { 
 
  proxy_pass 
 http://127.0.0.1:8081; 
 
  } 
 
  } 
 
 

Context: http , server , location

Pattern matching:

  • Patterns starting with / match URL prefixes: /api/ matches /api/v1/users
  • Patterns starting with * match URL suffixes: *.woff2 matches /fonts/main.woff2
  • Other patterns match as substrings: admin matches /site/admin/panel

Multiple pagespeed_disallow directives can be specified. They are checked in order; the first match causes the request to bypass PageSpeed.

Notification Deduplication

The worker automatically skips processing when the target cache variant already exists. This prevents redundant work when multiple requests arrive for the same URL before the worker has finished optimizing.

For example, if 10 requests for /photo.jpg with WebP capability arrive in quick succession, only the first notification triggers image transcoding. The remaining 9 are skipped because the WebP variant is already in the cache.

Health Check

The worker exposes a health check socket at {socket_path}.health . Connect to it to get the current status:

   python3 
 -c 
 " 
 
  import socket 
 
  s = socket.socket(socket.AF_UNIX) 
 
  s.connect('/var/lib/pagespeed/pagespeed.sock.health') 
 
  print(s.recv(256).decode()) 
 
  s.close() 
 
  " 
 
 

Response format: OK {active}/{max} notifs=N variants=N proactive=N errors=N cache_entries=N\n

Example: OK 5/128 notifs=1542 variants=986 proactive=724 errors=3 cache_entries=2048