Skip to main content
ModPageSpeed 2.0 and mod_pagespeed 1.1 — Now available
2.0 1.1

Run in production (ASP.NET Core)

Production deployment guide for the ModPageSpeed 2.0 ASP.NET Core middleware: secret management, health probes, license enforcement, multi-instance scale, and zero-downtime restarts.

This page is for teams shipping the WeAmp.PageSpeed.AspNetCore NuGet middleware into a production ASP.NET Core deployment. If you are still evaluating the middleware locally, start with Install ASP.NET Core middleware instead.

ModPageSpeed 2.0 v1.0.0 ships only as the ASP.NET Core middleware (NuGet) plus the existing Docker / Helm images. If you need a native module for nginx, Apache, or IIS, use mod_pagespeed 1.1 — same optimization pipeline, production-hardened native server modules.

Prerequisites

  • .NET 9 runtime on your production host.
  • An ASP.NET Core application that serves HTML responses.
  • A supported runtime identifier — linux-x64, linux-arm64, osx-arm64, or win-x64. The meta-package resolves the matching native asset automatically at restore time.
  • A valid license key from your FastSpring purchase confirmation or your account page on modpagespeed.com.

Install

Add the NuGet package to your project. CI restores the correct native library for the target RID — no per-platform branching in your build.

dotnet add package WeAmp.PageSpeed.AspNetCore

If your build container differs from the deployment target, restore with an explicit RID so the native asset matches the production host:

dotnet publish -c Release -r linux-x64 --self-contained false

Wire up the middleware

Register the services and add UsePageSpeed() early in the pipeline, before any middleware that writes to the response body:

using WeAmp.PageSpeed.AspNetCore;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddPageSpeed(builder.Configuration);

var app = builder.Build();
app.UsePageSpeed();
app.MapHealthChecks("/healthz");
app.MapRazorPages();
app.Run();

Bind a PageSpeed section in your configuration. For the full set of PageSpeedOptions properties and hot-reload semantics, see the ASP.NET Core configuration reference.

Manage the license key as a secret

Never check LicenseKey into source control or appsettings.json committed to your repo. Source it from your platform’s secret store and surface it through ASP.NET Core’s standard configuration providers:

# Linux systemd unit
Environment=PageSpeed__LicenseKey=eyJ...

# Kubernetes (Secret mounted as env)
env:
  - name: PageSpeed__LicenseKey
    valueFrom:
      secretKeyRef:
        name: pagespeed-license
        key: token

# Azure App Service / Key Vault reference
PageSpeed__LicenseKey=@Microsoft.KeyVault(SecretUri=https://...)

The double-underscore convention maps to PageSpeedOptions.LicenseKey per ASP.NET Core’s configuration precedence — environment variables override appsettings.json. See Activate your license for token format details and verification.

The middleware polls the worker’s /v1/health endpoint every 30 seconds to re-check license state. Renewals issued by the license service propagate to running instances within that window without a restart; expired tokens cause the middleware to fall back to pass-through automatically.

Cache backing

The middleware writes a single memory-mapped Cyclone volume file under PageSpeed.Cache.VolumePath (default: /var/cache/pagespeed/volume.dat, 1 GB). For production:

  • Set an explicit PageSpeed__Cache__VolumePath (double-underscore for nesting) on a persistent volume so the cache survives container restarts and isn’t billed against ephemeral disk. The path is a single file, pre-sized to PageSpeed__Cache__VolumeSizeBytes.
  • Ensure the application user has read/write access to the file’s parent directory.
  • Leave CacheMode at Safe for the initial rollout. Safe mode adds must-revalidate to asset responses so a misconfigured filter recovers in minutes rather than hours. Switch to Aggressive only after a stable baseline and with a CDN purge path available — see Choose a cache mode for the full trade-off.

Validate the deployment

After deploy, confirm the middleware is active and licensed:

curl -I https://your-app.example.com/
# X-PageSpeed: WeAmp.PageSpeed/2.0.0

curl -s https://your-app.example.com/healthz | jq .
# "status": "Healthy"
# entries, version, license_active: true, license_plan: "pro"

The /healthz endpoint surfaces license_active, license_plan, and the expiry timestamp from the polling service. When the license is missing or expired, the check reports Degraded (HTTP 200 with a Degraded status payload) — wire your monitoring to alert on the license_active=false condition rather than only on Unhealthy. The worker process exposes its own diagnostic console SPA out-of-process; bind PageSpeed.Worker.ApiPort to expose it on a port reachable by your ops team (not by the public internet — there is no authentication on the worker management API).

Scale across multiple instances

The license token is bound to your subscription, not to a single host. Each instance of the same application validates the same token against the worker health endpoint independently — there is no instance-level activation step. Your plan’s max_instances ceiling applies to the aggregate count reported by the license service. Stay within it when sizing replica counts; contact support before exceeding the limit so the operator can re-mint a token with the correct seat allocation.

Each instance maintains its own on-disk cache. There is no shared cache backing across replicas; each replica builds its own warm cache, which isolates cache state across instances.

Zero-downtime restarts

The middleware honors ASP.NET Core’s standard graceful-shutdown semantics. On SIGTERM, in-flight requests complete against the existing cache before the worker shuts down; new requests route to a fresh instance. Configure your orchestrator’s terminationGracePeriodSeconds (Kubernetes) or equivalent to at least 30 seconds so the license-poll cycle and any mid-flight HTML rewrites complete cleanly.

For container deployments, the Deploy to production guide covers the Docker / Helm shipping path; the systemd and Kubernetes patterns there apply equally to the NuGet middleware when wrapped in your own container image.

See also