线上服务分分钟给你表演一个“502 Bad Gateway”或者“Connection Refused”,到时候运维大哥找上门,哭都没地儿哭。今天咱们就把这层窗户纸捅破,把 Nginx 和 Kestrel 伺候得明明白白。

标题:

老铁们,是不是总遇到这种情况:本地 dotnet run 好好的,一上服务器,配了反向代理,不是 404 就是 502?甚至有时候连日志都看不出个所以然,简直是玄学运维。

别慌,今天墨夶就把当年被运维大佬按在地上摩擦后总结出来的“反向代理避坑宝典”掏出来。咱们不整虚的,直接上生产环境实测可用的硬核代码,注释写得比代码还多,保姆级教程,看完直接能抄作业!

一、痛点直击:为啥你的 ASP.NET Core 总是“连不上”?

做反向代理,最怕的就是“请求发出去了,响应回不来”或者“重定向到一个不存在的端口”。

这里面最大的坑在于:ASP.NET Core 的 Kestrel 服务器默认是只监听内部端口的,它不知道外面有个 Nginx 在替它挡子弹。 如果你不告诉它“我是被代理的”,它生成的链接(比如 Swagger 的地址、重定向的 Location)就会直接带上内部端口(比如 5000),用户拿着这个地址去访问,自然就 404 了。

这就跟“代购”一个道理:
Nginx 是代购(面对客户)。
Kestrel 是你(在背后发货)。
如果你不告诉代购你的收货地址是“代购仓库”,代购就会直接把你的家庭住址发给客户,这不乱套了吗?

二、保姆级配置:Nginx + ASP.NET Core 深度绑定

咱们先从最常用的 Nginx 开始。这配置不是随便抄的,每一行都有讲究。

Nginx 配置(/etc/nginx/sites-available/your-app)

这是生产环境的黄金配置,拿走不谢。

server 块:定义一个虚拟主机
server {
# 监听 80 端口,开启 SSL(443)是必须的,现在都 2026 年了,谁还用 HTTP 明文传输?
listen 80;
listen 443 ssl http2;

# ⚠️ 重点:这里的 server_name 必须和你申请 SSL 证书的域名一致!
# 如果不一致,HTTPS 握手直接失败,你会收到一个“连接被重置”的错误。
server_name api.yourdomain.com;

# 如果是 HTTPS,必须指定证书路径
# 这里我用的是 Let's Encrypt 的默认路径,你们根据实际情况改
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;

# ⚡️ 性能优化:开启 Gzip 压缩,减少传输体积,省流量还快
gzip on;
gzip_types text/plain application/json application/javascript text/css;

# 🛡️ 安全头:防止 XSS 和点击劫持,虽然不是核心,但能防扫描器骚扰
add_header X-Frame-Options "DENY";
add_header X-Content-Type-Options "nosniff";

# 📂 日志路径,出了问题第一件事就是看 error.log
# access_log /var/log/nginx/your-app.access.log;
error_log /var/log/nginx/your-app.error.log;

# 🚪 核心 Location:所有请求都转发给后端
location / {
    # ⚠️ 重点:proxy_pass 指向 Kestrel 监听的内部地址
    # 这里必须是 http,因为 Kestrel 默认不支持 HTTPS(除非你特地配了)
    proxy_pass http://localhost:5000;

    # ⚠️ 重点:必须重写 Host 头
    # 如果不写这行,后端收到的 Host 是 localhost:5000,导致生成的 URL 全是错的
    proxy_http_version 1.1;
    proxy_set_header Host host;

    # ⚠️ 重点:传递真实 IP
    # 如果不传,后端日志里全是 127.0.0.1,你根本不知道谁在攻击你
    proxy_set_header X-Real-IP remote_addr;

    # ⚠️ 重点:传递协议(HTTP/HTTPS)
    # 如果用户是 HTTPS 访问,后端必须知道,否则 RedirectPermanent 会强制跳回 HTTP
    proxy_set_header X-Forwarded-For proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto scheme;

    # ⚠️ 重点:WebSocket 支持
    # 如果你用 SignalR 或者 WebSocket,这两行缺一不可,否则连接建立一半就断了
    proxy_set_header Upgrade http_upgrade;
    proxy_set_header Connection "upgrade";

    # ⚠️ 重点:超时设置
    # 防止大文件上传或者慢接口导致 Nginx 直接报 504 Gateway Timeout
    proxy_read_timeout 360s;
    proxy_send_timeout 360s;
}

# 🛑 拒绝访问敏感目录
location ~ /. {
    deny all;
}

}

💡 墨夶的避坑指南(Nginx 篇):
502 Bad Gateway:通常是因为 proxy_pass 的端口(这里是 5000)没开,或者 Kestrel 没启动。先去服务器 curl localhost:5000 试试。
404 Not Found:检查 server_name 和域名解析。如果是在本地测试,记得改 hosts 文件。
WebSocket 断连:检查 Upgrade 和 Connection 头,少一个都不行。

ASP.NET Core 代码配置(深度定制)

光配 Nginx 不够,你得让 .NET 知道“我是被代理的”。很多新手在这里栽跟头,导致 HttpContext.Connection.RemoteIpAddress 拿到的永远是 127.0.0.1。

Startup.cs / Program.cs (ASP.NET 6+)

var builder = WebApplication.CreateBuilder(args);

// 1. 配置服务容器
builder.Services.AddControllers();

// ⚠️ 重点:配置 Forwarded Headers
// 这是 .NET 处理反向代理的核心中间件
// 它的作用是:读取 Nginx 传过来的 X-Forwarded-* 头,替换掉当前的上下文信息
builder.Services.Configure(options =>
{
// 默认情况下,.NET 只信任本地的代理(比如 IIS Express)
// 我们需要告诉它:信任来自任何地方的代理(因为在 Docker 或者云服务器里,IP 可能是动态的)
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost;

// ⚠️ 安全警告:在生产环境,千万不要用 KnownNetworks.All
// 这会导致伪造 IP 攻击(比如有人伪造 X-Forwarded-For: 8.8.8.8)
// 生产环境应该写死 Nginx 的 IP 段,比如 options.KnownProxies.Add(IPAddress.Parse("10.0.0.1"));
// 但在开发/测试环境,为了方便,先全开
options.KnownNetworks.Clear();
options.KnownProxies.Clear();

});

// 2. 构建应用
var app = builder.Build();

// ⚠️ 重点:必须在 UseRouting 之前使用 Forwarded Headers
// 中间件的顺序是地狱,顺序错了,Header 就拿不到
// 这行代码必须是管道里的第一个(或者至少在认证之前)
app.UseForwardedHeaders();

// 其他中间件…
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

app.Run();

代码深度解析:
ForwardedHeadersOptions:这是 .NET 的“翻译官”。Nginx 说:“嘿,用户真实 IP 是 1.1.1.1”,.NET 说:“收到,我记下来了”。
KnownNetworks.Clear():这行代码很危险,也很方便。它意味着“我信任所有转发请求的服务器”。在云环境(比如 AWS、Azure)或者 Docker Swarm 里,因为 IP 变化太快,我们通常这么干。但在传统 IDC,建议指定具体的 IP 段,防止 IP 欺骗。

进阶:Docker 部署下的特殊坑

如果你是用 Docker 部署,还有一种情况:Nginx 和 .NET 在不同的容器里。

这时候,Nginx 容器的 IP 对 .NET 容器来说是“外部网络”,.NET 默认根本不理这个 IP。

解决方案:
在 docker-compose.yml 里,给 .NET 服务加一个环境变量,或者在代码里显式指定 Nginx 容器的 IP。

// 在代码里硬编码(不推荐,但为了演示原理)
options.KnownProxies.Add(IPAddress.Parse(“172.20.0.10”)); // 假设 Nginx 的容器 IP 是这个

或者,在 docker-compose.yml 中设置网络模式,让它们共享网络栈(但这不安全),最推荐的做法还是在生产环境使用 Traefik 或者 Kubernetes Ingress,那是后话了。

三、真实踩坑故事:那个被“重定向”逼疯的夜晚

当年我在做第一个高并发项目时,就遇到过一个诡异的问题:
用户登录后,系统应该跳转到首页,结果浏览器疯狂报错,提示“重定向次数过多”。

查了整整 2 小时,最后发现是HTTPS 协议传递的问题。

原因分析:
用户访问 HTTPS (443)。
Nginx 接收,转发给 Kestrel (HTTP 5000)。
Kestrel 里的代码逻辑:检测到不是 HTTPS,强制重定向到 HTTPS。
Nginx 再次接收,又转发给 Kestrel…
死循环了!

解决办法:
就是上面代码里的这一行:
proxy_set_header X-Forwarded-Proto scheme;

加上这行,Kestrel 就知道“哦,用户其实是 HTTPS 访问的”,就不会再强制跳转了。

四、总结与互动

老铁们,今天的干货够硬核吧?从 Nginx 的配置细节,到 .NET 的中间件原理,再到 Docker 的网络坑,我都给你们扒出来了。

核心金句:
反向代理不是简单的“转发”,而是一场“信息的接力赛”。Nginx 跑第一棒(拿真实信息),.NET 跑第二棒(用这些信息)。如果交接棒(Header)掉了,比赛就输了。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐