← ASP.NET Core: applications, contracts and operations
10 / 10 · 60 MIN

Deployment, proxies and recovery

Distinguish the published artifact from the active process and preserve trust, identity and recovery boundaries across deployment.

Files, process and acceptance

dotnet publish produces files for running the application but does not establish that a server received them or that its process loaded them. A basic probe can keep responding through the previous version. Distinguish distribution, activation and functional acceptance.

Relate a trustworthy build identifier to the expected artifact and observe required behavior through the relevant route. Startup and restart supervision also needs configuration in the destination environment.

The proxy boundary

When a proxy terminates TLS and uses HTTP to the backend, the application needs trusted metadata to know the original scheme. ForwardedHeaders selects processed fields; KnownProxies identifies expected senders and ForwardLimit bounds consumed entries.

A header sent directly by a client does not become trusted because it contains an internal address. Also retain network controls preventing unintended direct ingress and audit default trust entries.

Order and original scheme

UseForwardedHeaders must precede consumers deciding by scheme or origin. If HTTPS redirection always sees HTTP after TLS termination, it can redirect an already-HTTPS client and create a loop. Processing accepted XForwardedProto before that decision corrects the premise.

Increasing client redirect limits or accepting metadata from every source does not properly address the cause. Configuration must match the actual proxy and chain.

Public path and internal path

Establish whether the proxy preserves or strips the public prefix. With /portal/version intact, UsePathBase("/portal") separates PathBase and Path before matching, so UseRouting must follow it. If the proxy already delivers /version, the application may need to restore PathBase for correct public links without removing another route segment.

Applying one configuration to different topologies can produce 404 responses or links missing the expected prefix.

Shutdown and uncertain work

StopAsync belongs to graceful shutdown. A crash can prevent that call, so the only progress record must not be saved solely there. The shutdown token and timeout guide service cooperation but do not undo a remote write.

If a response is lost after an effect, restarting can repeat already committed work. Use durable state and operation identity to reconcile the result under its contract; do not infer no effect merely from cancellation.

Identity across instances

Data Protection uses keys to protect data such as application cookies. With independent local key rings, one instance may be unable to unprotect a cookie issued by another. Before blaming a password, correlate the node change with the observed error.

Configure controlled key sharing and other required compatibility settings. Affinity can keep requests on one node but does not establish cookie validity when that node stops serving.

Recover the observable service

Reverting files can restore a code version without restoring data, remote effects or earlier configuration. Define acceptance and recovery criteria before the change: active version, public paths, identity, dependencies and functional outcome.

When a new directory exists but the old process continues running, 200 from /health is insufficient. Diagnosis must connect each piece of evidence to its producing component and version while preserving the limits of observation.

// ASP.NET Core 10 Program.cs reading example.
// Topology premise: one trusted edge at 192.0.2.10 (documentation address).
// The edge overwrites X-Forwarded-For/Proto and preserves /portal in Path.
// Production must use its real proxy address and restrict direct ingress.
using System.Net;
using Microsoft.AspNetCore.HttpOverrides;

var builder = WebApplication.CreateBuilder(args);
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedHeaders = ForwardedHeaders.XForwardedFor |
                               ForwardedHeaders.XForwardedProto;
    options.ForwardLimit = 1;
    options.KnownProxies.Add(IPAddress.Parse("192.0.2.10"));
    // Framework loopback trust defaults remain; audit them for your topology.
});
builder.Services.AddHttpsRedirection(options => options.HttpsPort = 443);
var app = builder.Build();
app.UseForwardedHeaders();
app.UseHttpsRedirection();
app.UsePathBase("/portal");
app.UseRouting();
app.MapGet("/version", () => Results.Ok(new { build = "training-r2" }));
app.Run();
IN PRACTICE

Interpret the example with one proxy and /portal preserved. A public /portal/version request arrives with that path; UsePathBase separates the base before UseRouting. XForwardedProto permits interpreting original HTTPS when the sender is accepted.

The IP is illustrative and default loopback values remain. The training-r2 constant is only an example identifier; it does not prove distribution, artifact hash or behavior of every instance.

Common pitfalls

Copying files does not necessarily activate a version. Arbitrary forwarded headers do not prove origin. Removing a prefix twice breaks routes. Graceful shutdown does not guarantee execution during a crash. Node affinity is not equivalent to sharing keys across every node.

Related topics: Hosting, middleware and request flow · Health, observability and diagnosis · Authentication and authorization

Take this idea with you

Accept the change through the actually active version, correct public path and functional outcome. Keep recovery independent of callbacks that may never occur and preserve trust configuration across instances.

Create account

Reference: Publish artifact boundary · ASP.NET Core 10.0 / .NET 10 / C# 14