使用 Nginx 在 Linux 上托管 ASP.NET Core

作者:Sourabh Shirhatti

本指南介绍如何在 Ubuntu 16.04 服务器上设置生产就绪 ASP.NET Core 环境。 这些说明可能适用于较新版本的 Ubuntu,但尚未使用较新版本进行测试。

有关 ASP.NET Core 支持的其他 Linux 分配的信息,请参阅 Linux 上 .NET Core 的先决条件

备注

对于 Ubuntu 14.04,建议使用 supervisord 作为监视 Kestrel 进程的解决方案。 systemd 不适用于 Ubuntu 14.04。 有关 Ubuntu 14.04 的说明,请参阅本主题的以前版本

本指南:

  • 将现有 ASP.NET Core 应用置于反向代理服务器后方。
  • 设置反向代理服务器,以便将请求转发到 Kestrel Web 服务器。
  • 确保 Web 应用在启动时作为守护程序运行。
  • 配置进程管理工具以帮助重新启动 Web 应用。

先决条件

  • 使用具有 sudo 特权的标准用户帐户访问 Ubuntu 16.04 服务器。
  • 服务器上安装了最新的非预览版 .NET 运行时
  • 一个现有 ASP.NET Core 应用。

升级共享框架后,可随时重启服务器托管的 ASP.NET Core 应用。

通过应用发布和复制

配置应用以进行依赖框架的部署

如果应用在本地运行,且未配置为建立安全连接 (HTTPS),则采用以下任一方法:

  • 配置应用,以处理安全的本地连接。 有关详细信息,请参阅 HTTPS 配置部分。
  • Properties/launchSettings.json 文件的 applicationUrl 属性中删除 https://localhost:5001(如果存在)。

在开发环境中运行 dotnet publish,将应用打包到可在服务器上运行的目录中(例如 bin/Release/{TARGET FRAMEWORK MONIKER}/publish,其中 {TARGET FRAMEWORK MONIKER} 占位符表示目标框架名字对象/TFM):

dotnet publish --configuration Release

如果不希望维护服务器上的 .NET Core 运行时,还可将应用发布为独立部署

使用集成到组织工作流的工具(例如 SCPSFTP)将 ASP.NET Core 应用复制到服务器。 通常可在 var 目录(例如 var/www/helloapp)下找到 Web 应用。

备注

在生产部署方案中,持续集成工作流会执行发布应用并将资产复制到服务器的工作。

测试应用:

  1. 在命令行中运行应用:dotnet <app_assembly>.dll
  2. 在浏览器中,导航到 http://<serveraddress>:<port> 以确认应用在 Linux 本地正常运行。

配置反向代理服务器

反向代理是为动态 Web 应用提供服务的常见设置。 反向代理终止 HTTP 请求,并将其转发到 ASP.NET Core 应用。

使用反向代理服务器

Kestrel 非常适合从 ASP.NET Core 提供动态内容。 但是,Web 服务功能不像服务器(如 IIS、Apache 或 Nginx)那样功能丰富。 反向代理服务器可以卸载 HTTP 服务器的工作负载,如提供静态内容、缓存请求、压缩请求和 HTTPS 终端。 反向代理服务器可能驻留在专用计算机上,也可能与 HTTP 服务器一起部署。

鉴于此指南的目的,使用 Nginx 的单个实例。 它与 HTTP 服务器一起运行在同一服务器上。 根据要求,可以选择不同的设置。

由于请求通过反向代理转发,因此使用 Microsoft.AspNetCore.HttpOverrides 包中的转接头中间件。 此中间件使用 X-Forwarded-Proto 标头来更新 Request.Scheme,使重定向 URI 和其他安全策略能够正常工作。

转接头中间件应在其他中间件之前运行。 此顺序可确保依赖于转接头信息的中间件可以使用标头值进行处理。 若要在诊断和错误处理中间件后运行转接头中间件,请参阅转接头中间件顺序

调用其他中间件之前,请先在 Startup.Configure 的基础上调用 UseForwardedHeaders 方法。 配置中间件以转接 X-Forwarded-ForX-Forwarded-Proto 标头:

using Microsoft.AspNetCore.HttpOverrides;

...

app.UseForwardedHeaders(new ForwardedHeadersOptions
{
    ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
});

app.UseAuthentication();

如果没有为中间件指定 ForwardedHeadersOptions,则要转接的默认标头为 None

默认情况下,在环回地址 (127.0.0.0/8, [::1])(包括标准 localhost 地址 (127.0.0.1))上运行的代理受信任。 如果组织内的其他受信任代理或网络处理 Internet 与 Web 服务器之间的请求,请使用 ForwardedHeadersOptions 将其添加到 KnownProxiesKnownNetworks 的列表。 以下示例会将 IP 地址为 10.0.0.100 的受信任代理服务器添加到 Startup.ConfigureServices 中的转接头中间件 KnownProxies

using System.Net;

...

services.Configure<ForwardedHeadersOptions>(options =>
{
    options.KnownProxies.Add(IPAddress.Parse("10.0.0.100"));
});

有关详细信息,请参阅 配置 ASP.NET Core 以使用代理服务器和负载均衡器

安装 Nginx

使用 apt-get 安装 Nginx。 安装程序将创建一个 systemd init 脚本,该脚本运行 Nginx,作为系统启动时的守护程序。 按照以下网站上的 Ubuntu 安装说明操作:Nginx:官方 Debian/Ubuntu 包

备注

如果需要可选 Nginx 模块,则可能需要从源代码生成 Nginx。

因为是首次安装 Nginx,通过运行以下命令显式启动:

sudo service nginx start

确认浏览器显示 Nginx 的默认登陆页。 可在 http://<server_IP_address>/index.nginx-debian.html 访问登陆页面。

配置 Nginx

若要将 Nginx 配置为反向代理以将 HTTP 请求转发到 ASP.NET Core 应用程序,请修改 /etc/nginx/sites-available/default。 在文本编辑器中打开它,并将内容替换为以下代码片段:

server {
    listen        80;
    server_name   example.com *.example.com;
    location / {
        proxy_pass         http://127.0.0.1:5000;
        proxy_http_version 1.1;
        proxy_set_header   Upgrade $http_upgrade;
        proxy_set_header   Connection keep-alive;
        proxy_set_header   Host $host;
        proxy_cache_bypass $http_upgrade;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto $scheme;
    }
}

如果应用是 SignalR 或 Blazor Server 应用,请分别参阅 ASP.NET Core SignalR 生产托管和缩放托管和部署 ASP.NET Core Blazor Server 以了解详细信息。

当没有匹配的 server_name 时,Nginx 使用默认服务器。 如果没有定义默认服务器,则配置文件中的第一台服务器是默认服务器。 最佳做法是,添加一个特定的默认服务器,它会在配置文件中返回状态代码 444。 默认的服务器配置示例是:

server {
    listen   80 default_server;
    # listen [::]:80 default_server deferred;
    return   444;
}

使用上述配置文件和默认服务器,Nginx 接受主机标头 example.com*.example.com 端口 80 上的公共流量。 与这些主机不匹配的请求不会转发到 Kestrel。 Nginx 将匹配的请求转发到 http://127.0.0.1:5000 中的 Kestrel。 有关详细信息,请参阅 nginx 如何处理请求。 要更改 Kestrel 的 IP/端口,请参阅 Kestrel:终结点配置

使用上述配置文件和默认服务器,Nginx 接受主机标头 example.com*.example.com 端口 80 上的公共流量。 与这些主机不匹配的请求不会转发到 Kestrel。 Nginx 将匹配的请求转发到 http://127.0.0.1:5000 中的 Kestrel。 有关详细信息,请参阅 nginx 如何处理请求。 要更改 Kestrel 的 IP/端口,请参阅 Kestrel:终结点配置

警告

未能指定正确的 server_name 指令会公开应用的安全漏洞。 如果可控制整个父域(区别于易受攻击的 *.com),则子域通配符绑定(例如,*.example.com)不具有此安全风险。 有关详细信息,请参阅 rfc7230 第 5.4 条

完成配置 Nginx 后,运行 sudo nginx -t 来验证配置文件的语法。 如果配置文件测试成功,可以通过运行 sudo nginx -s reload 强制 Nginx 选取更改。

要直接在服务器上运行应用:

  1. 请导航到应用目录。
  2. 运行应用:dotnet <app_assembly.dll>,其中 app_assembly.dll 是应用的程序集文件名。

如果应用在服务器上运行,但无法通过 Internet 响应,请检查服务器的防火墙,确认端口 80 已打开。 如果使用 Azure Ubuntu VM,请添加启用入站端口 80 流量的网络安全组 (NSG) 规则。 不需要启用出站端口 80 规则,因为启用入站规则后会自动许可出站流量。

完成应用测试后,请在命令提示符处按 Ctrl + C 关闭应用。

监视应用

服务器设置为将对 http://<serveraddress>:80 发起的请求转发到在 http://127.0.0.1:5000 中的 Kestrel 上运行的 ASP.NET Core 应用。 但是,未将 Nginx 设置为管理 Kestrel 进程。 systemd 可用于创建服务文件以启动和监视基础 Web 应用。 systemd 是一个初始系统,可以提供启动、停止和管理进程的许多强大的功能。

创建服务文件

创建服务定义文件:

sudo nano /etc/systemd/system/kestrel-helloapp.service

以下示例是应用的服务文件:

[Unit]
Description=Example .NET Web API App running on Ubuntu

[Service]
WorkingDirectory=/var/www/helloapp
ExecStart=/usr/bin/dotnet /var/www/helloapp/helloapp.dll
Restart=always
# Restart service after 10 seconds if the dotnet service crashes:
RestartSec=10
KillSignal=SIGINT
SyslogIdentifier=dotnet-example
User=www-data
Environment=ASPNETCORE_ENVIRONMENT=Production
Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false

[Install]
WantedBy=multi-user.target

在前面的示例中,管理服务的用户由 User 选项指定。 用户 (www-data) 必须存在并且拥有正确应用文件的所有权。

使用 TimeoutStopSec 配置在收到初始中断信号后等待应用程序关闭的持续时间。 如果应用程序在此时间段内未关闭,则将发出 SIGKILL 以终止该应用程序。 提供作为无单位秒数的值(例如,150)、时间跨度值(例如,2min 30s)或 infinity 以禁用超时。 TimeoutStopSec 默认为管理器配置文件 (systemd-system.conf, system.conf.d, systemd-user.conf, user.conf.d) 中 DefaultTimeoutStopSec 的值。 大多数分发版的默认超时时间为 90 秒。

# The default value is 90 seconds for most distributions.
TimeoutStopSec=90

Linux 具有区分大小写的文件系统。 将 ASPNETCORE_ENVIRONMENT 设置为 Production 时,将搜索配置文件 appsettings.Production.json,而不搜索 appsettings.production.json

必须转义某些值(例如,SQL 连接字符串)以供配置提供程序读取环境变量。 使用以下命令生成适当的转义值以供在配置文件中使用:

systemd-escape "<value-to-escape>"

环境变量名不支持冒号 (:) 分隔符。 使用双下划线 (__) 代替冒号。 环境变量读入配置时,环境变量配置提供程序将双下划线转换为冒号。 以下示例中,连接字符串密钥 ConnectionStrings:DefaultConnectionConnectionStrings__DefaultConnection 形式设置到服务定义文件中:

环境变量名不支持冒号 (:) 分隔符。 使用双下划线 (__) 代替冒号。 环境变量读入配置时,环境变量配置提供程序将双下划线转换为冒号。 以下示例中,连接字符串密钥 ConnectionStrings:DefaultConnectionConnectionStrings__DefaultConnection 形式设置到服务定义文件中:

Environment=ConnectionStrings__DefaultConnection={Connection String}

保存该文件并启用该服务。

sudo systemctl enable kestrel-helloapp.service

启用该服务,并确认它正在运行。

sudo systemctl start kestrel-helloapp.service
sudo systemctl status kestrel-helloapp.service

◝ kestrel-helloapp.service - Example .NET Web API App running on Ubuntu
    Loaded: loaded (/etc/systemd/system/kestrel-helloapp.service; enabled)
    Active: active (running) since Thu 2016-10-18 04:09:35 NZDT; 35s ago
Main PID: 9021 (dotnet)
    CGroup: /system.slice/kestrel-helloapp.service
            └─9021 /usr/local/bin/dotnet /var/www/helloapp/helloapp.dll

在配置了反向代理并通过 systemd 管理 Kestrel 后,Web 应用现已完全配置,并能在本地计算机上的浏览器中从 http://localhost 进行访问。 也可以从远程计算机进行访问,同时限制可能进行阻止的任何防火墙。 检查响应标头,Server 标头显示由 Kestrel 所提供的 ASP.NET Core 应用。

HTTP/1.1 200 OK
Date: Tue, 11 Oct 2016 16:22:23 GMT
Server: Kestrel
Keep-Alive: timeout=5, max=98
Connection: Keep-Alive
Transfer-Encoding: chunked

查看日志

使用 Kestrel 的 Web 应用是通过 systemd 进行管理的,因此所有事件和进程都被记录到集中日志。 但是,此日志包含由 systemd 管理的所有服务和进程的全部条目。 若要查看特定于 kestrel-helloapp.service 的项,请使用以下命令:

sudo journalctl -fu kestrel-helloapp.service

有关进一步筛选,使用时间选项(如 --since today--until 1 hour ago)或这些选项的组合可以减少返回的条目数。

sudo journalctl -fu kestrel-helloapp.service --since "2016-10-18" --until "2016-10-18 04:00"

数据保护

ASP.NET Core 数据保护堆栈由多个 ASP.NET Core 中间件(包括 cookie 中间件等身份验证中间件)和跨站点请求伪造 (CSRF) 保护使用。 即使用户代码不调用数据保护 API,也应该配置数据保护,以创建持久的加密密钥存储。 如果不配置数据保护,则密钥存储在内存中。重启应用时,密钥会被丢弃。

如果密钥环存储于内存中,则在应用重启时:

  • 所有基于 cookie 的身份验证令牌都无效。
  • 用户需要在下一次请求时再次登录。
  • 无法再解密使用密钥环保护的任何数据。 这可能包括 CSRF 令牌ASP.NET Core MVC TempData cookie

若要配置数据保护以持久保存并加密密钥环,请参阅:

较长的请求标头字段

代理服务器默认设置通常将请求标头字段限制为 4 K 或 8 K,具体取决于平台。 某些应用可能需要超过默认值的字段(例如,使用 Azure Active Directory 的应用)。 如果需要更长的字段,则代理服务器的默认设置需要进行调整。 要应用的值具体取决于方案。 有关详细信息,请参见服务器文档。

警告

除非必要,否则不要提高代理缓冲区的默认值。 提高这些值将增加缓冲区溢出的风险和恶意用户的拒绝服务 (DoS) 攻击风险。

保护应用

启用 AppArmor

Linux 安全模块 (LSM) 是一个框架,它是自 Linux 2.6 后的 Linux kernel 的一部分。 LSM 支持安全模块的不同实现。 AppArmor 是实现强制访问控制系统的 LSM,它允许将程序限制在一组有限的资源内。 确保已启用并成功配置 AppArmor。

配置防火墙

关闭所有未使用的外部端口。 通过为配置防火墙提供 CLI,不复杂的防火墙 (ufw) 为 iptables 提供了前端。

警告

如果未正确配置,防火墙将阻止对整个系统的访问。 在使用 SSH 进行连接时,未能指定正确的 SSH 端口最终会将你关在系统之外。 默认端口为 22。 有关详细信息,请参阅 ufw 简介手册

安装 ufw,并将其配置为允许所需任何端口上的流量。

sudo apt-get install ufw

sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

sudo ufw enable

保护 Nginx

更改 Nginx 响应名称

编辑 src/http/ngx_http_header_filter_module.c

static char ngx_http_server_string[] = "Server: Web Server" CRLF;
static char ngx_http_server_full_string[] = "Server: Web Server" CRLF;

配置选项

用其他必需模块配置服务器。 请考虑使用 ModSecurity 等 Web 应用防火墙来加强对应用的保护。

HTTPS 配置

配置应用,以进行安全的 (HTTPS) 本地连接

dotnet run 命令使用应用的 Properties/launchSettings.json 文件,该文件将应用配置为侦听 applicationUrl 属性提供的 URL。 例如,https://localhost:5001;http://localhost:5000

使用以下方法之一配置应用,使其在开发过程中将证书用于 dotnet run 命令或开发环境(Visual Studio Code 中的 F5 或 Ctrl+F5):

配置反向代理,以便进行安全 (HTTPS) 客户端连接

警告

本部分中的安全配置是一种常规配置,可以作为进一步进行自定义的起点。 我们无法为第三方工具、服务器和操作系统提供支持。 如果使用本部分中的配置,请自行承担风险。 有关更多信息,请访问以下资源:

  • 通过指定由受信任的证书颁发机构 (CA) 颁发的有效证书来配置服务器,以侦听端口 443 上的 HTTPS 流量。

  • 通过采用以下“/etc/nginx/nginx.conf”文件中所示的某些做法来增强安全保护。

  • 下面的示例未将服务器配置为重定向不安全的请求。 建议使用 HTTPS 重定向中间件。 有关详细信息,请参阅 在 ASP.NET Core 中强制实施 HTTPS

    备注

    对于由服务器配置(而非 HTTPS 重定向中间件)处理安全重定向的开发环境,建议使用临时重定向 (302) 替代永久性重定向 (301)。 链接缓存会导致开发环境中的行为不稳定。

  • 添加 Strict-Transport-Security (HSTS) 标头可确保由客户端发起的所有后续请求都通过 HTTPS。 有关设置 Strict-Transport-Security 标头的指南,请参阅 在 ASP.NET Core 中强制实施 HTTPS

  • 如果将来将禁用 HTTPS,请使用以下方法之一:

    • 不要添加 HSTS 标头。
    • 选择短的 max-age 值。

添加 /etc/nginx/proxy.conf 配置文件:

proxy_redirect          off;
proxy_set_header        Host $host;
proxy_set_header        X-Real-IP $remote_addr;
proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header        X-Forwarded-Proto $scheme;
client_max_body_size    10m;
client_body_buffer_size 128k;
proxy_connect_timeout   90;
proxy_send_timeout      90;
proxy_read_timeout      90;
proxy_buffers           32 4k;

将 /etc/nginx/nginx.conf 配置文件的内容替换为以下文件。 示例包含一个配置文件中的 httpserver 部分。

http {
    include        /etc/nginx/proxy.conf;
    limit_req_zone $binary_remote_addr zone=one:10m rate=5r/s;
    server_tokens  off;

    sendfile on;
    # Adjust keepalive_timeout to the lowest possible value that makes sense 
    # for your use case.
    keepalive_timeout   29;
    client_body_timeout 10; client_header_timeout 10; send_timeout 10;

    upstream helloapp{
        server 127.0.0.1:5000;
    }

    server {
        listen                    443 ssl http2;
        listen                    [::]:443 ssl http2;
        server_name               example.com *.example.com;
        ssl_certificate           /etc/ssl/certs/testCert.crt;
        ssl_certificate_key       /etc/ssl/certs/testCert.key;
        ssl_session_timeout       1d;
        ssl_protocols             TLSv1.2 TLSv1.3;
        ssl_prefer_server_ciphers off;
        ssl_ciphers               ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
        ssl_session_cache         shared:SSL:10m;
        ssl_session_tickets       off;
        ssl_stapling              off;

        add_header X-Frame-Options DENY;
        add_header X-Content-Type-Options nosniff;

        #Redirects all traffic
        location / {
            proxy_pass http://helloapp;
            limit_req  zone=one burst=10 nodelay;
        }
    }
}

备注

Blazor WebAssembly 应用需要更大的 burst 参数值才能容纳应用发出的更大量的请求。 有关详细信息,请参阅 托管和部署 ASP.NET Core Blazor WebAssembly

备注

上面的示例禁用了在线证书状态协议 (OCSP) 装订。 如果启用,请确认证书支持该功能。 有关启用 OCSP 的详细信息和指南,请参阅模块 ngx_http_ssl_module(Nginx 文档)一文中的以下属性:

  • ssl_stapling
  • ssl_stapling_file
  • ssl_stapling_responder
  • ssl_stapling_verify

保护 Nginx 免受点击劫持的侵害

点击劫持(也称为 UI 伪装攻击)是一种恶意攻击,其中网站访问者会上当受骗,从而导致在与当前要访问的页面不同的页面上单击链接或按钮。 使用 X-FRAME-OPTIONS 可保护网站。

缓解点击劫持攻击:

  1. 编辑 nginx.conf 文件:

    sudo nano /etc/nginx/nginx.conf
    

    添加行:add_header X-Frame-Options "SAMEORIGIN";

  2. 保存该文件。

  3. 重启 Nginx。

MIME 类型探查

此标头可阻止大部分浏览器通过 MIME 方式探查来自已声明内容类型的响应,因为标头会指示浏览器不要替代响应内容类型。 使用 nosniff 选项后,如果服务器认为内容是“text/html”,则浏览器将其显示为“text/html”。

  1. 编辑 nginx.conf 文件:

    sudo nano /etc/nginx/nginx.conf
    

    添加行:add_header X-Content-Type-Options "nosniff";

  2. 保存该文件。

  3. 重启 Nginx。

其他 Nginx 建议

在服务器上升级共享框架后,重启服务器托管的 ASP.NET Core 应用。

其他资源