Cấu hình Endpoints cho Kestrel web server
Tổng quan
Kestrel endpoints (điểm cuối) cung cấp cơ sở hạ tầng để lắng nghe các request đến và định tuyến chúng đến middleware phù hợp. Một endpoint kết hợp:
- Address (địa chỉ): Network interface mà server lắng nghe (ví dụ: TCP port)
- Protocol (giao thức): Giao thức giao tiếp (HTTP/1.1, HTTP/2, HTTP/3)
- Security (bảo mật): Cấu hình HTTPS tùy chọn sử dụng URL scheme
httpshoặc phương thứcUseHttps
Cấu hình Endpoint mặc định
Các project ASP.NET Core mới bind vào các endpoint mặc định:
- Port HTTP ngẫu nhiên: 5000-5300
- Port HTTPS ngẫu nhiên: 7000-7300
Các port này được lưu trong file Properties/launchSettings.json (chỉ dùng cho phát triển local).
Nếu không có cấu hình endpoint nào, Kestrel bind vào http://localhost:5000.
Cấu hình Endpoints
Chỉ định endpoints bằng URLs
Bạn có thể cấu hình endpoints bằng:
- Biến môi trường
ASPNETCORE_URLS - Tham số dòng lệnh
--urls - Khóa cấu hình host
urls - Extension method
UseUrls - Thuộc tính
WebApplication.Urls
Ví dụ định dạng URL
# Địa chỉ IPv4 với port http://65.55.39.10:80/ # Địa chỉ IPv6 với port http://[0:0:0:0:0:ffff:4137:270a]:80/ # Wildcard với port http://*:80/ http://contoso.com:80/ # Localhost http://localhost:5000/ http://127.0.0.1:5000/ http://[::1]:5000/ # Nhiều endpoints (phân tách bằng dấu chấm phẩy) http://*:5000;http://localhost:5001;https://hostname:5002
Chỉ định chỉ ports
Dùng các khóa cấu hình HTTP_PORTS và HTTPS_PORTS như biến môi trường hoặc trong file cấu hình:
ASPNETCORE_HTTP_PORTS=80;8080 ASPNETCORE_HTTPS_PORTS=443;8081
Đây là cách viết tắt của:
ASPNETCORE_URLS=http://*:80/;http://*:8080/;https://*:443/;https://*:8081/
Tạo endpoints trong appsettings.json
Cấu hình endpoints trong phần Kestrel:Endpoints:
{
"Kestrel": {
"Endpoints": {
"MyHttpEndpoint": {
"Url": "http://localhost:8080"
}
}
}
}Tải lại endpoints từ configuration
Mặc định, cấu hình endpoint có thể được tải lại khi source cấu hình thay đổi. Điều này có thể bị vô hiệu hóa bằng phương thức Configure(IConfiguration, Boolean).
Khi có thay đổi được báo hiệu:
- Cấu hình mới được so sánh với phiên bản cũ
- Các endpoints không có thay đổi cấu hình sẽ không bị sửa đổi
- Các endpoints bị xóa hoặc sửa đổi có 5 giây để hoàn thành xử lý và tắt
- Các endpoints mới hoặc được sửa đổi sẽ được khởi động
Lưu ý: Các client kết nối đến endpoints được sửa đổi có thể bị ngắt kết nối hoặc bị từ chối trong quá trình khởi động lại.
ConfigurationLoader
Phương thức Configure trả về một KestrelConfigurationLoader cho phép bổ sung cài đặt endpoint:
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel((context, serverOptions) =>
{
var kestrelSection = context.Configuration.GetSection("Kestrel");
serverOptions.Configure(kestrelSection)
.Endpoint("HTTPS", listenOptions =>
{
// ...
});
});Định nghĩa endpoints trong code
Sử dụng các phương thức KestrelServerOptions:
ListenListenLocalhostListenAnyIPListenUnixSocketListenNamedPipe
Lưu ý: Khi cả Listen và UseUrls được sử dụng đồng thời, các endpoint Listen ghi đè các endpoint UseUrls.
Bind vào TCP socket
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel((context, serverOptions) =>
{
serverOptions.Listen(IPAddress.Loopback, 5000);
serverOptions.Listen(IPAddress.Loopback, 5001, listenOptions =>
{
listenOptions.UseHttps("testCert.pfx", "testPassword");
});
});Bind vào Unix socket
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel((context, serverOptions) =>
{
serverOptions.ListenUnixSocket("/tmp/kestrel-test.sock");
});Trong cấu hình Nginx:
proxy_pass http://unix:/tmp/kestrel-test.sock:/;
Đảm bảo socket có thể ghi:
chmod go+w /tmp/kestrel-test.sock
Cấu hình endpoint defaults
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel(serverOptions =>
{
serverOptions.ConfigureEndpointDefaults(listenOptions =>
{
// ...
});
});Lưu ý: Các endpoints được tạo bằng cách gọi Listen trước khi gọi ConfigureEndpointDefaults sẽ không áp dụng các defaults.
Sử dụng dynamic port binding
Khi port 0 được chỉ định, Kestrel tự động bind vào một port khả dụng:
app.Run(async (context) =>
{
var serverAddressFeature = context.Features.Get<IServerAddressesFeature>();
if (serverAddressFeature is not null)
{
var listenAddresses = string.Join(", ", serverAddressFeature.Addresses);
// ...
}
});Giới hạn: Dynamic port binding không khả dụng với:
ListenLocalhost- HTTP/1.1 hoặc HTTP/2 dựa trên TCP được bind cùng với HTTP/3 dựa trên QUIC
Cấu hình HTTPS
HTTPS bảo mật các endpoints bằng Transport Layer Security (TLS). Kestrel yêu cầu một chứng chỉ TLS.
Cấu hình HTTPS trong appsettings.json
{
"Kestrel": {
"Endpoints": {
"Http": {
"Url": "http://localhost:5000"
},
"HttpsInlineCertFile": {
"Url": "https://localhost:5001",
"Certificate": {
"Path": "<đường dẫn đến file .pfx>",
"Password": "$CREDENTIAL_PLACEHOLDER$"
}
},
"HttpsInlineCertAndKeyFile": {
"Url": "https://localhost:5002",
"Certificate": {
"Path": "<đường dẫn đến file .pem/.crt>",
"KeyPath": "<đường dẫn đến file .key>",
"Password": "$CREDENTIAL_PLACEHOLDER$"
}
},
"HttpsInlineCertStore": {
"Url": "https://localhost:5003",
"Certificate": {
"Subject": "<subject; bắt buộc>",
"Store": "<certificate store; bắt buộc>",
"Location": "<location; mặc định là CurrentUser>",
"AllowInvalid": "<true hoặc false; mặc định là false>"
}
},
"HttpsDefaultCert": {
"Url": "https://localhost:5004"
}
},
"Certificates": {
"Default": {
"Path": "<đường dẫn đến file .pfx>",
"Password": "$CREDENTIAL_PLACEHOLDER$"
}
}
}
}⚠️ Cảnh báo: Mật khẩu chứng chỉ không nên lưu dưới dạng plain text trong appsettings.json. Hãy dùng:
- Phát triển: Protect secrets in development
- Production: Azure Key Vault configuration provider
Ghi chú về Schema
- Tên endpoint không phân biệt hoa/thường
- Tham số
Urllà bắt buộc cho mỗi endpoint - Các endpoints định nghĩa trong configuration thay thế cấu hình
Urlscấp cao nhất - Các endpoints định nghĩa trong code qua
Listencộng dồn với endpoints trong configuration - Phần
Certificatelà tùy chọn; nếu không có, sẽ dùng defaults từCertificates:Default, rồi đến chứng chỉ development
Nguồn chứng chỉ
Chứng chỉ có thể được tải từ:
- File PFX:
PathvàPassword - File PEM/CRT và Key:
Path,KeyPathvàPassword - Certificate store:
SubjectvàStore
Cấu hình client certificates trong appsettings.json
{
"Kestrel": {
"Endpoints": {
"MyHttpsEndpoint": {
"Url": "https://localhost:5001",
"ClientCertificateMode": "AllowCertificate",
"Certificate": {
"Path": "<đường dẫn đến file .pfx>",
"Password": "$CREDENTIAL_PLACEHOLDER$"
}
}
}
}
}Mặc định: ClientCertificateMode.NoCertificate (không yêu cầu client certificate)
Cấu hình SSL/TLS protocols trong appsettings.json
{
"Kestrel": {
"Endpoints": {
"MyHttpsEndpoint": {
"Url": "https://localhost:5001",
"SslProtocols": ["Tls12", "Tls13"],
"Certificate": {
"Path": "<đường dẫn đến file .pfx>",
"Password": "$CREDENTIAL_PLACEHOLDER$"
}
}
}
}
}Mặc định: SslProtocols.None (dùng OS defaults để có giao thức tốt nhất)
Cấu hình HTTPS trong code
Dùng API Listen với UseHttps:
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel((context, serverOptions) =>
{
serverOptions.Listen(IPAddress.Loopback, 5000);
serverOptions.Listen(IPAddress.Loopback, 5001, listenOptions =>
{
listenOptions.UseHttps("testCert.pfx", "testPassword");
});
});Cấu hình client certificates trong code
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel(serverOptions =>
{
serverOptions.ConfigureHttpsDefaults(listenOptions =>
{
listenOptions.ClientCertificateMode = ClientCertificateMode.AllowCertificate;
});
});Cấu hình HTTPS defaults trong code
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel(serverOptions =>
{
serverOptions.ConfigureHttpsDefaults(listenOptions =>
{
// Cấu hình HTTPS defaults cho tất cả endpoints
});
});Lưu ý: Các endpoints được tạo bằng cách gọi Listen trước khi gọi ConfigureHttpsDefaults sẽ không áp dụng defaults.
Cấu hình SSL/TLS protocols trong code
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel(serverOptions =>
{
serverOptions.ConfigureHttpsDefaults(listenOptions =>
{
listenOptions.SslProtocols = SslProtocols.Tls13;
});
});Cấu hình TLS cipher suites filter trong code
Trên Linux, dùng CipherSuitesPolicy để lọc TLS handshakes theo từng kết nối:
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel((context, serverOptions) =>
{
serverOptions.ConfigureHttpsDefaults(listenOptions =>
{
listenOptions.OnAuthenticate = (context, sslOptions) =>
{
sslOptions.CipherSuitesPolicy = new CipherSuitesPolicy(
new[]
{
TlsCipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
TlsCipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
});
};
});
});Cấu hình Server Name Indication (SNI)
SNI (Chỉ thị tên server) cho phép lưu trữ nhiều domain trên cùng IP và port. Client gửi tên host trong quá trình TLS handshake, cho phép server cung cấp chứng chỉ đúng.
Cấu hình SNI trong appsettings.json
{
"Kestrel": {
"Endpoints": {
"MySniEndpoint": {
"Url": "https://*",
"SslProtocols": ["Tls11", "Tls12"],
"Sni": {
"a.example.org": {
"Protocols": "Http1AndHttp2",
"SslProtocols": ["Tls11", "Tls12", "Tls13"],
"Certificate": {
"Subject": "<subject; bắt buộc>",
"Store": "<certificate store; bắt buộc>"
},
"ClientCertificateMode": "NoCertificate"
},
"*.example.org": {
"Certificate": {
"Path": "<đường dẫn đến file .pfx>",
"Password": "$CREDENTIAL_PLACEHOLDER$"
}
},
"*": {
"Protocols": "Http1"
}
}
}
},
"Certificates": {
"Default": {
"Path": "<đường dẫn đến file .pfx>",
"Password": "$CREDENTIAL_PLACEHOLDER$"
}
}
}
}SNI có thể ghi đè:
Certificate: Nguồn chứng chỉProtocols: Các giao thức HTTP được phépSslProtocols: Các giao thức SSL được phépClientCertificateMode: Yêu cầu client certificate
Wildcard matching
- Khớp chính xác:
a.example.orgkhớp vớia.example.org - Tiền tố wildcard:
*.example.orgkhớp vớib.example.orgvàc.example.org(pattern dài nhất thắng) - Wildcard đầy đủ:
*khớp với tất cả
Cấu hình SNI với code
SNI với ServerCertificateSelector
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel(serverOptions =>
{
serverOptions.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)
{
["localhost"] = localhostCert,
["example.com"] = exampleCert,
["sub.example.com"] = subExampleCert
};
httpsOptions.ServerCertificateSelector = (connectionContext, name) =>
{
if (name is not null && certs.TryGetValue(name, out var cert))
{
return cert;
}
return exampleCert;
};
});
});
});SNI với TlsHandshakeCallbackOptions
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel(serverOptions =>
{
serverOptions.ListenAnyIP(5005, listenOptions =>
{
var localhostCert = CertificateLoader.LoadFromStoreCert(
"localhost", "My", StoreLocation.CurrentUser, allowInvalid: true);
var exampleCert = CertificateLoader.LoadFromStoreCert(
"example.com", "My", StoreLocation.CurrentUser, allowInvalid: true);
listenOptions.UseHttps(new TlsHandshakeCallbackOptions
{
OnConnection = context =>
{
if (string.Equals(context.ClientHelloInfo.ServerName, "localhost",
StringComparison.OrdinalIgnoreCase))
{
context.AllowDelayedClientCertificateNegotation = true;
return new ValueTask<SslServerAuthenticationOptions>(
new SslServerAuthenticationOptions
{
ServerCertificate = localhostCert
});
}
return new ValueTask<SslServerAuthenticationOptions>(
new SslServerAuthenticationOptions
{
ServerCertificate = exampleCert
});
}
});
});
});Cấu hình HTTP protocols
Kestrel hỗ trợ tất cả các phiên bản HTTP phổ biến được cấu hình qua enum HttpProtocols.
| Giá trị HttpProtocols | Giao thức được phép | Ghi chú |
|---|---|---|
Http1 | HTTP/1.1 | Có thể dùng có hoặc không có TLS |
Http2 | HTTP/2 | Không có TLS chỉ khi client hỗ trợ Prior Knowledge mode |
Http3 | HTTP/3 | Yêu cầu TLS |
Http1AndHttp2 | HTTP/1.1 & HTTP/2 | HTTP/2 yêu cầu TLS ALPN negotiation; mặc định về HTTP/1.1 nếu không có |
Http1AndHttp2AndHttp3 | HTTP/1.1, HTTP/2 & HTTP/3 | Request đầu tiên dùng HTTP/1.1 hoặc HTTP/2; header alt-svc kích hoạt nâng cấp HTTP/3 |
Mặc định: HttpProtocols.Http1AndHttp2
Giới hạn TLS cho HTTP/2
- Yêu cầu TLS 1.2 trở lên
- Renegotiation bị vô hiệu
- Compression bị vô hiệu
- Kích thước ephemeral key exchange tối thiểu:
- ECDHE: tối thiểu 224 bits
- DHE: tối thiểu 2,048 bits
Cấu hình HTTP protocols trong appsettings.json
Đặt HTTP/1.1 cho endpoint cụ thể:
{
"Kestrel": {
"Endpoints": {
"HttpsDefaultCert": {
"Url": "https://localhost:5001",
"Protocols": "Http1"
}
}
}
}Đặt giao thức mặc định cho tất cả endpoints:
{
"Kestrel": {
"EndpointDefaults": {
"Protocols": "Http1"
}
}
}Lưu ý: Các giao thức được chỉ định trong code ghi đè giá trị configuration.
Cấu hình HTTP protocols trong code
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel((context, serverOptions) =>
{
serverOptions.Listen(IPAddress.Any, 8000, listenOptions =>
{
listenOptions.UseHttps("testCert.pfx", "testPassword");
listenOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3;
});
});Tùy chỉnh named pipe endpoints của Kestrel (ASP.NET Core 10.0+)
Thuộc tính CreateNamedPipeServerStream trên named pipe options cho phép tùy chỉnh theo từng endpoint:
using System.IO.Pipes;
using System.Security.AccessControl;
using System.Security.Principal;
var builder = WebApplication.CreateBuilder();
builder.WebHost.ConfigureKestrel(options =>
{
options.ListenNamedPipe("defaultPipe");
options.ListenNamedPipe("securedPipe");
});
builder.WebHost.UseNamedPipes(options =>
{
options.CreateNamedPipeServerStream = (context) =>
{
var pipeName = context.NamedPipeEndPoint.PipeName;
switch (pipeName)
{
case "defaultPipe":
return NamedPipeTransportOptions.CreateDefaultNamedPipeServerStream(context);
case "securedPipe":
var allowSecurity = new PipeSecurity();
allowSecurity.AddAccessRule(
new PipeAccessRule("Users", PipeAccessRights.FullControl, AccessControlType.Allow));
return NamedPipeServerStreamAcl.Create(
pipeName, PipeDirection.InOut,
NamedPipeServerStream.MaxAllowedServerInstances,
PipeTransmissionMode.Byte, context.PipeOptions,
inBufferSize: 0, outBufferSize: 0, allowSecurity);
default:
throw new InvalidOperationException($"Unexpected pipe name: {pipeName}");
}
};
});
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();