AllowedHosts

Restrict which Host headers the app answers with AllowedHosts, avoid breaking health probes, and decide between host filtering in the app and at the proxy.

Signature
"AllowedHosts": "api.example.com;*.shop.example.com"  |  AllowedHosts=*
  • Added in .NET ASP.NET Core 2.1

Parameters

ParameterTypeDescription
$AllowedHostsoptionalstring (hosts separated by ;)= unset: all hosts allowed; the templates set *Exact names, *.domain for subdomains (not the bare domain), or * for any host. Punycode for internationalised names. Ports are ignored.
$HostFilteringOptions.AllowEmptyHostsoptionalbool= trueAccept requests with no or empty Host header (HTTP/1.0).
$HostFilteringOptions.IncludeFailureMessageoptionalbool= trueWrite the HTML error body on rejection.

Return value

stringA semicolon-separated list of host names (without ports) the host filtering middleware accepts. Any other Host header gets 400 Bad Request with an "Invalid Hostname" HTML body.

Errors

Nothing at startup. Rejected requests get 400 and are logged at Information by Microsoft.AspNetCore.HostFiltering (The host '...' does not match an allowed host), which the usual Microsoft.AspNetCore: Warning rule hides.

Both WebApplication.CreateBuilder and CreateSlimBuilder register host filtering middleware at the very start of the pipeline. It reads AllowedHosts from configuration and compares the request's Host header against it. A mismatch ends the request with 400 and a small HTML page titled "Bad Request - Invalid Hostname" before routing, authentication or your endpoints run. The templates put "AllowedHosts": "*" in appsettings.json, which turns the check off.

Why it exists

Code that builds absolute URLs from Request.Host (password-reset links, Location headers, LinkGenerator.GetUriByName) trusts whatever the client sent. If the app is reachable directly, an attacker can send Host: evil.example and get links pointing at their domain into emails or caches. Kestrel does not check host names at all, so AllowedHosts is the in-app defence. IIS and many reverse proxies already filter by host; when the ingress only routes known hosts to the app, the app-level check is defence in depth.

Matching rules

EntryAllowsRejects
api.example.comapi.example.com, api.example.com:8443www.example.com
*.shop.example.comeu.shop.example.comshop.example.com (list it separately)
*EverythingNothing
(empty Host, HTTP/1.0)Allowed while AllowEmptyHosts is true

The production failure

Setting AllowedHosts to the public name breaks every caller that does not use it. Kubernetes HTTP probes send the pod IP as the Host (10.1.4.23:8080), so liveness fails and the pod restarts in a loop. Internal callers that use the Service name (orders-api.shop.svc.cluster.local) also get 400. Rejections are logged at Information under Microsoft.AspNetCore.HostFiltering, and the usual Microsoft.AspNetCore: Warning rule hides them, so the symptom is a mysterious 400 with no log line. Either list every name the app is legitimately reached by, set a Host header on the probe (httpGet.httpHeaders), or leave * and enforce host names at the ingress. Behind a proxy, the Host the middleware sees is what the proxy forwarded; it only uses X-Forwarded-Host if forwarded headers are configured for it and run first.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Stop password-reset emails from carrying attacker-supplied domains when the API builds absolute links from Request.Host
  • Diagnose a CrashLoopBackOff where liveness probes get 400 Invalid Hostname after AllowedHosts was set to the public name
  • Allow every tenant subdomain with *.shop.example.com while still listing shop.example.com for the bare domain

Frequently asked questions

Does *.example.com allow example.com?

No. The wildcard matches subdomains only. List example.com as a separate entry.

Why do I get 400 Bad Request with no log?

Host filtering rejected the Host header and logged it at Information, which the usual Microsoft.AspNetCore: Warning rule hides. Set Logging:LogLevel:Microsoft.AspNetCore.HostFiltering to Information, then compare the Host your caller sends (pod IP, Service name, public name) against AllowedHosts.

Should I set AllowedHosts if my ingress already routes by host?

It is defence in depth for when the app is reached some other way. If you set it, list internal Service names and give probes a Host header; otherwise * plus strict ingress rules is a reasonable choice.

More examples

appsettings.Production.json listing every real name

Every name a legitimate caller uses must be listed: the public host, the Service DNS names other pods use, and any custom domain. The pod IP is deliberately absent, so the probes in the next example send a Host header.

Example · json
{
  // Public name, the in-cluster Service name, and the short Service name
  "AllowedHosts": "api.shop.example.com;orders-api.shop.svc.cluster.local;orders-api"
}

Probes that send a valid Host header

Without <code>httpHeaders</code> the kubelet sends the pod IP and port as Host, which a strict list rejects with 400, and a failing liveness probe restarts the container. Setting the header keeps host filtering on without carving out exceptions in code.

Example · yaml
apiVersion: v1
kind: Pod
metadata:
  name: orders-api
spec:
  containers:
    - name: api
      image: registry.example.com/orders-api:1.8.2
      ports:
        - containerPort: 8080
      livenessProbe:
        httpGet:
          path: /health/live
          port: 8080
          httpHeaders:
            - name: Host
              value: orders-api
      readinessProbe:
        httpGet:
          path: /health/ready
          port: 8080
          httpHeaders:
            - name: Host
              value: orders-api

Tightening the options in code

The list itself still comes from the <code>AllowedHosts</code> key; these options change behaviour around it. Rejected requests are logged at Information by the <code>Microsoft.AspNetCore.HostFiltering</code> category, so add a rule setting that category to Information when you investigate unexplained 400s.

Example · csharp
using Microsoft.AspNetCore.HostFiltering;

var builder = WebApplication.CreateBuilder(args);

builder.Services.Configure<HostFilteringOptions>(options =>
{
    // HTTP/1.0 clients without a Host header are not expected for this API.
    options.AllowEmptyHosts = false;
    // Plain 400 with no HTML body for API clients.
    options.IncludeFailureMessage = false;
});

var app = builder.Build();
app.MapGet("/products/{sku}", (string sku) => TypedResults.Ok(new { sku, price = 24.90m }));
app.Run();

Discussion

  • Be the first to comment on this lesson.