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)
| Option | Type | Default | Description |
|---|---|---|---|
Enabled | bool | true | Master switch. When false, the middleware passes through all responses unchanged. |
CacheMode | enum | Safe | Cache-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. |
CssMaxAgeSeconds | int | 300 | Max-age for CSS and JS cache HIT responses, in seconds. |
ImageMaxAgeSeconds | int | 1800 | Max-age for image cache HIT responses, in seconds. |
HtmlMaxAgeSeconds | int | 0 | Reserved. HTML always receives no-cache regardless of this value. |
MaxResponseBufferBytes | int | 5242880 (5 MB) | Max response body size to buffer. Larger responses pass through unmodified. |
MaxAssetCacheBytes | int | 10485760 (10 MB) | Max asset (image/CSS/JS) size to cache. Larger assets pass through without caching or worker notification. |
ExcludePaths | string[] | ["/api/", "/signalr/", "/_blazor/", "/_framework/"] | URL path prefixes the middleware skips entirely. Override to add your own prefixes. |
LicenseKey | string? | null | Base64url-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.
| Option | Type | Default | Description |
|---|---|---|---|
VolumePath | string | /var/cache/pagespeed/volume.dat | Required. Path to the Cyclone cache volume file (created on first run). |
VolumeSizeBytes | ulong | 1073741824 (1 GiB) | Volume size in bytes. |
EnableChecksum | bool | true | Verify checksums on cache reads. |
RamCacheSizeBytes | long | 67108864 (64 MiB) | In-process RAM cache size in bytes. Set to 0 to disable. |
MaxMetadataSizeBytes | long | 8192 (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.
| Option | Type | Default | What it does |
|---|---|---|---|
EnableCriticalCss | bool | true | Extract above-the-fold CSS and inline it; defer the rest. |
EnableLazyLoad | bool | true | Add loading="lazy" to below-the-fold images. |
EnableImageDimensions | bool | true | Add explicit width/height to images that omit them. |
EnableLcpPreload | bool | true | Emit a <link rel="preload"> hint for the detected LCP image. |
EnablePreconnect | bool | true | Emit <link rel="preconnect"> hints for referenced third-party origins. |
EnableSpeculationRules | bool | false | Emit a speculation-rules script for in-page prefetching. |
EnableAsyncCss | bool | true | Load non-critical stylesheets asynchronously with fetchpriority. |
EnableScriptDeferral | bool | true | Defer scripts based on browser-coverage analysis. See Browser analysis. |
Viewport | enum | Desktop | Viewport class used for capability-mask classification. Mobile, Tablet, or Desktop. |
MaxHtmlSizeBytes | int | 5242880 (5 MB) | Max HTML size to process. Larger documents pass through. |
MaxCssSizeBytes | int | 2097152 (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.
| Option | Type | Default | Description |
|---|---|---|---|
AutoStart | bool | true | Resolve 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. |
SocketPath | string? | unset | Worker-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. |
ApiPort | int | 0 | TCP 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. |
LogLevel | string? | null | Worker log level: debug, info, warning, or error. null keeps the worker’s default (info). |
ConsoleDir | string? | null | Filesystem 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.
nullor 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
- Install ASP.NET Core middleware — installation and first setup
- Choose a cache mode —
SafevsAggressivesemantics - Activate your license — token provisioning and rotation
- Troubleshoot common issues — worker startup, cache, license
- Licensing — Commercial subscription today; BSL 1.1 source publication on the roadmap