ASPNETCORE_URLS and Kestrel Endpoints

Bind Kestrel to exact addresses with ASPNETCORE_URLS or named Kestrel:Endpoints, set protocols and certificates, and predict which setting wins.

Signature
ASPNETCORE_URLS=http://+:8080;https://+:8443  |  Kestrel:Endpoints:<Name>:Url
  • Added in .NET ASP.NET Core 1.0

Parameters

ParameterTypeDescription
$ASPNETCORE_URLSoptionalstring (URLs separated by ;)= http://localhost:5000Addresses to bind. Host part: an IP, localhost (loopback only), * or + for all IPv4 and IPv6 interfaces, or 0.0.0.0 for all IPv4 interfaces only. Also --urls, UseUrls(), app.Urls.
$Kestrel:Endpoints:<Name>:UrloptionalstringAddress for a named endpoint. Any endpoint here replaces every URLS and HTTP_PORTS value.
$Kestrel:Endpoints:<Name>:ProtocolsoptionalHttpProtocols= Http1AndHttp2Http1, Http2, Http1AndHttp2, Http3 or Http1AndHttp2AndHttp3. HTTP/2 without TLS needs Http2 only (prior knowledge), as gRPC services inside a cluster use.
$Kestrel:Endpoints:<Name>:Certificateoptionalobject= the default certificate (Kestrel:Certificates:Default, then the dev cert)Path and Password for a PFX, or Path and KeyPath for PEM files, or Subject/Store/Location for a store certificate.
$Kestrel:EndpointDefaults:ProtocolsoptionalHttpProtocols= Http1AndHttp2Protocols applied to every endpoint that does not set its own.

Return value

stringThe set of addresses Kestrel binds. URLS is a flat list; Kestrel:Endpoints is a named map where each endpoint can also set Protocols, Certificate, ClientCertificateMode and SslProtocols.

Errors

Binding a port in use throws IOException at StartAsync. An HTTPS endpoint without a certificate throws InvalidOperationException. A malformed URL throws at startup.

Changelog

  • PHP ASP.NET Core 8.0ASPNETCORE_HTTP_PORTS and HTTPS_PORTS added below URLS in precedence.

There are three ways to tell Kestrel where to listen, and they do not merge: the most specific one replaces the others. Kestrel endpoints (from the Kestrel:Endpoints configuration section or ConfigureKestrel code) win over URLS (ASPNETCORE_URLS, --urls, UseUrls, app.Urls), which win over ASPNETCORE_HTTP_PORTS. When nothing is set the app listens on http://localhost:5000, which nobody outside the machine or container can reach. Each override is announced with a warning at startup (Overriding address(es) ...), so read the first log lines when a port looks wrong.

URL syntax

Host partBinds
http://localhost:8080IPv4 and IPv6 loopback only
http://127.0.0.1:8080That IP only
http://+:8080, http://*:8080All IPv4 and IPv6 interfaces (any host name that is not an IP or localhost is treated the same)
http://0.0.0.0:8080All IPv4 interfaces only; unreachable over IPv6 on dual-stack clusters
http://127.0.0.1:0A random free port; read the real one from app.Urls after start

Kestrel does not route by host name. http://api.example.com:8080 binds all interfaces just like *; host checks belong to AllowedHosts.

Kestrel:Endpoints

Use named endpoints when one setting is not enough: an HTTP/2-only endpoint for gRPC beside an HTTP/1.1 endpoint for REST, a certificate per endpoint, or an admin port on 127.0.0.1. Endpoints only choose where Kestrel listens; they do not partition routes. Every mapped route answers on every endpoint, so an /admin route meant for the loopback port is also served on the public port unless you reject requests whose HttpContext.Connection.LocalPort is wrong. RequireHost("*:9090") is not enough on its own, because it matches the Host header the client sends. The section is read by CreateBuilder and CreateSlimBuilder, and endpoint changes are picked up when the JSON file reloads. Certificates are referenced by path; the password belongs in a secret (Kestrel__Endpoints__Https__Certificate__Password from a Kubernetes Secret), never in appsettings.json.

What teams decide

In containers, terminate TLS at the ingress or load balancer and serve HTTP on one port; set it with ASPNETCORE_HTTP_PORTS and use no URLS at all. Reach for Kestrel:Endpoints only for multiple protocols or end-to-end TLS. The failure mode to know: an endpoint added to appsettings.json for local HTTPS (say https://localhost:7001) ships to production, replaces the container's 8080, binds loopback inside the pod, and every readiness probe fails. Keep local endpoints in launchSettings.json or appsettings.Development.json.

Example

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

When to use it

  • Serve REST on HTTP/1.1 port 8080 and gRPC on cleartext HTTP/2 port 8081 from one service inside a cluster
  • Bind 127.0.0.1:9090 for diagnostics and reject /admin routes unless Connection.LocalPort is 9090, so only a sidecar in the pod can call them
  • Run an integration test host on http://127.0.0.1:0 and read the random port from app.Urls after StartAsync

Frequently asked questions

What is the difference between http://+:80, http://*:80 and http://0.0.0.0:80?

+ and * are the same in Kestrel: they bind the IPv6 any address in dual mode, which covers every IPv4 and IPv6 interface. 0.0.0.0 is parsed as IPAddress.Any and binds IPv4 only. Use * or + in containers, especially on dual-stack clusters.

Why do my URLS settings get ignored?

A Kestrel:Endpoints section (often in appsettings.json for local HTTPS) or ConfigureKestrel Listen calls replace all URLS values. The startup log says Overriding address(es) when this happens.

Can I change ports without restarting?

Endpoint changes in Kestrel:Endpoints are picked up when appsettings.json reloads, but in containers config files are usually immutable; plan on a restart.

More examples

appsettings.json with REST and gRPC endpoints

Cleartext HTTP/2 only works on an endpoint set to <code>Http2</code> alone, because without TLS there is no ALPN to negotiate it. Only connections from inside the pod reach the loopback admin endpoint, but that does not hide admin routes: every route is also served on 8080 and 8081, so admin routes must check <code>Connection.LocalPort</code> (see the code example).

Example · json
{
  "Kestrel": {
    "EndpointDefaults": {
      "Protocols": "Http1AndHttp2"
    },
    "Endpoints": {
      "Rest": {
        "Url": "http://*:8080"
      },
      "Grpc": {
        "Url": "http://*:8081",
        // HTTP/2 without TLS needs prior knowledge: Http2 only
        "Protocols": "Http2"
      },
      "Admin": {
        "Url": "http://127.0.0.1:9090"
      }
    }
  }
}

HTTPS endpoint with a certificate from a mounted secret

PEM certificate and key files mounted from a Kubernetes TLS Secret work directly with <code>Path</code> and <code>KeyPath</code>. For a password-protected PFX use <code>Path</code> plus <code>Password</code>, and supply the password through the environment variable <code>Kestrel__Endpoints__Https__Certificate__Password</code>.

Example · json
{
  "Kestrel": {
    "Endpoints": {
      "Https": {
        "Url": "https://*:8443",
        "SslProtocols": [ "Tls12", "Tls13" ],
        "Certificate": {
          "Path": "/etc/tls/tls.crt",
          "KeyPath": "/etc/tls/tls.key"
        }
      }
    }
  }
}

The same endpoints in code

Code-defined endpoints have the highest precedence and are fixed at build time. Prefer configuration so operations can change ports without a rebuild; use code when the endpoint layout is part of the application's contract. The filter ties the admin group to the loopback port by checking the local port of the connection, which the client cannot forge, unlike the Host header that <code>RequireHost</code> matches.

Example · csharp
using System.Net;
using Microsoft.AspNetCore.Server.Kestrel.Core;

var builder = WebApplication.CreateBuilder(args);

builder.WebHost.ConfigureKestrel(kestrel =>
{
    kestrel.ListenAnyIP(8080);
    kestrel.ListenAnyIP(8081, listen => listen.Protocols = HttpProtocols.Http2);
    kestrel.Listen(IPAddress.Loopback, 9090);
});

var app = builder.Build();
app.MapGet("/health/live", () => "ok");

// Endpoints do not partition routes: without this filter /admin answers on 8080 too.
var admin = app.MapGroup("/admin").AddEndpointFilter(async (context, next) =>
    context.HttpContext.Connection.LocalPort == 9090
        ? await next(context)
        : TypedResults.NotFound());
admin.MapPost("/cache/clear", () => TypedResults.NoContent());

app.Run();

Discussion

  • Be the first to comment on this lesson.