# ASP.NET Core in production

Production deployment of the mod_pagespeed 2.1 ASP.NET Core middleware: configuration, health probes, multi-instance scale and zero-downtime restarts.

Canonical URL: https://modpagespeed.com/docs/production-deployment/

This page is for teams shipping the mod_pagespeed 2.1 ASP.NET Core middleware
(`WeAmp.PageSpeed.AspNetCore`) into a production ASP.NET Core deployment. If you
are still evaluating the middleware locally, start with
[Install ASP.NET Core middleware](https://modpagespeed.com/docs/aspnet-getting-started/) instead.

The current NuGet release is v2.3.0. The middleware runs
in-process in your request pipeline and, by default, launches the `factory_worker`
binary from the same package as a child process to do the expensive work — image
transcoding and critical-CSS extraction — out of process. If you need an in-process
native module for Apache, nginx or IIS instead, see [mod_pagespeed 2.1](https://modpagespeed.com/), where
the module runs in-process in the web server. The IIS package ships from the 1.15
packaging channel.

## Prerequisites

- .NET 8 or .NET 10 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.

## 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.

```bash
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:

```bash
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:

```csharp
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](https://modpagespeed.com/docs/aspnet-configuration/).

## 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 option 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](https://modpagespeed.com/docs/cache-modes/) for the full trade-off.

## Validate the deployment

After deploy, confirm the middleware is active:

```bash
curl -I https://<your-domain>/
# X-PageSpeed: WeAmp.PageSpeed/2.3.0

curl -s https://<your-domain>/healthz | jq .
# "status": "Healthy"
# entries, version
```

The `/healthz` endpoint reports `Healthy`, with the cache entry count and the
middleware version, while the cache is operational, and `Unhealthy` when the
cache cannot be opened — wire your monitoring to alert on `Unhealthy`.

The worker process exposes its own diagnostic console SPA out-of-process. The
middleware starts the worker with `--api-no-auth` on a loopback-only management
port (`PageSpeed.Worker.ApiPort`; an ephemeral port when left at `0`), so the
API answers anything on the host and is never reachable off-host — the worker
refuses a non-loopback bind without a token. Reach it from the host or through
an SSH tunnel rather than by publishing the port.

## Scale across multiple instances

mod_pagespeed 2.1 is licensed under Apache-2.0 and free to run in development
and in production, so scaling out adds no software cost. Size replica counts
for your traffic and availability needs.

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 any mid-flight HTML rewrites complete
cleanly.

For container deployments, the [Deploy to production](https://modpagespeed.com/docs/deployment/)
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

- [ASP.NET Core configuration reference](https://modpagespeed.com/docs/aspnet-configuration/) — full
  options table, hot reload, environment-specific layering.
- [License](https://modpagespeed.com/docs/license/) — Apache License 2.0, support subscriptions.
- [Choose a cache mode](https://modpagespeed.com/docs/cache-modes/) — `Safe` vs `Aggressive`
  trade-offs and CDN guidance.
- [Troubleshoot common issues](https://modpagespeed.com/docs/troubleshooting/) — diagnostic flow for
  cache and worker problems.

## Frequently asked questions

**Does scaling the ASP.NET Core middleware across multiple instances add per-server cost?**

No. mod_pagespeed 2.1 is licensed under Apache-2.0 and free to run in development and in production, so scaling out adds no software cost — size replica counts for traffic and availability.

**What does the /healthz check report?**

`Healthy`, with the cache entry count and the middleware version, while the cache is operational; `Unhealthy` when the cache cannot be opened — wire monitoring to alert on `Unhealthy`.
