Skip to main content
ModPageSpeed 2.0: AVIF, WebP, and critical CSS — up to 69% less page weight on the live demo
2.0 1.1

Configure ASP.NET Core middleware

Configuration reference for ModPageSpeed ASP.NET Core middleware. appsettings.json options, hot reload, environment variables, and common patterns.

Configuration uses standard ASP.NET Core sources: appsettings.json, environment variables, or any IConfiguration provider. The middleware binds its options to the PageSpeed section via IServiceCollection.AddPageSpeed(IConfiguration).

Basic configuration

Add a PageSpeed section to your appsettings.json:

{
  "PageSpeed": {
    "Enabled": true,
    "LicenseKey": "",
    "CacheMode": "Safe",
    "Cache": {
      "VolumePath": "/var/cache/pagespeed/volume.dat",
      "VolumeSizeBytes": 1073741824
    },
    "Html": {
      "EnableCriticalCss": true,
      "EnableLazyLoad": true,
      "EnableAsyncCss": true
    },
    "Worker": {
      "AutoStart": true
      // "ApiPort": 9191  // Worker management API. Do NOT expose to the public
      // internet — there is no authentication on this port.
    }
  }
}

Leave LicenseKey empty in checked-in files; supply the actual Ed25519 token via the PageSpeed__LicenseKey environment variable, user secrets, or a secret store. See Activate your license.

Options

The middleware binds to a PageSpeedOptions instance with three nested sections: Cache, Html, and Worker. All options support hot reload via IOptionsMonitor<PageSpeedOptions>.

Top-level (PageSpeed)

OptionTypeDefaultDescription
EnabledbooltrueMaster switch. When false, the middleware passes through all responses unchanged.
CacheModeenumSafeCache-Control header strategy on cache HIT. Safe adds must-revalidate on assets; Aggressive adds public, stale-if-error. HTML always gets no-cache. See Choose a cache mode.
CssMaxAgeSecondsint300Max-age for CSS and JS cache HIT responses, in seconds.
ImageMaxAgeSecondsint1800Max-age for image cache HIT responses, in seconds.
HtmlMaxAgeSecondsint0Reserved. HTML always receives no-cache regardless of this value.
MaxResponseBufferBytesint5242880 (5 MB)Max response body size to buffer. Larger responses pass through unmodified.
MaxAssetCacheBytesint10485760 (10 MB)Max asset (image/CSS/JS) size to cache. Larger assets pass through without caching or worker notification.
ExcludePathsstring[]["/api/", "/signalr/", "/_blazor/", "/_framework/"]URL path prefixes the middleware skips entirely. Override to add your own prefixes.
LicenseKeystring?nullBase64url-encoded Ed25519 license token. Forwarded to the worker via --license-key. Supply via env var or secret store, not appsettings.json.

Cache (PageSpeed:Cache)

The cache is a single mmap’d Cyclone volume file — not a directory of fragments.

OptionTypeDefaultDescription
VolumePathstring/var/cache/pagespeed/volume.datRequired. Path to the Cyclone cache volume file (created on first run).
VolumeSizeBytesulong1073741824 (1 GiB)Volume size in bytes.
EnableChecksumbooltrueVerify checksums on cache reads.
RamCacheSizeByteslong67108864 (64 MiB)In-process RAM cache size in bytes. Set to 0 to disable.
MaxMetadataSizeByteslong8192 (8 KiB)Per-entry metadata size cap, in bytes.

HTML processing (PageSpeed:Html)

Each rewrite is a boolean toggle — no rewrite-level abstraction, no filter strings. Toggles default to “on” except EnableSpeculationRules.

OptionTypeDefaultWhat it does
EnableCriticalCssbooltrueExtract above-the-fold CSS and inline it; defer the rest.
EnableLazyLoadbooltrueAdd loading="lazy" to below-the-fold images.
EnableImageDimensionsbooltrueAdd explicit width/height to images that omit them.
EnableLcpPreloadbooltrueEmit a <link rel="preload"> hint for the detected LCP image.
EnablePreconnectbooltrueEmit <link rel="preconnect"> hints for referenced third-party origins.
EnableSpeculationRulesboolfalseEmit a speculation-rules script for in-page prefetching.
EnableAsyncCssbooltrueLoad non-critical stylesheets asynchronously with fetchpriority.
EnableScriptDeferralbooltrueDefer scripts based on browser-coverage analysis. See Browser analysis.
ViewportenumDesktopViewport class used for capability-mask classification. Mobile, Tablet, or Desktop.
MaxHtmlSizeBytesint5242880 (5 MB)Max HTML size to process. Larger documents pass through.
MaxCssSizeBytesint2097152 (2 MB)Max CSS size to process. Larger stylesheets pass through.

Worker (PageSpeed:Worker)

The middleware optimizes inline; the worker process runs out-of-process for expensive jobs (image transcoding, critical-CSS extraction) and exposes a diagnostic console.

OptionTypeDefaultDescription
AutoStartbooltrueResolve the factory_worker binary from the NuGet package and launch it as a child process on startup. Set false to launch no worker process at all.
SocketPathstring?unsetWorker-coordination socket. Leave it unset (the default) and the middleware auto-resolves a per-process socket with coordination on — no configuration needed. Set it to a concrete path to share one socket with an out-of-process worker (split-process deployments). Setting it to null or "" disables worker coordination and logs a loud startup warning.
ApiPortint0TCP port for the worker’s HTTP management API. 0 disables the API. Do not expose this port to the public internet — there is no authentication on this endpoint.
LogLevelstring?nullWorker log level: debug, info, warning, or error. null keeps the worker’s default (info).
ConsoleDirstring?nullFilesystem path to the diagnostic-console SPA assets. When null and AutoStart is true, the middleware auto-discovers a console directory next to factory_worker. Set to "" to disable.

The worker process exposes its diagnostic console out-of-process. Set Worker.ApiPort to a port reachable from your ops team — but never from the public internet — to enable it. The console SPA is served from Worker.ConsoleDir.

Worker coordination and SocketPath

For the default in-process deployment, leave the Worker section empty. With AutoStart true and SocketPath unset, the middleware launches the worker and connects to it over an auto-resolved per-process socket — image and critical-CSS optimization are on with no extra configuration.

There are two reasons to set SocketPath explicitly:

  • A concrete path points the middleware at a socket you manage, so it can talk to a worker you run as a separate process (a split-process deployment where the worker lives in its own container or unit). The path is used verbatim.
  • null or an empty string disables worker coordination entirely. The middleware still optimizes inline, but no out-of-process variants (image transcoding, critical-CSS extraction) are built, and the Dashboard stays at zero. The worker logs a startup warning so this state is never silent. Only set this if you specifically want inline-only behavior.

To run the worker as a fully separate process, pair a concrete SocketPath with AutoStart false so the middleware connects to your externally managed worker instead of launching its own.

Hot reload

Configuration changes are picked up automatically through ASP.NET Core’s IOptionsMonitor<PageSpeedOptions> pattern. Edit appsettings.json while the application is running — changes take effect on the next request. No restart required.

This includes toggling the master switch, flipping any Html.Enable* toggle, adjusting cache TTLs, and switching cache modes. Cache contents are preserved across configuration changes. Worker-process options (AutoStart, SocketPath, ApiPort) apply at the next worker (re)start.

Validation runs on startup and on every reload via IValidateOptions<PageSpeedOptions>. Negative TTLs or unknown CacheMode values cause the application to fail fast on startup, and reload errors are logged.

Environment-specific configuration

Use ASP.NET Core’s standard environment layering. Create appsettings.Development.json to override settings during development:

{
  "PageSpeed": {
    "Enabled": false
  }
}

This disables optimization in development while leaving the rest of the pipeline intact. Production uses the base appsettings.json.

Environment variables

All options can be set via environment variables using the standard ASP.NET Core double-underscore convention. Nested keys use __ as the separator:

PageSpeed__Enabled=true
PageSpeed__CacheMode=Aggressive
PageSpeed__Cache__VolumePath=/data/pagespeed/volume.dat
PageSpeed__Cache__VolumeSizeBytes=2147483648
PageSpeed__Html__EnableCriticalCss=true
PageSpeed__Html__EnableSpeculationRules=false
PageSpeed__Worker__ApiPort=0
PageSpeed__LicenseKey=...your-token...

Environment variables take precedence over appsettings.json, following ASP.NET Core’s standard configuration precedence. Supply PageSpeed__LicenseKey from a secret store rather than committing it to source.

Rewrites you control

There is no rewrite-level abstraction and no filter-name list. Every rewrite is a boolean toggle on PageSpeed:Html — see the table above. To match the behaviour of a PassThrough configuration in 1.1, set Enabled to false (or flip the individual Enable* toggles you want off). To bias toward bandwidth-only rewrites, leave image and CSS toggles on and turn off EnableCriticalCss, EnableLcpPreload, EnableSpeculationRules, and EnableAsyncCss.

Image transcoding (WebP, AVIF, resizing) and CSS/JS minification are handled by the worker process and are not gated by Html.Enable* toggles. They run for every cached asset.

Cache volume

Optimized resources are stored in a single memory-mapped Cyclone volume file defined by Cache.VolumePath. By default the volume lives at /var/cache/pagespeed/volume.dat and is sized to 1 GiB. In containers or production deployments, point it at a persistent volume:

{
  "PageSpeed": {
    "Cache": {
      "VolumePath": "/data/pagespeed/volume.dat",
      "VolumeSizeBytes": 4294967296
    }
  }
}

The parent directory must exist and be writable by the application process. The volume file is created on first run and grown to VolumeSizeBytes. Reusing the same file across restarts preserves the cache; pointing at a fresh path starts cold.

Health checks

The middleware participates in ASP.NET Core health checks. Expose the standard endpoint to surface its status:

app.MapHealthChecks("/healthz");

The check reports Healthy when the worker process is running and the license state is active. It reports Unhealthy only when the worker is unreachable. A missing, expired, or revoked license reports Degraded (HTTP 200) — the middleware keeps optimizing responses regardless of license state, and emits an X-PageSpeed-Warn: unlicensed header (plus an admin-console notice and a startup log warning) to flag the unlicensed-in-production state — so alert on the license_active field, or watch for the X-PageSpeed-Warn: unlicensed header, rather than treating a license lapse as Unhealthy. Use this endpoint for container orchestration liveness and readiness probes.

License enforcement

The middleware polls the worker’s /v1/health endpoint to check license state. A commercial license is required for production use. When the license is invalid (expired, cancelled, or missing), the middleware keeps optimizing responses, but signals the unlicensed state by adding an X-PageSpeed-Warn: unlicensed header to responses, surfacing a notice in the admin console, and writing a warning to the startup log. To detect an unlicensed-in-production state, monitor the license_active field on the health endpoint and/or watch for the X-PageSpeed-Warn: unlicensed header. When a valid license is restored, the warning signals clear on the next poll cycle.

License state changes take a few polling intervals to propagate. If the health endpoint is unreachable, the middleware keeps the last known state to avoid flapping on transient failures. See Activate your license for token provisioning and Troubleshoot common issues when the worker fails to report healthy.

Common patterns

Disable optimization for specific paths

The middleware skips any request whose path starts with a prefix in ExcludePaths. The defaults already cover /api/, /signalr/, /_blazor/, and /_framework/. Add your own:

{
  "PageSpeed": {
    "ExcludePaths": ["/api/", "/signalr/", "/_blazor/", "/_framework/", "/webhooks/", "/healthz"]
  }
}

You can also exclude paths with standard ASP.NET Core middleware ordering — register UsePageSpeed() after the routes that should bypass it, or use IApplicationBuilder.MapWhen to conditionally skip the middleware.

Bandwidth-only rewrites

Disable HTML-mutating toggles while leaving the worker’s image and CSS minification untouched:

{
  "PageSpeed": {
    "Html": {
      "EnableCriticalCss": false,
      "EnableLcpPreload": false,
      "EnableAsyncCss": false,
      "EnableSpeculationRules": false,
      "EnableScriptDeferral": false
    }
  }
}

This is the closest equivalent to 1.1’s OptimizeForBandwidth — safe for sites with strict CSP policies that disallow inlined CSS.

See also