Configuration
Configure ModPageSpeed 2.0 options for your use case.
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.
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_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 Factory 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 Factory 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
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 | 3600 | Default max-age for images when origin sends no Cache-Control |
pagespeed_synthesize_swr | on | Synthesize stale-while-revalidate on HIT responses |
pagespeed_conditional_revalidation | on | Enable conditional revalidation (If-None-Match / If-Modified-Since) |
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 textjson— 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 converts render-blocking <link rel="stylesheet"> tags to
non-blocking loads using media="print" onload="this.media='all'" with a
<noscript> fallback. 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.
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:
- Inlines cached stylesheets into the HTML (so Chrome can compute real CSS coverage)
- Runs CSS Coverage API at three viewport sizes (Mobile 375x667, Tablet 768x1024, Desktop 1440x900)
- Extracts above-the-fold detection, LCP candidates, and rendered image dimensions
- 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 system is warn-only — the software continues to function without a valid license key, but log warnings are emitted. The Business Source License (BSL 1.1) is the legal enforcement mechanism.
| 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),
and the bits are better utilized for a format the worker can actually produce.
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 Factory 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
Acceptheader. If the client advertisesimage/webp, WebP variants are preferred. Similarly forimage/avif. - Viewport Class: Derived from the
Sec-CH-Viewport-Widthclient hint orUser-Agentheuristics (mobile vs. desktop). - Pixel Density: From
Sec-CH-DPRclient hint or device heuristics. - Save-Data: From the
Save-Data: onrequest header. - Transfer Encoding: From the
Accept-Encodingheader (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 Factory 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 Factory Worker access the same Cyclone cache file. For this to work correctly:
- Same file path —
pagespeed_cache_pathand--cache-pathmust point to the same file. - File permissions — The cache file must be
chmod 666so both processes can read and write. The parent directory should bechmod 777. - 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 | 1 GB (default) |
| Medium site (1,000 pages) | 256-512 MB |
| Large site (10,000+ pages) | 1-4 GB |
| Image-heavy site | 2-8 GB |
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 variants (36 raster plus 1 SVG for eligible images). 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:*.woff2matches/fonts/main.woff2 - Other patterns match as substrings:
adminmatches/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 Factory 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 Factory 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