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 based on libuv, a cross-platform asynchronous I/O library. 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

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

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 — can also be used if Kestrel is exposed only to an internal network.

A scenario that requires a reverse proxy is when you have multiple applications that share the same IP and port running on a single server. That doesn't work with Kestrel directly because Kestrel doesn't support sharing the same IP and port between multiple processes. When you configure Kestrel to listen on a port, it handles all traffic for that port regardless of host header. A reverse proxy that can share ports must then forward to Kestrel on a unique IP and port.

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

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

Warning

If you're not using a reverse proxy with host filtering enabled, you must enable host filtering.

How to use Kestrel in ASP.NET Core apps

The Microsoft.AspNetCore.Server.Kestrel package is included in the Microsoft.AspNetCore.All metapackage.

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)
{
    BuildWebHost(args).Run();
}

public static IWebHost BuildWebHost(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");
            });
        })
        .Build();

If you need to configure Kestrel options, call UseKestrel in Program.cs as shown in the following example:

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

public static IWebHost BuildWebHost(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");
            });
        })
        .Build();

Kestrel options

The Kestrel web server has constraint configuration options that are especially useful in Internet-facing deployments. Here are some of the limits you can set:

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

You set these constraints and others in the Limits property of the KestrelServerOptions class. The Limits property holds an instance of the KestrelServerLimits class.

Maximum client connections

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

.UseKestrel(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");
    });
})

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.

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

Maximum request body size

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

The recommended way 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 entire application, every request:

.UseKestrel(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");
    });
})

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 try to configure the limit on a request after the application has started reading the request. There's an IsReadOnly property that tells you if the MaxRequestBodySize property is in read-only state, meaning it's too late to configure the limit.

Minimum request body data rate

Kestrel checks every second if data is coming in 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:

.UseKestrel(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");
    });
})

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));

For information about other Kestrel options, see the following classes:

Endpoint configuration

By default ASP.NET Core binds to http://localhost:5000. You configure URL prefixes and ports for Kestrel to listen on by calling Listen or ListenUnixSocket methods on KestrelServerOptions. (UseUrls, the urls command-line argument, and the ASPNETCORE_URLS environment variable also work but have the limitations noted later in this article.)

Bind to a TCP socket

The Listen method binds to a TCP socket, and an options lambda lets you configure an SSL certificate:

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

public static IWebHost BuildWebHost(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");
            });
        })
        .Build();

Notice how this example configures SSL for a particular endpoint by using ListenOptions. You can use the same API to configure other Kestrel settings for particular 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

You can listen on a Unix socket for improved performance with Nginx, as shown in this example:

.UseKestrel(options =>
{
    options.ListenUnixSocket("/tmp/kestrel-test.sock");
    options.ListenUnixSocket("/tmp/kestrel-test.sock", listenOptions =>
    {
        listenOptions.UseHttps("testCert.pfx", "testpassword");
    });
})

Port 0

If you specify port number 0, Kestrel dynamically binds to an available port. The following example shows how to determine which port Kestrel actually bound to at runtime:

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

    app.UseStaticFiles();

    app.Run(async (context) =>
    {
        context.Response.ContentType = "text/html";
        await context.Response
            .WriteAsync("<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>");
    });
}

UseUrls limitations

You can configure endpoints by calling the UseUrls method or using the urls command-line argument or the ASPNETCORE_URLS environment variable. These methods are useful if you want your code to work with servers other than Kestrel. However, be aware of these limitations:

  • You can't use SSL with these methods.
  • If you use both the Listen method and UseUrls, the Listen endpoints override the UseUrls endpoints.

Endpoint configuration for IIS

If you use IIS, the URL bindings for IIS override any bindings that you set by calling either Listen or UseUrls. For more information, see Introduction to ASP.NET Core Module.

URL prefixes

If you call UseUrls or use the urls command-line argument 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 you configure URL bindings by 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 +, are not special. Anything that's not a recognized IP address or "localhost" will bind to all IPv4 and IPv6 IPs. If you need to bind different host names to different ASP.NET Core applications on the same port, use HTTP.sys or a reverse proxy server such as IIS, Nginx, or Apache.

    Warning

    If you're not using a reverse proxy with host filtering enabled, you must enable host filtering.

  • "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 tries 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.

Host filtering

While Kestrel supports configuration based on prefixes such as http://example.com:5000, it largely ignores the host name. 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.

There are two possible workarounds:

  • Host behind a reverse proxy with host header filtering. This was the only supported scenario for Kestrel in ASP.NET Core 1.x.
  • Use a middleware to filter requests by the host header. A sample middleware follows:
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 the diagnostic middleware (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 preceding middleware expects an AllowedHosts key in appsettings.<EnvironmentName>.json. This key's value is a semicolon-delimited list of host names without the port numbers. Include the AllowedHosts key-value pair in appsettings.Production.json:

{
  "AllowedHosts": "example.com"
}

The localhost configuration file, appsettings.Development.json, looks like this:

{
  "AllowedHosts": "localhost"
}

Next steps

For more information, see the following resources: