Kestrel web server implementation in ASP.NET Core

By Tom Dykstra, Chris Ross, and Stephen Halter

Kestrel is a cross-platform web server for ASP.NET Core. Kestrel is the web server that's included by default in ASP.NET Core project templates.

Kestrel supports the following features:

  • HTTPS
  • Opaque upgrade used to enable WebSockets
  • Unix sockets for high performance behind Nginx
  • HTTP/2 (except on macOS†)

†HTTP/2 will be supported on macOS in a future release.

  • HTTPS
  • Opaque upgrade used to enable WebSockets
  • Unix sockets for high performance behind Nginx

Kestrel is supported on all platforms and versions that .NET Core supports.

View or download sample code (how to download)

HTTP/2 support

HTTP/2 is available for ASP.NET Core apps if the following base requirements are met:

  • Operating system†
    • Windows Server 2012 R2/Windows 8.1 or later
    • Linux with OpenSSL 1.0.2 or later (for example, Ubuntu 16.04 or later)
  • Target framework: .NET Core 2.2 or later
  • Application-Layer Protocol Negotiation (ALPN) connection
  • TLS 1.2 or later connection

†HTTP/2 will be supported on macOS in a future release.

If an HTTP/2 connection is established, HttpRequest.Protocol reports HTTP/2.

HTTP/2 is disabled by default. For more information on configuration, see the Kestrel options and Endpoint configuration sections.

When to use Kestrel with a reverse proxy

You can use Kestrel by itself or with a reverse proxy server, such as IIS, Nginx, or Apache. A reverse proxy server receives HTTP requests from the Internet and forwards them to Kestrel after some preliminary handling.

Kestrel communicates directly with the Internet without a reverse proxy server

Kestrel communicates indirectly with the Internet through a reverse proxy server, such as IIS, Nginx, or Apache

Either configuration—with or without a reverse proxy server—is a valid and supported hosting configuration for ASP.NET Core 2.0 or later apps.

If an app accepts requests only from an internal network, Kestrel can be used directly as the app's server.

Kestrel communicates directly with your internal network

If you expose your app to the Internet, use IIS, Nginx, or Apache as a reverse proxy server. A reverse proxy server receives HTTP requests from the Internet and forwards them to Kestrel after some preliminary handling.

Kestrel communicates indirectly with the Internet through a reverse proxy server, such as IIS, Nginx, or Apache

A reverse proxy is required for public-facing edge server deployments (exposed to traffic from the Internet) for security reasons. The 1.x versions of Kestrel don't have a full complement of defenses against attacks, such as appropriate timeouts, size limits, and concurrent connection limits.

A reverse proxy scenario exists when there are multiple apps that share the same IP and port running on a single server. Kestrel doesn't support this scenario because Kestrel doesn't support sharing the same IP and port among multiple processes. When Kestrel is configured to listen on a port, Kestrel handles all of the traffic for that port regardless of requests' host header. A reverse proxy that can share ports has the ability to forward requests to Kestrel on a unique IP and port.

Even if a reverse proxy server isn't required, using a reverse proxy server might be a good choice:

  • It can limit the exposed public surface area of the apps that it hosts.
  • It provides an additional layer of configuration and defense.
  • It might integrate better with existing infrastructure.
  • It simplifies load balancing and SSL configuration. Only the reverse proxy server requires an SSL certificate, and that server can communicate with your app servers on the internal network using plain HTTP.

Warning

If not using a reverse proxy with host filtering enabled, host filtering must be enabled.

How to use Kestrel in ASP.NET Core apps

The Microsoft.AspNetCore.Server.Kestrel package is included in the Microsoft.AspNetCore.App metapackage (ASP.NET Core 2.1 or later).

ASP.NET Core project templates use Kestrel by default. In Program.cs, the template code calls CreateDefaultBuilder, which calls UseKestrel behind the scenes.

public static void Main(string[] args)
{
    CreateWebHostBuilder(args).Build().Run();
}

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .UseStartup<Startup>();

To provide additional configuration after calling CreateDefaultBuilder, use ConfigureKestrel:

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .UseStartup<Startup>()
        .ConfigureKestrel((context, options) =>
        {
            // Set properties and call methods on options
        });

To provide additional configuration after calling CreateDefaultBuilder, call UseKestrel:

public static IWebHost BuildWebHost(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .UseStartup<Startup>()
        .UseKestrel(options =>
        {
            // Set properties and call methods on options
        })
        .Build();

Install the Microsoft.AspNetCore.Server.Kestrel NuGet package.

Call the UseKestrel extension method on WebHostBuilder in the Main method, specifying any Kestrel options required, as shown in the next section.

public static void Main(string[] args)
{
    Console.WriteLine("Running demo with Kestrel.");

    var config = new ConfigurationBuilder()
        .AddCommandLine(args)
        .Build();

    var builder = new WebHostBuilder()
        .UseContentRoot(Directory.GetCurrentDirectory())
        .UseConfiguration(config)
        .UseStartup<Startup>()
        .UseKestrel(options =>
        {
            if (config["threadCount"] != null)
            {
                options.ThreadCount = int.Parse(config["threadCount"]);
            }
        })
        .UseUrls("http://localhost:5000");

    var host = builder.Build();
    host.Run();
}

Kestrel options

The Kestrel web server has constraint configuration options that are especially useful in Internet-facing deployments. A few important limits that can be customized:

  • Maximum client connections
  • Maximum request body size
  • Minimum request body data rate

Set these and other constraints on the Limits property of the KestrelServerOptions class. The Limits property holds an instance of the KestrelServerLimits class.

Maximum client connections

MaxConcurrentConnections
MaxConcurrentUpgradedConnections

The maximum number of concurrent open TCP connections can be set for the entire app with the following code:

.ConfigureKestrel((context, options) =>
{
    options.Limits.MaxConcurrentConnections = 100;
    options.Limits.MaxConcurrentUpgradedConnections = 100;
    options.Limits.MaxRequestBodySize = 10 * 1024;
    options.Limits.MinRequestBodyDataRate =
        new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
    options.Limits.MinResponseDataRate =
        new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
    options.Listen(IPAddress.Loopback, 5000);
    options.Listen(IPAddress.Loopback, 5001, listenOptions =>
    {
        listenOptions.UseHttps("testCert.pfx", "testPassword");
    });
});
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .UseStartup<Startup>()
        .UseKestrel(options =>
        {
            options.Limits.MaxConcurrentConnections = 100;
        });

There's a separate limit for connections that have been upgraded from HTTP or HTTPS to another protocol (for example, on a WebSockets request). After a connection is upgraded, it isn't counted against the MaxConcurrentConnections limit.

.ConfigureKestrel((context, options) =>
{
    options.Limits.MaxConcurrentConnections = 100;
    options.Limits.MaxConcurrentUpgradedConnections = 100;
    options.Limits.MaxRequestBodySize = 10 * 1024;
    options.Limits.MinRequestBodyDataRate =
        new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
    options.Limits.MinResponseDataRate =
        new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
    options.Listen(IPAddress.Loopback, 5000);
    options.Listen(IPAddress.Loopback, 5001, listenOptions =>
    {
        listenOptions.UseHttps("testCert.pfx", "testPassword");
    });
});
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .UseStartup<Startup>()
        .UseKestrel(options =>
        {
            options.Limits.MaxConcurrentUpgradedConnections = 100;
        });

The maximum number of connections is unlimited (null) by default.

Maximum request body size

MaxRequestBodySize

The default maximum request body size is 30,000,000 bytes, which is approximately 28.6 MB.

The recommended approach to override the limit in an ASP.NET Core MVC app is to use the RequestSizeLimit attribute on an action method:

[RequestSizeLimit(100000000)]
public IActionResult MyActionMethod()

Here's an example that shows how to configure the constraint for the app on every request:

.ConfigureKestrel((context, options) =>
{
    options.Limits.MaxConcurrentConnections = 100;
    options.Limits.MaxConcurrentUpgradedConnections = 100;
    options.Limits.MaxRequestBodySize = 10 * 1024;
    options.Limits.MinRequestBodyDataRate =
        new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
    options.Limits.MinResponseDataRate =
        new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
    options.Listen(IPAddress.Loopback, 5000);
    options.Listen(IPAddress.Loopback, 5001, listenOptions =>
    {
        listenOptions.UseHttps("testCert.pfx", "testPassword");
    });
});
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .UseStartup<Startup>()
        .UseKestrel(options =>
        {
            options.Limits.MaxRequestBodySize = 10 * 1024;
        });

You can override the setting on a specific request in middleware:

app.Run(async (context) =>
{
    context.Features.Get<IHttpMaxRequestBodySizeFeature>()
        .MaxRequestBodySize = 10 * 1024;
    context.Features.Get<IHttpMinRequestBodyDataRateFeature>()
        .MinDataRate = new MinDataRate(bytesPerSecond: 100, 
            gracePeriod: TimeSpan.FromSeconds(10));
    context.Features.Get<IHttpMinResponseDataRateFeature>()
        .MinDataRate = new MinDataRate(bytesPerSecond: 100, 
            gracePeriod: TimeSpan.FromSeconds(10));

An exception is thrown if you attempt to configure the limit on a request after the app has started to read the request. There's an IsReadOnly property that indicates if the MaxRequestBodySize property is in read-only state, meaning it's too late to configure the limit.

Minimum request body data rate

MinRequestBodyDataRate
MinResponseDataRate

Kestrel checks every second if data is arriving at the specified rate in bytes/second. If the rate drops below the minimum, the connection is timed out. The grace period is the amount of time that Kestrel gives the client to increase its send rate up to the minimum; the rate isn't checked during that time. The grace period helps avoid dropping connections that are initially sending data at a slow rate due to TCP slow-start.

The default minimum rate is 240 bytes/second with a 5 second grace period.

A minimum rate also applies to the response. The code to set the request limit and the response limit is the same except for having RequestBody or Response in the property and interface names.

Here's an example that shows how to configure the minimum data rates in Program.cs:

.ConfigureKestrel((context, options) =>
{
    options.Limits.MaxConcurrentConnections = 100;
    options.Limits.MaxConcurrentUpgradedConnections = 100;
    options.Limits.MaxRequestBodySize = 10 * 1024;
    options.Limits.MinRequestBodyDataRate =
        new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
    options.Limits.MinResponseDataRate =
        new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
    options.Listen(IPAddress.Loopback, 5000);
    options.Listen(IPAddress.Loopback, 5001, listenOptions =>
    {
        listenOptions.UseHttps("testCert.pfx", "testPassword");
    });
});
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .UseStartup<Startup>()
        .UseKestrel(options =>
        {
            options.Limits.MinRequestBodyDataRate =
                new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
            options.Limits.MinResponseDataRate =
                new MinDataRate(bytesPerSecond: 100, gracePeriod: TimeSpan.FromSeconds(10));
        });

You can configure the rates per request in middleware:

app.Run(async (context) =>
{
    context.Features.Get<IHttpMaxRequestBodySizeFeature>()
        .MaxRequestBodySize = 10 * 1024;
    context.Features.Get<IHttpMinRequestBodyDataRateFeature>()
        .MinDataRate = new MinDataRate(bytesPerSecond: 100, 
            gracePeriod: TimeSpan.FromSeconds(10));
    context.Features.Get<IHttpMinResponseDataRateFeature>()
        .MinDataRate = new MinDataRate(bytesPerSecond: 100, 
            gracePeriod: TimeSpan.FromSeconds(10));

Maximum streams per connection

Http2.MaxStreamsPerConnection limits the number of concurrent request streams per HTTP/2 connection. Excess streams are refused.

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .UseStartup<Startup>()
        .ConfigureKestrel((context, options) =>
        {
            options.Limits.Http2.MaxStreamsPerConnection = 100;
        });

The default value is 100.

Header table size

The HPACK decoder decompresses HTTP headers for HTTP/2 connections. Http2.HeaderTableSize limits the size of the header compression table that the HPACK decoder uses. The value is provided in octets and must be greater than zero (0).

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .UseStartup<Startup>()
        .ConfigureKestrel((context, options) =>
        {
            options.Limits.Http2.HeaderTableSize = 4096;
        });

The default value is 4096.

Maximum frame size

Http2.MaxFrameSize indicates the maximum size of the HTTP/2 connection frame payload to receive. The value is provided in octets and must be between 2^14 (16,384) and 2^24-1 (16,777,215).

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .UseStartup<Startup>()
        .ConfigureKestrel((context, options) =>
        {
            options.Limits.Http2.MaxFrameSize = 16384;
        });

The default value is 2^14 (16,384).

For information about other Kestrel options and limits, see:

For information about Kestrel options and limits, see:

Endpoint configuration

By default, ASP.NET Core binds to http://localhost:5000. Call Listen or ListenUnixSocket methods on KestrelServerOptions to configure URL prefixes and ports for Kestrel. UseUrls, the --urls command-line argument, urls host configuration key, and the ASPNETCORE_URLS environment variable also work but have the limitations noted later in this section.

The urls host configuration key must come from the host configuration, not the app configuration. Adding a urls key and value to appsettings.json doesn't affect host configuration because the host is completely initialized by the time the configuration is read from the configuration file. However, a urls key in appsettings.json can be used with UseConfiguration on the host builder to configure the host:

var config = new ConfigurationBuilder()
    .SetBasePath(Directory.GetCurrentDirectory())
    .AddJsonFile("appSettings.json", optional: true, reloadOnChange: true)
    .Build();

var host = new WebHostBuilder()
    .UseKestrel()
    .UseConfiguration(config)
    .UseContentRoot(Directory.GetCurrentDirectory())
    .UseStartup<Startup>()
    .Build();

By default, ASP.NET Core binds to:

  • http://localhost:5000
  • https://localhost:5001 (when a local development certificate is present)

A development certificate is created:

Some browsers require that you grant explicit permission to the browser to trust the local development certificate.

ASP.NET Core 2.1 and later project templates configure apps to run on HTTPS by default and include HTTPS redirection and HSTS support.

Call Listen or ListenUnixSocket methods on KestrelServerOptions to configure URL prefixes and ports for Kestrel.

UseUrls, the --urls command-line argument, urls host configuration key, and the ASPNETCORE_URLS environment variable also work but have the limitations noted later in this section (a default certificate must be available for HTTPS endpoint configuration).

ASP.NET Core 2.1 KestrelServerOptions configuration:

ConfigureEndpointDefaults(Action<ListenOptions>)

Specifies a configuration Action to run for each specified endpoint. Calling ConfigureEndpointDefaults multiple times replaces prior Actions with the last Action specified.

ConfigureHttpsDefaults(Action<HttpsConnectionAdapterOptions>)

Specifies a configuration Action to run for each HTTPS endpoint. Calling ConfigureHttpsDefaults multiple times replaces prior Actions with the last Action specified.

Configure(IConfiguration)

Creates a configuration loader for setting up Kestrel that takes an IConfiguration as input. The configuration must be scoped to the configuration section for Kestrel.

ListenOptions.UseHttps

Configure Kestrel to use HTTPS.

ListenOptions.UseHttps extensions:

  • UseHttps – Configure Kestrel to use HTTPS with the default certificate. Throws an exception if no default certificate is configured.
  • UseHttps(string fileName)
  • UseHttps(string fileName, string password)
  • UseHttps(string fileName, string password, Action<HttpsConnectionAdapterOptions> configureOptions)
  • UseHttps(StoreName storeName, string subject)
  • UseHttps(StoreName storeName, string subject, bool allowInvalid)
  • UseHttps(StoreName storeName, string subject, bool allowInvalid, StoreLocation location)
  • UseHttps(StoreName storeName, string subject, bool allowInvalid, StoreLocation location, Action<HttpsConnectionAdapterOptions> configureOptions)
  • UseHttps(X509Certificate2 serverCertificate)
  • UseHttps(X509Certificate2 serverCertificate, Action<HttpsConnectionAdapterOptions> configureOptions)
  • UseHttps(Action<HttpsConnectionAdapterOptions> configureOptions)

ListenOptions.UseHttps parameters:

  • filename is the path and file name of a certificate file, relative to the directory that contains the app's content files.
  • password is the password required to access the X.509 certificate data.
  • configureOptions is an Action to configure the HttpsConnectionAdapterOptions. Returns the ListenOptions.
  • storeName is the certificate store from which to load the certificate.
  • subject is the subject name for the certificate.
  • allowInvalid indicates if invalid certificates should be considered, such as self-signed certificates.
  • location is the store location to load the certificate from.
  • serverCertificate is the X.509 certificate.

In production, HTTPS must be explicitly configured. At a minimum, a default certificate must be provided.

Supported configurations described next:

  • No configuration
  • Replace the default certificate from configuration
  • Change the defaults in code

No configuration

Kestrel listens on http://localhost:5000 and https://localhost:5001 (if a default cert is available).

Specify URLs using the:

  • ASPNETCORE_URLS environment variable.
  • --urls command-line argument.
  • urls host configuration key.
  • UseUrls extension method.

For more information, see Server URLs and Override configuration.

The value provided using these approaches can be one or more HTTP and HTTPS endpoints (HTTPS if a default cert is available). Configure the value as a semicolon-separated list (for example, "Urls": "http://localhost:8000;http://localhost:8001").

Replace the default certificate from configuration

WebHost.CreateDefaultBuilder calls serverOptions.Configure(context.Configuration.GetSection("Kestrel")) by default to load Kestrel configuration. A default HTTPS app settings configuration schema is available for Kestrel. Configure multiple endpoints, including the URLs and the certificates to use, either from a file on disk or from a certificate store.

In the following appsettings.json example:

  • Set AllowInvalid to true to permit the use of invalid certificates (for example, self-signed certificates).
  • Any HTTPS endpoint that doesn't specify a certificate (HttpsDefaultCert in the example that follows) falls back to the cert defined under Certificates > Default or the development certificate.
{
"Kestrel": {
  "EndPoints": {
    "Http": {
      "Url": "http://localhost:5000"
    },

    "HttpsInlineCertFile": {
      "Url": "https://localhost:5001",
      "Certificate": {
        "Path": "<path to .pfx file>",
        "Password": "<certificate password>"
      }
    },

    "HttpsInlineCertStore": {
      "Url": "https://localhost:5002",
      "Certificate": {
        "Subject": "<subject; required>",
        "Store": "<certificate store; defaults to My>",
        "Location": "<location; defaults to CurrentUser>",
        "AllowInvalid": "<true or false; defaults to false>"
      }
    },

    "HttpsDefaultCert": {
      "Url": "https://localhost:5003"
    },

    "Https": {
      "Url": "https://*:5004",
      "Certificate": {
      "Path": "<path to .pfx file>",
      "Password": "<certificate password>"
      }
    }
    },
    "Certificates": {
      "Default": {
        "Path": "<path to .pfx file>",
        "Password": "<certificate password>"
      }
    }
  }
}

An alternative to using Path and Password for any certificate node is to specify the certificate using certificate store fields. For example, the Certificates > Default certificate can be specified as:

"Default": {
  "Subject": "<subject; required>",
  "Store": "<cert store; defaults to My>",
  "Location": "<location; defaults to CurrentUser>",
  "AllowInvalid": "<true or false; defaults to false>"
}

Schema notes:

  • Endpoints names are case-insensitive. For example, HTTPS and Https are valid.

  • The Url parameter is required for each endpoint. The format for this parameter is the same as the top-level Urls configuration parameter except that it's limited to a single value.

  • These endpoints replace those defined in the top-level Urls configuration rather than adding to them. Endpoints defined in code via Listen are cumulative with the endpoints defined in the configuration section.

  • The Certificate section is optional. If the Certificate section isn't specified, the defaults defined in earlier scenarios are used. If no defaults are available, the server throws an exception and fails to start.

  • The Certificate section supports both PathPassword and SubjectStore certificates.

  • Any number of endpoints may be defined in this way so long as they don't cause port conflicts.

  • options.Configure(context.Configuration.GetSection("Kestrel")) returns a KestrelConfigurationLoader with an .Endpoint(string name, options => { }) method that can be used to supplement a configured endpoint's settings:

    options.Configure(context.Configuration.GetSection("Kestrel"))
        .Endpoint("HTTPS", opt =>
        {
            opt.HttpsOptions.SslProtocols = SslProtocols.Tls12;
        });
    

    You can also directly access KestrelServerOptions.ConfigurationLoader to keep iterating on the existing loader, such as the one provided by WebHost.CreateDefaultBuilder.

  • The configuration section for each endpoint is a available on the options in the Endpoint method so that custom settings may be read.

  • Multiple configurations may be loaded by calling options.Configure(context.Configuration.GetSection("Kestrel")) again with another section. Only the last configuration is used, unless Load is explicitly called on prior instances. The metapackage doesn't call Load so that its default configuration section may be replaced.

  • KestrelConfigurationLoader mirrors the Listen family of APIs from KestrelServerOptions as Endpoint overloads, so code and config endpoints may be configured in the same place. These overloads don't use names and only consume default settings from configuration.

Change the defaults in code

ConfigureEndpointDefaults and ConfigureHttpsDefaults can be used to change default settings for ListenOptions and HttpsConnectionAdapterOptions, including overriding the default certificate specified in the prior scenario. ConfigureEndpointDefaults and ConfigureHttpsDefaults should be called before any endpoints are configured.

options.ConfigureEndpointDefaults(opt =>
{
    opt.NoDelay = true;
});

options.ConfigureHttpsDefaults(httpsOptions =>
{
    httpsOptions.SslProtocols = SslProtocols.Tls12;
});

Kestrel support for SNI

Server Name Indication (SNI) can be used to host multiple domains on the same IP address and port. For SNI to function, the client sends the host name for the secure session to the server during the TLS handshake so that the server can provide the correct certificate. The client uses the furnished certificate for encrypted communication with the server during the secure session that follows the TLS handshake.

Kestrel supports SNI via the ServerCertificateSelector callback. The callback is invoked once per connection to allow the app to inspect the host name and select the appropriate certificate.

SNI support requires:

  • Running on target framework netcoreapp2.1. On netcoreapp2.0 and net461, the callback is invoked but the name is always null. The name is also null if the client doesn't provide the host name parameter in the TLS handshake.
  • All websites run on the same Kestrel instance. Kestrel doesn't support sharing an IP address and port across multiple instances without a reverse proxy.
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .UseStartup<Startup>()
        .ConfigureKestrel((context, options) =>
        {
            options.ListenAnyIP(5005, listenOptions =>
            {
                listenOptions.UseHttps(httpsOptions =>
                {
                    var localhostCert = CertificateLoader.LoadFromStoreCert(
                        "localhost", "My", StoreLocation.CurrentUser, 
                        allowInvalid: true);
                    var exampleCert = CertificateLoader.LoadFromStoreCert(
                        "example.com", "My", StoreLocation.CurrentUser, 
                        allowInvalid: true);
                    var subExampleCert = CertificateLoader.LoadFromStoreCert(
                        "sub.example.com", "My", StoreLocation.CurrentUser, 
                        allowInvalid: true);
                    var certs = new Dictionary<string, X509Certificate2>(
                        StringComparer.OrdinalIgnoreCase);
                    certs["localhost"] = localhostCert;
                    certs["example.com"] = exampleCert;
                    certs["sub.example.com"] = subExampleCert;

                    httpsOptions.ServerCertificateSelector = (connectionContext, name) =>
                    {
                        if (name != null && certs.TryGetValue(name, out var cert))
                        {
                            return cert;
                        }

                        return exampleCert;
                    };
                });
            });
        });
public static IWebHost BuildWebHost(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .UseStartup<Startup>()
        .UseKestrel((context, options) =>
        {
            options.ListenAnyIP(5005, listenOptions =>
            {
                listenOptions.UseHttps(httpsOptions =>
                {
                    var localhostCert = CertificateLoader.LoadFromStoreCert(
                        "localhost", "My", StoreLocation.CurrentUser, 
                        allowInvalid: true);
                    var exampleCert = CertificateLoader.LoadFromStoreCert(
                        "example.com", "My", StoreLocation.CurrentUser, 
                        allowInvalid: true);
                    var subExampleCert = CertificateLoader.LoadFromStoreCert(
                        "sub.example.com", "My", StoreLocation.CurrentUser, 
                        allowInvalid: true);
                    var certs = new Dictionary<string, X509Certificate2>(
                        StringComparer.OrdinalIgnoreCase);
                    certs["localhost"] = localhostCert;
                    certs["example.com"] = exampleCert;
                    certs["sub.example.com"] = subExampleCert;

                    httpsOptions.ServerCertificateSelector = (connectionContext, name) =>
                    {
                        if (name != null && certs.TryGetValue(name, out var cert))
                        {
                            return cert;
                        }

                        return exampleCert;
                    };
                });
            });
        })
        .Build();

Bind to a TCP socket

The Listen method binds to a TCP socket, and an options lambda permits SSL certificate configuration:

public static void Main(string[] args)
{
    CreateWebHostBuilder(args).Build().Run();
}

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .UseStartup<Startup>()
        .ConfigureKestrel((context, options) =>
        {
            options.Listen(IPAddress.Loopback, 5000);
            options.Listen(IPAddress.Loopback, 5001, listenOptions =>
            {
                listenOptions.UseHttps("testCert.pfx", "testPassword");
            });
        });
public static void Main(string[] args)
{
    CreateWebHostBuilder(args).Build().Run();
}

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .UseStartup<Startup>()
        .UseKestrel(options =>
        {
            options.Listen(IPAddress.Loopback, 5000);
            options.Listen(IPAddress.Loopback, 5001, listenOptions =>
            {
                listenOptions.UseHttps("testCert.pfx", "testPassword");
            });
        });

The example configures SSL for an endpoint with ListenOptions. Use the same API to configure other Kestrel settings for specific endpoints.

On Windows, self-signed certificates can be created using the New-SelfSignedCertificate PowerShell cmdlet. For an unsupported example, see UpdateIISExpressSSLForChrome.ps1.

On macOS, Linux, and Windows, certificates can be created using OpenSSL.

Bind to a Unix socket

Listen on a Unix socket with ListenUnixSocket for improved performance with Nginx, as shown in this example:

.ConfigureKestrel((context, options) =>
{
    options.ListenUnixSocket("/tmp/kestrel-test.sock");
    options.ListenUnixSocket("/tmp/kestrel-test.sock", listenOptions =>
    {
        listenOptions.UseHttps("testCert.pfx", "testpassword");
    });
});
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .UseStartup<Startup>()
        .UseKestrel(options =>
        {
            options.ListenUnixSocket("/tmp/kestrel-test.sock");
            options.ListenUnixSocket("/tmp/kestrel-test.sock", listenOptions =>
            {
                listenOptions.UseHttps("testCert.pfx", "testpassword");
            });
        });

Port 0

When the port number 0 is specified, Kestrel dynamically binds to an available port. The following example shows how to determine which port Kestrel actually bound at runtime:

public void Configure(IApplicationBuilder app)
{
    var serverAddressesFeature = 
        app.ServerFeatures.Get<IServerAddressesFeature>();

    app.UseStaticFiles();

    app.Run(async (context) =>
    {
        context.Response.ContentType = "text/html";
        await context.Response
            .WriteAsync("<!DOCTYPE html><html lang=\"en\"><head>" +
                "<title></title></head><body><p>Hosted by Kestrel</p>");

        if (serverAddressesFeature != null)
        {
            await context.Response
                .WriteAsync("<p>Listening on the following addresses: " +
                    string.Join(", ", serverAddressesFeature.Addresses) +
                    "</p>");
        }

        await context.Response.WriteAsync("<p>Request URL: " +
            $"{context.Request.GetDisplayUrl()}<p>");
    });
}

When the app is run, the console window output indicates the dynamic port where the app can be reached:

Listening on the following addresses: http://127.0.0.1:48508

Limitations

Configure endpoints with the following approaches:

  • UseUrls
  • --urls command-line argument
  • urls host configuration key
  • ASPNETCORE_URLS environment variable

These methods are useful for making code work with servers other than Kestrel. However, be aware of the following limitations:

  • SSL can't be used with these approaches unless a default certificate is provided in the HTTPS endpoint configuration (for example, using KestrelServerOptions configuration or a configuration file as shown earlier in this topic).
  • When both the Listen and UseUrls approaches are used simultaneously, the Listen endpoints override the UseUrls endpoints.

IIS endpoint configuration

When using IIS, the URL bindings for IIS override bindings are set by either Listen or UseUrls. For more information, see the ASP.NET Core Module topic.

By default, ASP.NET Core binds to http://localhost:5000. Configure URL prefixes and ports for Kestrel using:

  • UseUrls extension method
  • --urls command-line argument
  • urls host configuration key
  • ASP.NET Core configuration system, including ASPNETCORE_URLS environment variable

For more information on these methods, see Hosting.

IIS endpoint configuration

When using IIS, the URL bindings for IIS override bindings set by UseUrls. For more information, see the ASP.NET Core Module topic.

ListenOptions.Protocols

The Protocols property establishes the HTTP protocols (HttpProtocols) enabled on a connection endpoint or for the server. Assign a value to the Protocols property from the HttpProtocols enum.

HttpProtocols enum value Connection protocol permitted
Http1 HTTP/1.1 only. Can be used with or without TLS.
Http2 HTTP/2 only. Primarily used with TLS. May be used without TLS only if the client supports a Prior Knowledge mode.
Http1AndHttp2 HTTP/1.1 and HTTP/2. Requires a TLS and Application-Layer Protocol Negotiation (ALPN) connection to negotiate HTTP/2; otherwise, the connection defaults to HTTP/1.1.

The default protocol is HTTP/1.1.

TLS restrictions for HTTP/2:

  • TLS version 1.2 or later
  • Renegotiation disabled
  • Compression disabled
  • Minimum ephemeral key exchange sizes:
    • Elliptic curve Diffie-Hellman (ECDHE) [RFC4492] – 224 bits minimum
    • Finite field Diffie-Hellman (DHE) [TLS12] – 2048 bits minimum
  • Cipher suite not blacklisted

TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 [TLS-ECDHE] with the P-256 elliptic curve [FIPS186] is supported by default.

The following example permits HTTP/1.1 and HTTP/2 connections on port 8000. Connections are secured by TLS with a supplied certificate:

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .UseStartup<Startup>()
        .ConfigureKestrel((context, options) =>
        {
            options.Listen(IPAddress.Any, 8000, listenOptions =>
            {
                listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
                listenOptions.UseHttps("testCert.pfx", "testPassword");
            });
        }

Optionally create an IConnectionAdapter implementation to filter TLS handshakes on a per-connection basis for specific ciphers:

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .UseStartup<Startup>()
        .ConfigureKestrel((context, options) =>
        {
            options.Listen(IPAddress.Any, 8000, listenOptions =>
            {
                listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
                listenOptions.UseHttps("testCert.pfx", "testPassword");
                listenOptions.ConnectionAdapters.Add(new TlsFilterAdapter());
            });
        }
private class TlsFilterAdapter : IConnectionAdapter
{
    public bool IsHttps => false;

    public Task<IAdaptedConnection> OnConnectionAsync(ConnectionAdapterContext context)
    {
        var tlsFeature = context.Features.Get<ITlsHandshakeFeature>();

        // Throw NotSupportedException for any cipher algorithm that you don't 
        // wish to support. Alternatively, define and compare 
        // ITlsHandshakeFeature.CipherAlgorithm to a list of acceptable cipher 
        // suites.
        //
        // A ITlsHandshakeFeature.CipherAlgorithm of CipherAlgorithmType.Null 
        // indicates that no cipher algorithm supported by Kestrel matches the 
        // requested algorithm(s).
        if (tlsFeature.CipherAlgorithm == CipherAlgorithmType.Null)
        {
            throw new NotSupportedException("Prohibited cipher: " + tlsFeature.CipherAlgorithm);
        }

        return Task.FromResult<IAdaptedConnection>(new AdaptedConnection(context.ConnectionStream));
    }

    private class AdaptedConnection : IAdaptedConnection
    {
        public AdaptedConnection(Stream adaptedStream)
        {
            ConnectionStream = adaptedStream;
        }

        public Stream ConnectionStream { get; }

        public void Dispose()
        {
        }
    }
}

Set the protocol from configuration

WebHost.CreateDefaultBuilder calls serverOptions.Configure(context.Configuration.GetSection("Kestrel")) by default to load Kestrel configuration.

In the following appsettings.json example, a default connection protocol (HTTP/1.1 and HTTP/2) is established for all of Kestrel's endpoints:

{
  "Kestrel": {
    "EndPointDefaults": {
      "Protocols": "Http1AndHttp2"
    }
  }
}

The following configuration file example establishes a connection protocol for a specific endpoint:

{
  "Kestrel": {
    "EndPoints": {
      "HttpsDefaultCert": {
        "Url": "https://localhost:5001",
        "Protocols": "Http1AndHttp2"
      }
    }
  }
}

Protocols specified in code override values set by configuration.

Transport configuration

With the release of ASP.NET Core 2.1, Kestrel's default transport is no longer based on Libuv but instead based on managed sockets. This is a breaking change for ASP.NET Core 2.0 apps upgrading to 2.1 that call WebHostBuilderLibuvExtensions.UseLibuv and depend on either of the following packages:

For ASP.NET Core 2.1 or later projects that use the Microsoft.AspNetCore.App metapackage and require the use of Libuv:

  • Add a dependency for the Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv package to the app's project file:

    <PackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv" 
                      Version="<LATEST_VERSION>" />
    
  • Call WebHostBuilderLibuvExtensions.UseLibuv:

    public class Program
    {
        public static void Main(string[] args)
        {
            CreateWebHostBuilder(args).Build().Run();
        }
    
        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseLibuv()
                .UseStartup<Startup>();
    }
    

URL prefixes

When using UseUrls, --urls command-line argument, urls host configuration key, or ASPNETCORE_URLS environment variable, the URL prefixes can be in any of the following formats.

Only HTTP URL prefixes are valid. Kestrel doesn't support SSL when configuring URL bindings using UseUrls.

  • IPv4 address with port number

    http://65.55.39.10:80/
    

    0.0.0.0 is a special case that binds to all IPv4 addresses.

  • IPv6 address with port number

    http://[0:0:0:0:0:ffff:4137:270a]:80/
    

    [::] is the IPv6 equivalent of IPv4 0.0.0.0.

  • Host name with port number

    http://contoso.com:80/
    http://*:80/
    

    Host names, *, and +, aren't special. Anything not recognized as a valid IP address or localhost binds to all IPv4 and IPv6 IPs. To bind different host names to different ASP.NET Core apps on the same port, use HTTP.sys or a reverse proxy server, such as IIS, Nginx, or Apache.

    Warning

    If not using a reverse proxy with host filtering enabled, enable host filtering.

  • Host localhost name with port number or loopback IP with port number

    http://localhost:5000/
    http://127.0.0.1:5000/
    http://[::1]:5000/
    

    When localhost is specified, Kestrel attempts to bind to both IPv4 and IPv6 loopback interfaces. If the requested port is in use by another service on either loopback interface, Kestrel fails to start. If either loopback interface is unavailable for any other reason (most commonly because IPv6 isn't supported), Kestrel logs a warning.

  • IPv4 address with port number

    http://65.55.39.10:80/
    https://65.55.39.10:443/
    

    0.0.0.0 is a special case that binds to all IPv4 addresses.

  • IPv6 address with port number

    http://[0:0:0:0:0:ffff:4137:270a]:80/
    https://[0:0:0:0:0:ffff:4137:270a]:443/
    

    [::] is the IPv6 equivalent of IPv4 0.0.0.0.

  • Host name with port number

    http://contoso.com:80/
    http://*:80/
    https://contoso.com:443/
    https://*:443/
    

    Host names, *, and + aren't special. Anything that isn't a recognized IP address or localhost binds to all IPv4 and IPv6 IPs. To bind different host names to different ASP.NET Core apps on the same port, use WebListener or a reverse proxy server, such as IIS, Nginx, or Apache.

  • Host localhost name with port number or loopback IP with port number

    http://localhost:5000/
    http://127.0.0.1:5000/
    http://[::1]:5000/
    

    When localhost is specified, Kestrel attempts to bind to both IPv4 and IPv6 loopback interfaces. If the requested port is in use by another service on either loopback interface, Kestrel fails to start. If either loopback interface is unavailable for any other reason (most commonly because IPv6 isn't supported), Kestrel logs a warning.

  • Unix socket

    http://unix:/run/dan-live.sock
    

Port 0

When the port number is 0 is specified, Kestrel dynamically binds to an available port. Binding to port 0 is allowed for any host name or IP except for localhost.

When the app is run, the console window output indicates the dynamic port where the app can be reached:

Now listening on: http://127.0.0.1:48508

URL prefixes for SSL

If calling the UseHttps extension method, be sure to include URL prefixes with https::

var host = new WebHostBuilder()
    .UseKestrel(options =>
    {
        options.UseHttps("testCert.pfx", "testPassword");
    })
   .UseUrls("http://localhost:5000", "https://localhost:5001")
   .UseContentRoot(Directory.GetCurrentDirectory())
   .UseStartup<Startup>()
   .Build();

Note

HTTPS and HTTP can't be hosted on the same port.

On Windows, self-signed certificates can be created using the New-SelfSignedCertificate PowerShell cmdlet. For an unsupported example, see UpdateIISExpressSSLForChrome.ps1.

On macOS, Linux, and Windows, certificates can be created using OpenSSL.

Host filtering

While Kestrel supports configuration based on prefixes such as http://example.com:5000, Kestrel largely ignores the host name. Host localhost is a special case used for binding to loopback addresses. Any host other than an explicit IP address binds to all public IP addresses. None of this information is used to validate request Host headers.

As a workaround, host behind a reverse proxy with host header filtering. This is the only supported scenario for Kestrel in ASP.NET Core 1.x.

As a workaround, use middleware to filter requests by the Host header:

using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

// A normal middleware would provide an options type, config binding, extension methods, etc..
// This intentionally does all of the work inside of the middleware so it can be
// easily copy-pasted into docs and other projects.
public class HostFilteringMiddleware
{
    private readonly RequestDelegate _next;
    private readonly IList<string> _hosts;
    private readonly ILogger<HostFilteringMiddleware> _logger;

    public HostFilteringMiddleware(RequestDelegate next, IConfiguration config, ILogger<HostFilteringMiddleware> logger)
    {
        if (config == null)
        {
            throw new ArgumentNullException(nameof(config));
        }

        _next = next ?? throw new ArgumentNullException(nameof(next));
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));

        // A semicolon separated list of host names without the port numbers.
        // IPv6 addresses must use the bounding brackets and be in their normalized form.
        _hosts = config["AllowedHosts"]?.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
        if (_hosts == null || _hosts.Count == 0)
        {
            throw new InvalidOperationException("No configuration entry found for AllowedHosts.");
        }
    }

    public Task Invoke(HttpContext context)
    {
        if (!ValidateHost(context))
        {
            context.Response.StatusCode = 400;
            _logger.LogDebug("Request rejected due to incorrect Host header.");
            return Task.CompletedTask;
        }

        return _next(context);
    }

    // This does not duplicate format validations that are expected to be performed by the host.
    private bool ValidateHost(HttpContext context)
    {
        StringSegment host = context.Request.Headers[HeaderNames.Host].ToString().Trim();

        if (StringSegment.IsNullOrEmpty(host))
        {
            // Http/1.0 does not require the Host header.
            // Http/1.1 requires the header but the value may be empty.
            return true;
        }

        // Drop the port

        var colonIndex = host.LastIndexOf(':');

        // IPv6 special case
        if (host.StartsWith("[", StringComparison.Ordinal))
        {
            var endBracketIndex = host.IndexOf(']');
            if (endBracketIndex < 0)
            {
                // Invalid format
                return false;
            }
            if (colonIndex < endBracketIndex)
            {
                // No port, just the IPv6 Host
                colonIndex = -1;
            }
        }

        if (colonIndex > 0)
        {
            host = host.Subsegment(0, colonIndex);
        }

        foreach (var allowedHost in _hosts)
        {
            if (StringSegment.Equals(allowedHost, host, StringComparison.OrdinalIgnoreCase))
            {
                return true;
            }

            // Sub-domain wildcards: *.example.com
            if (allowedHost.StartsWith("*.", StringComparison.Ordinal) && host.Length >= allowedHost.Length)
            {
                // .example.com
                var allowedRoot = new StringSegment(allowedHost, 1, allowedHost.Length - 1);

                var hostRoot = host.Subsegment(host.Length - allowedRoot.Length, allowedRoot.Length);
                if (hostRoot.Equals(allowedRoot, StringComparison.OrdinalIgnoreCase))
                {
                    return true;
                }
            }
        }

        return false;
    }
}

Register the preceding HostFilteringMiddleware in Startup.Configure. Note that the ordering of middleware registration is important. Registration should occur immediately after Diagnostic Middleware registration (for example, app.UseExceptionHandler).

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseBrowserLink();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
    }

    app.UseMiddleware<HostFilteringMiddleware>();

    app.UseMvcWithDefaultRoute();
}

The middleware expects an AllowedHosts key in appsettings.json/appsettings.<EnvironmentName>.json. The value is a semicolon-delimited list of host names without port numbers:

As a workaround, use Host Filtering Middleware. Host Filtering Middleware is provided by the Microsoft.AspNetCore.HostFiltering package, which is included in the Microsoft.AspNetCore.App metapackage (ASP.NET Core 2.1 or later). The middleware is added by CreateDefaultBuilder, which calls AddHostFiltering:

public class Program
{
    public static void Main(string[] args)
    {
        CreateWebHostBuilder(args).Build().Run();
    }

    public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseStartup<Startup>();
}

Host Filtering Middleware is disabled by default. To enable the middleware, define an AllowedHosts key in appsettings.json/appsettings.<EnvironmentName>.json. The value is a semicolon-delimited list of host names without port numbers:

appsettings.json:

{
  "AllowedHosts": "example.com;localhost"
}

Note

Forwarded Headers Middleware also has an ForwardedHeadersOptions.AllowedHosts option. Forwarded Headers Middleware and Host Filtering Middleware have similar functionality for different scenarios. Setting AllowedHosts with Forwarded Headers Middleware is appropriate when the Host header isn't preserved while forwarding requests with a reverse proxy server or load balancer. Setting AllowedHosts with Host Filtering Middleware is appropriate when Kestrel is used as a public-facing edge server or when the Host header is directly forwarded.

For more information on Forwarded Headers Middleware, see Configure ASP.NET Core to work with proxy servers and load balancers.

Additional resources