Troubleshoot common issues
Fix common mod_pagespeed 2.1 issues: cache misses, images not converting to WebP or AVIF, the optimizer worker not processing, and socket diagnostics.
On this page
Most mod_pagespeed 2.1 problems surface in one place: the X-PageSpeed
response header. HIT means the worker served a cached response (normally an
optimized variant); MISS means it served the original. Read that header first,
then jump to the symptom
below that matches what you see. Flags referenced throughout are documented in
the configuration reference.
Cache Miss on Every Request
Symptom: Every response has X-PageSpeed: MISS, even for URLs that have
been requested before.
Possible causes:
-
Cache file not shared. The nginx module and worker must use the same cache file path. Verify that
pagespeed_cache_pathin your nginx config matches the worker’s cache (--cache-dir/--cache-path).After upgrading to 2.1 this is the first thing to check. The worker’s default paths moved to
/var/cache/pagespeed-optimizer/v1/cacheand/run/pagespeed-optimizer/notify.sock, but your web-server configuration keeps whatever it was set to. A configuration still naming the old/var/lib/pagespeed-optimizer/...paths makes the module attach to the abandoned cache and report the socket as absent — “start the optimizer worker” — while the optimizer worker is running perfectly well somewhere else. Repoint the directives and restart the web server.# Check nginx config nginx -T 2>/dev/null | grep pagespeed_cache_path # Check worker process ps aux | grep factory_worker -
Permissions. The cache directory and every file in it must be owned by the worker’s user (
pagespeedin the packaged install), mode 0660 owner+group — and the nginx worker user must be in grouppagespeed. Check:ls -la /var/cache/pagespeed-optimizer/v1/ id www-data # (or apache / nginx) — must list "pagespeed"Fix with (the module package’s postinst normally does the join):
sudo usermod -a -G pagespeed www-data sudo systemctl restart nginx # group membership applies on restart -
Memory-mapped directory not enabled. Both processes must open the cache with mmap directory sharing. This is automatic in the worker and nginx module, but if you see persistent misses, the processes may have separate in-memory directories. Restart both to re-sync:
sudo systemctl restart pagespeed-worker sudo systemctl reload nginx -
Cache size too small. If the cache is full, LRU eviction removes older entries before they can be served. Check cache utilization via the management socket:
echo "STATS" | socat - UNIX-CONNECT:/var/lib/pagespeed/pagespeed.sock.mgmtLook at
cache.sizerelative to your--cache-sizesetting. If they are close, increase the cache size.
X-PageSpeed: MISS on Every Reload in Chrome
Symptom: In Chrome you keep seeing X-PageSpeed: MISS and the original
image on every reload, and you are not sure optimization is working.
Cause: Almost certainly nothing is wrong. Chrome DevTools open with
Disable cache ticked, or a hard reload (Cmd/Ctrl+Shift+R), both make the
browser send Cache-Control: no-cache on every request. mod_pagespeed honors
that by revalidating and serving the unmodified origin response
(X-PageSpeed: MISS) instead of a cached optimized variant. Real visitors do
not send no-cache, so they always get the optimized response.
Fix: Untick Disable cache (or do a normal reload) and the same image URL
returns the optimized variant with X-PageSpeed: HIT and a smaller
image/webp or image/avif body. From the command line, request without the
no-cache header to see the variant directly:
curl -sI http://localhost:5050/hero.jpg -H 'Accept: image/webp'
Worker Not Processing Content
Symptom: The cache has original content (X-PageSpeed: HIT) but images
are not transcoded, CSS/JS are not minified, and no optimized variants appear.
Possible causes:
-
Worker not running. Verify the worker process is active:
# systemd sudo systemctl status pagespeed-worker # Docker docker compose ps worker -
Socket path mismatch. The worker writes its socket path to
pagespeed-shared.conf(next to the cache file), which nginx reads automatically. Verify the shared config file exists and contains the correct socket path:# Check the shared config and socket file cat /var/cache/pagespeed-optimizer/v1/pagespeed-shared.conf ls -la /run/pagespeed-optimizer/notify.sock -
Socket permissions. If the socket exists but nginx cannot connect, the nginx worker user is almost certainly not in group
pagespeed(the socket is 0660pagespeed:pagespeedby design — never world-writable). The worker’s log distinguishes “permission denied” from “socket absent”; the fix is the group join plus a web-server restart. -
Content type disabled. The worker may have processing disabled for specific content types. Check for
--disable-html,--disable-css,--disable-js, or--disable-imageflags in the worker’s startup command. -
Content too large. The worker silently skips content exceeding size limits. Check worker logs for “too large” warnings:
sudo journalctl -u pagespeed-worker | grep "too large"Increase limits with
--max-html-size,--max-css-size,--max-js-size, or--max-image-sizeas needed.
ASP.NET Core Dashboard Shows Zeros
Symptom: The Dashboard shows zeros and nothing moves in DevTools when using the ASP.NET Core middleware.
Cause: Almost always worker coordination. On ASP.NET Core releases before
2.0.14, Worker.SocketPath could default to a value that left the middleware
and worker unconnected, so no optimized variants were built and the Dashboard
stayed at zero. From 2.0.14 the default is an auto-resolved per-process socket
with coordination on, so the minimal AddPageSpeed() + UsePageSpeed() setup
works with no Worker config.
Fix: Upgrade to 2.0.14+ and confirm you have not set Worker.SocketPath to
null or "" (either disables coordination and logs a startup warning) or
Worker.AutoStart to false.
One thing that is not a fault: unlike
the 1.x .pagespeed. URL scheme, 2.0 does not
rewrite URLs into those links — the HTML stays clean and the original URL serves
optimized bytes through content negotiation, so you will not see new asset URLs in the
page source. To verify it is working, request a content route and check for
X-PageSpeed: HIT:
curl -i http://localhost:5050/
Then confirm transcoding on the same image URL — the following should return a
much smaller Content-Type: image/webp response with Vary: Accept:
curl -sI http://localhost:5050/hero.jpg -H 'Accept: image/webp'
A full walkthrough is in the getting-started guide.
Images Not Converting to WebP/AVIF
Symptom: Image requests with Accept: image/webp still return the original
JPEG or PNG.
Possible causes:
-
Worker has not processed yet. After the first request (cache miss), the worker optimizes asynchronously. The first few requests may serve the original. Wait a moment and request again.
-
Image too large. Images larger than
--max-image-size(default 10 MB) are skipped. Check worker logs:sudo journalctl -u pagespeed-worker | grep "Image too large" -
Image transcoding disabled. Verify
--disable-imageis not set. -
Transcoding failed. Some images (corrupted, unusual color profiles, very large dimensions) may fail to transcode. Check for error messages:
sudo journalctl -u pagespeed-worker | grep -i "transcode\|failed\|error" -
Decoded pixel buffer too large. The worker enforces a 50 MB limit on decoded pixel buffers to prevent out-of-memory conditions. A 10000x10000 RGBA image decodes to ~400 MB and will be rejected. There is no configuration override for this limit.
-
Proactive variants not enabled. Without
--proactive-image-variants, the worker only produces the single format matching the notification mask. WebP variants are only created when a WebP-capable client triggers the first notification.
Debug Logging
Enable debug-level logging to see detailed processing information:
# systemd: edit the service file
sudo systemctl edit pagespeed-worker --full
# Change --log-level info to --log-level debug
# Docker: set environment variable or modify command
docker compose exec worker factory_worker --log-level debug ...
Debug logging shows:
- Every notification received (URL, content type, capability mask)
- Deduplication decisions (which notifications are skipped)
- Cache reads and writes (key, size, success/failure)
- Image decode/encode details (format, dimensions, output size)
- CSS/JS minification results (original size vs. minified size)
- HTML scanning and critical CSS extraction details
For JSON log format (recommended for parsing):
factory_worker --log-format json --log-level debug
Parse JSON logs with jq:
sudo journalctl -u pagespeed-worker -o cat | jq 'select(.level == "ERROR")'
Management Socket Diagnostics
The management socket provides real-time insight into the worker’s state.
Check Overall Health
echo "STATS" | socat - UNIX-CONNECT:/var/lib/pagespeed/pagespeed.sock.mgmt
Look for:
- High
errorscount — Processing failures. Check logs for details. cache.entriesis 0 — Cache may not be opening correctly.notifications.receivedis 0 — The worker is not receiving notifications from nginx. Check socket connectivity.variants.writtenis 0 butnotifications.receivedis high — The worker receives notifications but fails to write variants. Check for permission issues or content processing errors.
Purge and Re-test a Specific URL
To force the worker to re-process a URL:
# Purge all variants
echo "PURGE localhost /images/photo.jpg" | socat - UNIX-CONNECT:/var/lib/pagespeed/pagespeed.sock.mgmt
# Request the URL again (triggers a cache miss and new notification)
curl -H "Accept: image/webp,*/*" http://localhost/images/photo.jpg -o /dev/null -w "%{size_download}\n"
# Wait a moment for the worker to process, then request again
sleep 2
curl -H "Accept: image/webp,*/*" http://localhost/images/photo.jpg -o /dev/null -w "%{size_download}\n"
If the second request returns a smaller size, the worker is processing correctly for that URL.
Cannot Connect to Management Socket
If socat or Python fails to connect to the management socket:
# Verify the socket file exists
ls -la /var/lib/pagespeed/pagespeed.sock.mgmt
# Verify the worker is running
sudo systemctl status pagespeed-worker
# Check socket permissions
stat /var/lib/pagespeed/pagespeed.sock.mgmt
The management socket is created when the worker starts and removed when it shuts down. If the socket file does not exist, the worker is not running or failed during initialization.
Common Error Messages
”Failed to open cache at …”
The worker cannot open or create the cache file, or refused to start. The optimizer worker refuses to start (loudly, naming the cause) when its cache directory is missing, unwritable, or holds content owned by another uid — it never chowns or migrates content itself. Read the refusal line in the journal; it distinguishes “does not exist” from “permission denied” from “foreign-owned”. For the packaged layout, recreate the directory the declarative way:
sudo systemd-tmpfiles --create pagespeed-optimizer.conf
“Failed to bind to …”
The Unix socket path is already in use by another process, or a stale socket file exists from a previous crash. The worker removes stale socket files on startup, but if another instance is running, you will see this error. Stop the other instance first.
”Max connections reached”
The worker is at its connection limit. This can happen under heavy load when nginx sends many notifications simultaneously. Increase the limit:
factory_worker --max-connections 256
“Client buffer exceeded max size”
A single notification is larger than --max-buffer-size (default 1 MB). This
typically indicates a malformed message. If you have legitimate very long URLs,
increase the buffer size.
”URL too long”
The notification URL exceeds --max-url-length (default 8192 bytes). Increase
the limit if your site uses very long URLs, or consider whether the long URL
is intentional.
Browser Analysis Issues
Browser analysis requires headless Chrome and is disabled by default. Enable it
with --enable-browser-analysis. These issues only apply when browser analysis
is active.
Chrome Not Found
Symptom: Worker logs Failed to start Chrome or chrome binary not found
on startup with browser analysis enabled.
Fix: The worker looks for Chrome at the path specified by --chrome-binary
(default: /usr/bin/chrome-headless-shell). Verify the binary exists and is
executable:
ls -la /usr/bin/chrome-headless-shell
# or wherever your Chrome is installed
# If Chrome is elsewhere, specify the path:
factory_worker --enable-browser-analysis --chrome-binary /usr/bin/chromium
In Docker containers, install chrome-headless-shell or chromium. The
workbench-demo Docker image includes it by default.
CDP Connection Failures
Symptom: Worker logs CDP pipe read error or Chrome pipe EOF during
analysis. Browser profiles are not generated.
Possible causes:
-
Chrome crashed. The worker automatically restarts Chrome after a 2-second delay. Check logs for the crash reason:
sudo journalctl -u pagespeed-worker | grep -i "chrome\|crash\|exit" -
Memory limit exceeded. If Chrome’s RSS exceeds
--chrome-max-memory(default 512 MB), the worker kills and restarts it. Increase the limit for sites with large pages:factory_worker --enable-browser-analysis --chrome-max-memory 1024 -
Startup timeout. Chrome may take longer to start in resource-constrained environments. Increase
--chrome-startup-timeout(default 10000 ms):factory_worker --enable-browser-analysis --chrome-startup-timeout 20000
Analysis Timeouts
Symptom: Worker logs session timeout for browser analysis. Some pages
never get browser-validated profiles.
Fix: The per-page timeout is controlled by --chrome-page-timeout (default
60000 ms). Complex pages with many stylesheets may need more time. However,
if timeouts are frequent, the root cause is often Chrome struggling with
inlined CSS volume. Check the page’s stylesheet count and total CSS size.
# Check browser analysis status via management socket
echo "BROWSER-STATUS" | socat - UNIX-CONNECT:/var/lib/pagespeed/pagespeed.sock.mgmt
The response includes analysis_errors, chrome_crashes, and queue_depth
counters.
Browser Analysis Not Improving Results
Symptom: Browser analysis is enabled and running, but HTML output is identical to heuristic-only mode.
Possible causes:
-
Profile TTL too short. If
--browser-profile-ttlis very short, profiles expire before they are used. The default (86400 seconds / 24 hours) works for most sites. -
Template mismatch. Browser profiles are keyed by DOM structure hash. If every page has a unique structure (e.g., inline content changes the DOM tree), each page gets its own profile and re-analysis runs constantly. This is normal for highly dynamic sites but reduces the benefit.
-
Individual features disabled. Check whether
--no-browser-critical-css,--no-browser-lazy-loading,--no-browser-lcp-preload, or--no-browser-image-sizingflags are set. Each disables a specific browser analysis output.
SVG Vectorization Issues
SVG auto-vectorization converts suitable raster images (logos, icons, flat
illustrations) to SVG format. It runs in detect mode by default, which only
evaluates candidacy without producing SVG output. Set --svg-mode auto for
production serving.
No SVG Variants Produced
Symptom: The worker processes images but no SVG variants appear in the cache.
Possible causes:
-
SVG mode is
detect(the default). In detect mode, the worker evaluates images for SVG candidacy and logs scores, but does not vectorize. Set--svg-mode autoor--svg-mode previewto produce SVG output:factory_worker --cache-path /data/cache.vol --svg-mode auto -
Candidacy threshold too high. The
--svg-candidacy-threshold(default 50) filters out images with low vectorization suitability. Photos and complex textures score low and are rejected. This is by design — SVG is only beneficial for simple graphics. Lower the threshold to see more candidates:factory_worker --svg-candidacy-threshold 30 -
Images too large. The
--svg-max-pixelsflag (default 65536, about 256x256) limits which images are evaluated. Large photos are excluded because vectorization produces enormous SVGs. Increase for larger icons:factory_worker --svg-max-pixels 262144 # ~512x512 -
Image processing disabled. If
--disable-imageis set, all image processing is skipped, including SVG vectorization.
SVG Variants Larger Than Raster
Symptom: Debug logs show svg_size_rejected counter increasing. SVGs are
produced but discarded.
This is the size gate working correctly. If the vectorized SVG is larger than
the raster original (which is common for photos and complex images), the SVG
variant is discarded. The svg_bytes_saved stat shows cumulative savings for
SVGs that did pass the gate.
SVG Path Count Exceeded
Symptom: Debug logs show svg_path_count_rejected counter increasing.
Complex images produce SVGs with many <path> elements, which can slow down
browser rendering. The --svg-max-paths flag (default 500) limits the maximum
path count. If you want to allow more complex SVGs:
factory_worker --svg-max-paths 1000
Be cautious: SVGs with thousands of paths can cause rendering jank on mobile devices.
LCP Images Not Vectorized
Symptom: The LCP hero image qualifies for SVG but no SVG variant is produced.
By default, --svg-exclude-lcp true skips vectorization for images identified
as the Largest Contentful Paint candidate. SVG path tessellation in the browser
can be slower than decoding a raster image, potentially regressing LCP.
If your LCP image is a simple logo or icon that renders quickly as SVG:
factory_worker --svg-exclude-lcp false
Vary Header and Cache Poisoning
Symptom: All responses are cache misses. The cache never populates even though nginx and the worker are running correctly.
Cause: The interceptor adds a Vary: User-Agent token to mark device-aware
responses. In builds before 2.0.16, that token could be evaluated as part of
the response’s own cacheability check, causing every response to be treated as
uncacheable.
Fix: Upgrade to 2.0.16 or newer, where this is resolved — confirm with
the X-PageSpeed response header. If the symptom persists after upgrading,
contact support with your worker version and a sample response’s
headers.
Native module issues
The issues below are specific to the in-process module (Apache, nginx, IIS) rather than the Docker / nginx reverse-proxy worker.
No X-Mod-Pagespeed or X-Page-Speed header
Cause: mod_pagespeed is not running or not intercepting the response.
Fix:
- Verify the module is loaded: check
nginx -Vfor the module. - Verify
pagespeed on;is set in your server block. - Check that the response Content-Type is
text/html. mod_pagespeed only rewrites HTML responses.
- Verify the module is loaded: check
apachectl -Mforpagespeed_module. - Verify
ModPagespeed onis set. - Verify
AddOutputFilterByType MOD_PAGESPEED_OUTPUT_FILTER text/htmlis present (usually set automatically). - Check that the response Content-Type is
text/html. mod_pagespeed only rewrites HTML responses.
- Open IIS Manager and verify the module appears under Modules for your site.
- Verify
pagespeed onis set in yourpagespeed.config. - Check that the response Content-Type is
text/html. mod_pagespeed only rewrites HTML responses. - Check the Windows Event Viewer (Windows Logs > Application) for startup errors from the mod_pagespeed source.
- Verify that the IIS worker process (app pool identity) can read the
pagespeed.configfile.
Pages are never served optimized / cache never warms
Cause: Resources are being optimized but the cache is not warming.
Fix:
- Optimization is progressive: the first request to a page triggers background optimization. Wait for the rewrite to complete and retry.
- If repeated requests still serve the original resources, check cache directory permissions and disk space.
- Verify the cache path exists and is writable by the web server process.
Optimized resources return 404
Cause: The .pagespeed. URL pattern is not being routed to mod_pagespeed.
Fix:
Ensure the .pagespeed. location block is present and appears before other location blocks that might match.
If using mod_rewrite, add RewriteCond %{REQUEST_URI} !\.pagespeed\. to prevent rewrite rules from intercepting pagespeed URLs.
Check that URL Rewrite rules (if installed) do not intercept .pagespeed. URLs. Add a stop-processing rule before other rewrite rules:
<rule name="PageSpeed" stopProcessing="true">
<match url="\.pagespeed\." />
<action type="None" />
</rule>
Also verify that the IIS app pool has not been recycled mid-optimization. The module reloads its cache on app pool restart, but in-flight rewrites are lost.
Verify that the configuration options match between the HTML-serving VHost and the resource-serving VHost.
mod_pagespeed broke my page layout or JavaScript
Cause: A filter is incompatible with your site’s CSS or JavaScript.
Fix:
- Identify which filter causes the issue by adding
?PageSpeed=offto the URL to disable all optimization. - If the page works with
?PageSpeed=off, narrow down the filter by disabling them one at a time. - Common culprits:
defer_javascript— breaks scripts usingdocument.writeor expecting to run during parse.combine_javascript— can break order-dependent scripts.prioritize_critical_css— can cause flash of unstyled content if critical CSS detection is incomplete.
- Disable the offending filter with
DisableFilters.
mod_pagespeed is not picking up file changes
Cause: Resources are cached with their original TTL or mod_pagespeed’s implicit cache TTL.
Fix:
- Flush the cache:
curl 'http://yoursite.com/pagespeed_admin/cache?purge=*' - Or touch the cache.flush file (legacy method).
- If resources are loaded from disk via
LoadFromFile, changes are picked up afterLoadFromFileCacheTtlMsexpires (default: same as ImplicitCacheTtlMs, 5 minutes). - For immediate pickup, set a shorter
ImplicitCacheTtlMsor purge the specific URL.
High memory usage
Cause: mod_pagespeed caches metadata and optimized resources in memory.
Fix:
- Check
CycloneRamCacheKb. From v1.15.0+r18 the RAM tier is off by default (0); a positive value or-1(the pre-r18 default, which sizes it fromLRUCacheKbPerProcess) adds per-process RAM on top of the shared memory-mapped cache. - Reduce
DefaultSharedMemoryCacheKB(default: 51200). - If using IPRO on a site with many unique uncacheable URLs, the metadata cache can grow. Consider disabling IPRO for those URL patterns.
SELinux blocks mod_pagespeed
Cause: SELinux policy prevents the web server from writing to the cache directory.
Fix:
sudo chcon -R -t httpd_sys_content_t /var/cache/pagespeed/
Beacons causing unexpected POST requests
Cause: Filters like lazyload_images, defer_javascript, and prioritize_critical_css use a JavaScript beacon to report client-side data. The beacon sends POST requests to the beacon URL.
Fix:
- This is expected behavior, not an error.
- If you do not use beacon-dependent filters, you can disable the beacon:
pagespeed CriticalImagesBeaconEnabled false; - To suppress the
<noscript>redirect tag inserted by some filters:pagespeed SupportNoScriptEnabled false;
How to get more diagnostic information
- Check the admin page at
/pagespeed_admin/for statistics, active filters, and cache status. - Enable message history: set
MessageBufferSizeto a non-zero value, then view messages at/pagespeed_admin/message_history. - Add the
debugfilter temporarily to see detailed rewriting information in HTML comments:pagespeed EnableFilters debug; - Check nginx error logs for mod_pagespeed messages.
- Check the admin page at
/pagespeed_admin/for statistics, active filters, and cache status. - Enable message history: set
MessageBufferSizeto a non-zero value, then view messages at/pagespeed_admin/message_history. - Add the
debugfilter temporarily:ModPagespeedEnableFilters debug - Check Apache error logs for mod_pagespeed messages.
- Windows Event Viewer: Open Event Viewer and check Windows Logs > Application for warnings and errors from the mod_pagespeed source. The module logs startup issues, configuration errors, and runtime warnings here automatically.
- Admin pages: Navigate to
/pagespeed_admin/for statistics, active filters, and cache status. - Message history: Set
pagespeed MessageBufferSize 100000in your config, then view messages at/pagespeed_global_admin/message_history. Messages are collected globally from all IIS worker processes. - DebugView: Use the Sysinternals DebugView tool for real-time debug output. Enable debug logging by adding
pagespeed diagnoseto your server-levelpagespeed.config. In DebugView, enable Capture > Capture Global Win32 to see the output. - Debug filter: Add
?PageSpeedFilters=+debugto any URL to see detailed optimization decisions, timing data, and filter descriptions in HTML comments. - App pool permissions: Verify that the IIS app pool identity has read access to
pagespeed.configand write access to the cache directory. Permission errors show up in Event Viewer.
Next Steps
- Configuration Reference — All worker flags and tuning options
- API Reference — Protocol details for management socket and IPC
- HTTP API Reference — REST and WebSocket endpoints for programmatic access
- Web Console — Visual cache inspector, debug console, and real-time metrics
- Deployment Guide — Production setup and monitoring