ASP.NET Core Middleware Pipeline: The Order That Actually Matters

TL;DR Middleware order determines what works and what silently breaks. ForwardedHeaders before HTTPS, Authentication before Authorization, Routing before Rate Limiting.

Middleware order is not a suggestion. Put ForwardedHeaders after HTTPS redirection and your reverse proxy integration breaks. Put Authorization before Authentication and every request fails.

Common questions this answers

  • What is the correct middleware order in ASP.NET Core?
  • Why does ForwardedHeaders need to come first?
  • What breaks if Authentication comes after Authorization?
  • Where should OutputCache go relative to ResponseCompression?
  • How do I know where to place custom middleware?

Definition (what this means in practice)

The ASP.NET Core middleware pipeline processes requests in the order middleware is registered. Each middleware can handle the request, pass it to the next middleware, or short-circuit the pipeline. The order determines which middleware sees what request state.

Think of middleware as layers of an onion: the first middleware added is the outermost layer (sees the request first, the response last). In practice, wrong ordering causes silent failures: HTTPS redirection loops, missing authentication, broken CORS, or incorrect client IPs from proxies.

Terms used

  • Middleware: a component that handles HTTP requests and responses in a pipeline.
  • Short-circuit: when middleware handles a request without calling the next middleware.
  • Terminal middleware: middleware that does not call next (like static files for matched paths).
  • Request delegate: the next middleware in the pipeline.

Reader contract

This article is for:

  • Engineers configuring ASP.NET Core application pipelines.
  • Teams debugging middleware ordering issues.

You will leave with:

  • The canonical middleware order with rationale.
  • Failure modes for each middleware when misordered.
  • A complete Program.cs template.

This is not for:

  • Writing custom middleware (implementation details).
  • Deep-dives on individual middleware configuration.

Quick start (10 minutes)

If you need the correct order without explanation:

Verified on: ASP.NET Core (.NET 10).

var app = builder.Build();

// 1. Exception handling (catches errors from all subsequent middleware)
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

// 2. Forwarded headers (before anything that uses scheme/host/IP)
app.UseForwardedHeaders();

// 3. HTTPS redirection (after forwarded headers know the real scheme)
app.UseHttpsRedirection();

// 4. Static files (short-circuits for static content)
app.UseStaticFiles();

// 5. Routing (makes route data available to subsequent middleware)
app.UseRouting();

// 6. CORS (after routing, before caching and auth)
app.UseCors();

// 7. Authentication (establishes identity)
app.UseAuthentication();

// 8. Authorization (checks permissions using identity)
app.UseAuthorization();

// 9. Output caching (caches authorized responses)
app.UseOutputCache();

// 10. Response compression (compresses before sending)
app.UseResponseCompression();

// 11. Rate limiting (after routing for endpoint-specific limits)
app.UseRateLimiter();

// 12. Endpoints
app.MapControllers();
app.Run();

The production middleware order

This is the order Microsoft recommends, with rationale for each position.

Request
   |
   v
[1] ExceptionHandler / DeveloperExceptionPage
[2] HSTS
[3] ForwardedHeaders
[4] HttpsRedirection
[5] Static Files
[6] Routing
[7] CORS
[8] Authentication
[9] Authorization
[10] Session
[11] OutputCache / ResponseCaching
[12] ResponseCompression
[13] Rate Limiting
[14] Custom Middleware
[15] Endpoint Mapping
   |
   v
Response (travels back up through middleware)

ForwardedHeaders: why it must come early

When your application runs behind a reverse proxy (Azure App Service, nginx, AWS ALB), the proxy terminates TLS and forwards requests over HTTP. Without ForwardedHeaders, your application sees:

  • Scheme: http (not https)
  • Host: internal proxy address
  • Client IP: proxy IP (not real client)

What breaks if wrong

ForwardedHeaders position Failure
After HttpsRedirection Infinite redirect loop (scheme appears as http)
After authentication Auth cookies may use wrong secure flag
After rate limiting Rate limits apply to proxy IP, not client IP
Missing entirely HTTPS redirects loop; client IP is wrong

Correct position

// After exception handling, before everything else
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseForwardedHeaders(new ForwardedHeadersOptions
{
    ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto,
    KnownNetworks = { /* your proxy networks */ }
});

app.UseHttpsRedirection(); // Now sees correct scheme

HTTPS Redirection and HSTS

HSTS tells browsers to always use HTTPS. HttpsRedirection redirects HTTP requests to HTTPS.

What breaks if wrong

Issue Failure
HSTS before ExceptionHandler Errors may not set HSTS header
HttpsRedirection before ForwardedHeaders Redirect loop behind proxy
HSTS in development Localhost becomes HTTPS-only (annoying)

Correct position

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts(); // Only in production
}

app.UseForwardedHeaders();
app.UseHttpsRedirection();

Static Files positioning

Static file middleware serves files from wwwroot and short-circuits the pipeline for matched files.

What breaks if wrong

Position Failure
After Routing Unnecessary routing overhead for every static file
After Authentication Static files require authentication (usually wrong)
After Authorization Static files blocked by authorization policies

Correct position

app.UseStaticFiles(); // Before routing - no auth, fast short-circuit

// Or use MapStaticAssets() after routing for fingerprinted assets
app.UseRouting();
app.MapStaticAssets(); // After routing, integrates with endpoint routing

Why UseAuthentication must come before UseAuthorization

This is the most common ordering mistake. Authentication establishes who the user is. Authorization checks what they can do.

What breaks if wrong

Order Failure
Authorization before Authentication HttpContext.User is null; all authorization fails
Missing Authentication User identity never populated
Missing Authorization [Authorize] attributes ignored

Correct position

app.UseAuthentication();  // First: establish identity
app.UseAuthorization();   // Second: check permissions

CORS positioning

CORS middleware adds headers for cross-origin requests. Position affects whether CORS headers appear on cached responses.

What breaks if wrong

Position Failure
After ResponseCaching CORS headers may be cached incorrectly or missing
Before Routing Cannot use endpoint-specific CORS policies
After Authorization Preflight requests may be blocked

Correct position

app.UseRouting();
app.UseCors();           // After routing, before caching
app.UseAuthentication();
app.UseAuthorization();
app.UseResponseCaching();

OutputCache and ResponseCompression order

These interact: do you cache compressed responses or compress cached responses?

Options

// Option A: Cache then compress (compress on every cache hit)
app.UseOutputCache();
app.UseResponseCompression();

// Option B: Compress then cache (cache compressed responses)
app.UseResponseCompression();
app.UseOutputCache();

Trade-offs

Order Effect
OutputCache then Compression Cached uncompressed; compress on every response
Compression then OutputCache Cached compressed; less CPU but more cache variants

Most applications use Option A (OutputCache before ResponseCompression) for simplicity.

Rate Limiting positioning

Rate limiting can use global limits or endpoint-specific limits with attributes.

What breaks if wrong

Position Failure
Before Routing Cannot use [EnableRateLimiting] attributes
Before ForwardedHeaders Rate limits apply to proxy IP

Correct position

app.UseRouting();
app.UseRateLimiter(); // After routing for endpoint-specific policies

Routing and endpoint mapping

UseRouting() makes route data available. Endpoint mapping (MapControllers, MapRazorPages) defines what routes exist.

What breaks if wrong

Issue Failure
UseRouting after middleware that needs routes Route data unavailable
Missing UseRouting Implicit routing may not work as expected
MapControllers before UseAuthorization Authorization not applied

Correct position

app.UseRouting();         // Makes route data available
// ... middleware that uses routing ...
app.UseAuthorization();   // Uses route data for policies
app.MapControllers();     // Defines endpoints

Custom middleware placement

Where should your custom middleware go?

Guidelines

Middleware purpose Position
Request logging Early (after exception handler)
Security headers After exception handler, before responses
Request transformation Before routing
Response transformation After endpoint middleware adds content
Metrics/timing Early to capture full request

Example: security headers

app.UseExceptionHandler("/Error");
app.UseHsts();
app.UseForwardedHeaders();

// Custom security headers - early, affects all responses
app.Use(async (context, next) =>
{
    context.Response.Headers.XContentTypeOptions = "nosniff";
    context.Response.Headers["X-Frame-Options"] = "DENY";
    await next();
});

app.UseHttpsRedirection();

Copy/paste artifact: complete production pipeline

var app = builder.Build();

// === Error Handling ===
if (app.Environment.IsDevelopment())
{
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

// === Forwarded Headers (proxy support) ===
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
    ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});

// === Security ===
app.UseHttpsRedirection();

// Security headers
app.Use(async (context, next) =>
{
    context.Response.Headers.XContentTypeOptions = "nosniff";
    context.Response.Headers["Referrer-Policy"] = "strict-origin-when-cross-origin";
    context.Response.Headers["X-Frame-Options"] = "DENY";
    await next();
});

// === Routing ===
app.UseRouting();

// === Cross-Origin ===
app.UseCors("AllowSpecificOrigins");

// === Authentication & Authorization ===
app.UseAuthentication();
app.UseAuthorization();

// === Caching & Compression ===
app.UseOutputCache();
app.UseResponseCompression();

// === Rate Limiting ===
app.UseRateLimiter();

// === Static Assets ===
app.MapStaticAssets();

// === Endpoints ===
app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

Common failure modes

Symptom Likely cause
Infinite HTTPS redirect loop ForwardedHeaders after HttpsRedirection
Authorization always fails Authentication after Authorization
Client IP always same (proxy IP) Missing or late ForwardedHeaders
CORS headers missing on cached responses CORS after ResponseCaching
Rate limits ignore endpoint attributes RateLimiter before Routing
Static files require login StaticFiles after Authentication
Exceptions not caught ExceptionHandler not first

Checklist

  • ExceptionHandler is first middleware (in production).
  • ForwardedHeaders before HttpsRedirection (if behind proxy).
  • Authentication before Authorization.
  • Routing before middleware that needs route data.
  • CORS before ResponseCaching.
  • RateLimiter after Routing (for endpoint-specific limits).
  • Static files before Authentication (unless auth required).

FAQ

Do I always need UseRouting()?

In .NET 6+, routing is added implicitly when you call Map* methods. Explicit UseRouting() is needed when middleware between routing and endpoints needs route data.

Can I have multiple exception handlers?

Yes, but only the first one catches exceptions. Subsequent ones only see handled responses.

Does middleware order affect performance?

Yes. Put short-circuiting middleware (static files) early to avoid unnecessary processing. Put expensive middleware late when possible.

What if I am not behind a proxy?

Skip ForwardedHeaders. It only matters when a reverse proxy terminates TLS or modifies headers.

How do I debug middleware order issues?

Add logging middleware at different points to see what state exists at each stage. Check request scheme, host, and user identity.

Why must UseAuthentication come before UseAuthorization?

UseAuthentication populates HttpContext.User with the authenticated identity. UseAuthorization checks that identity against policies. If reversed, User is null and all authorization fails silently.

How do I fix an infinite redirect loop in ASP.NET Core?

Usually caused by ForwardedHeaders being after HttpsRedirection. Behind a reverse proxy, the app sees scheme as "http" and keeps redirecting. Move UseForwardedHeaders before UseHttpsRedirection.

What to do next

Review your Program.cs middleware order against the reference in this article. If you are behind a reverse proxy, verify ForwardedHeaders is configured correctly.

For more on authentication configuration, read Authentication Patterns: Cookie vs JWT vs OIDC.

If you want help debugging middleware ordering issues, reach out via Contact.

References

Author notes

Decisions:

  • Present canonical order with failure modes. Rationale: understanding what breaks is more memorable than just the correct order.
  • Include ForwardedHeaders prominently. Rationale: proxy integration issues are common and hard to diagnose.
  • Show OutputCache before ResponseCompression. Rationale: simpler mental model; CPU cost is usually acceptable.

Observations:

  • ForwardedHeaders issues often manifest as redirect loops in production but work locally.
  • Authentication/Authorization order is the most common mistake in code reviews.
  • Teams often add middleware without considering pipeline position.