ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

.NET站点部署:IIS+Nginx反向代理、HTTPS与真实IP配置实战

.NET站点部署:IIS+Nginx反向代理、HTTPS与真实IP配置实战 把一个 .NET 站点从本地开发机搬到服务器上跑起来IIS 大概是最省事的一条路装上角色、建个站点、绑个端口半小时就能访问。但真正在生产环境里扛过流量的同学心里都清楚单靠 IIS 顶在最前面的方案往往第二周就开始出状况——80 端口被别的服务占了、三个子站点要共用同一张证书、运维要求在最外层统一做访问日志和限流、临时要从公网换个端口做灰度。这时候绝大多数人的选择是在 IIS 前面架一层 Nginx 做反向代理让 Nginx 负责 HTTP 和 HTTPS 的入口、证书、跳转与静态资源IIS 退到内网端口后面安安心心跑 .NET 应用。这套架构说起来只有两层两个字实际落地的时候坑是分散在每一个环节里的IIS 装不上、应用池权限报 0x80005000、Nginx 转发后应用拿不到真实客户端 IP、HTTPS 打开报 ERR_SSL_PROTOCOL_ERROR、页面明明是 https 却因为一张 http 图片被浏览器拦掉。下面我会按想清楚架构 → 落 IIS → 配 Nginx → 打通 HTTPS → 验证排查这条真实的上线顺序把每一步的配置、理由和踩坑记录下来代码和命令都可以直接抄。1. 先把架构想清楚IIS 和 Nginx 各自该干什么1.1 只挂 IIS 也能跑为什么还要在前面加一层 Nginx先把结论摆出来如果只是内网一个单体 .NET 网站、只有一个域名、证书就一张那确实没必要加 NginxIIS 自己的 URL Rewrite 加证书绑定完全够用少一层就少一个故障点。加 Nginx 的价值出现在下面这些具体场景里而且这些场景在生产环境里出现的概率远比你想象的高。第一是入口统一。一台服务器上跑五六个站点是常态有的是 .NET有的是 Node 写的小工具有的只是几个静态页面。如果所有东西都塞进 IIS端口、证书、日志、限流规则要分散在好几处配出了事得一个个翻。把 Nginx 放在 80/443 上它只管按域名分发后面挂什么由 upstream 决定运维的心智负担会小很多。第二是 HTTPS 的集中管理。证书续期、证书链拼接、TLS 版本与加密套件策略这些都是改一次要影响所有站点的事情。在 Nginx 里改一处比在 IIS 里给每个站点重新导入一遍 PFX 靠谱得多尤其是证书文件本身是 PEM 格式的时候Nginx 直接读文件改完 reload 就生效。第三是平滑迁移的可能。业务以后有可能迁到别的运行时、别的操作系统如果入口一直在 Nginx 上迁的时候只要把 upstream 指过去对外的域名和证书完全不用动。这就是典型的把变化隔离在边界上。我一般的判断标准是这样的场景建议方案理由单站点、单域名、内网只用 IIS少一层转发排查路径最短多站点共用一台机器IIS Nginx入口统一域名分发清晰需要证书集中续期IIS Nginx证书只在一处配置需要统一访问日志/限流IIS NginxNginx 日志格式和限流模块更灵活未来可能迁到 LinuxIIS Nginx迁移时只改 upstream1.2 HTTPS 究竟在哪一层终结这是最容易含糊、后期最容易返工的一个决定。所谓终结就是 TLS 握手在哪一层解开、后面的流量是明文还是密文。方案一Nginx 终结 TLS。浏览器到 Nginx 是 HTTPSNginx 到 IIS 走 127.0.0.1 的明文 HTTP。优点是证书只装一份IIS 侧完全不用管证书后端性能压力也小缺点是 Nginx 和 IIS 之间是明文如果两者不在同一台机器上就要慎重。同一台机器上用回环地址通信这个风险是可以接受的。方案二IIS 终结 TLS。Nginx 只做四层转发stream 模块或者根本不用 Nginx。优点是链路端到端加密缺点是证书得装在 IIS 上续期要重新导入多站点场景下比较烦。方案三两层都配证书。我基本不推荐除了多一点无谓的开销还容易因为证书链不一致导致某一层验证失败这种很难查的问题。大多数情况下选方案一就够了。但选了方案一就必须在应用侧做一件事告诉 .NET 应用外面其实是 HTTPS否则应用生成的重定向地址、回调地址、静态资源绝对路径都可能变成 http:// 开头浏览器直接拦掉。这个后面 3.2 节会具体讲。1.3 请求从浏览器走到 .NET 进程中间丢了什么一个请求进来浏览器发的内容大概是这样的GET /account/login HTTP/1.1 Host: www.example.com X-Forwarded-For: 203.0.113.25 X-Forwarded-Proto: https到了 Nginx如果配置里什么都不做Nginx 转给 IIS 的请求会变成这样GET /account/login HTTP/1.1 Host: 127.0.0.1:8080看出来了吗三件关键信息全部丢了真实客户端 IP应用里Request.UserHostAddress拿到的是 127.0.0.1所有基于 IP 的审计、风控、黑名单全部失效。原始协议应用认为自己是 HTTP 服务Request.Url.Scheme是 http任何用Url.Action生成的绝对地址都会带上 http。原始 Host如果站点里有多域名绑定或者有基于 Host 的逻辑也会读错。所以 Nginx 侧必须显式地把这三个头传下去应用侧必须显式地去读。很多人配完 Nginx 发现网站能开但登录后跳回 http 页面99% 就是这里没处理。这个不是可选项是这套架构的必答题。2. IIS 这一侧的落地站点、应用池与运行时对齐2.1 装 IIS 之前先把 .NET 运行时版本对齐这一步错的人特别多。IIS 本身只是一个 Web 服务器外壳它自己不认识 .NET真正把请求交给应用的是对应的托管模块。不同代的 .NET 需要的组件完全不同项目类型需要安装的组件应用池 .NET CLR 版本.NET Framework 4.xMVC/WebForms/WCFIIS 的 ASP.NET 4.x 功能 对应 .NET Frameworkv4.0.NET Core / .NET 5/6/8ASP.NET CoreASP.NET Core Hosting Bundle含运行时 ANCM 模块无托管代码纯静态站点 / 前端打包产物只需 IIS 静态内容功能无托管代码两个高频报错要先说清楚。第一个是安装 .NET Framework 4.8 离线包时提示这台计算机中已经安装了 .NET Framework 4.8 或版本更高的更新——这不是错误是系统自带的比你手上的包还新查一下注册表HKLM\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full里的Release值确认版本就行不用折腾。第二个是在 IIS 里找不到 .NET 8 的应用程序池选项。IIS 的应用程序池下拉框本来就只有无托管代码和 v2.0、v4.0 四档它反映的是 .NET Framework 的 CLR 版本跟 .NET 8 无关。跑 ASP.NET Core 站点时应用池选无托管代码真正的运行时绑定由 web.config 里的aspNetCore processPath... /节点和 Hosting Bundle 安装的 ANCM 模块负责。理解了这一点就不会在IIS 里为什么没有 .NET 8这个问题上浪费时间。安装路径也顺手记一下Windows Server 2019 走服务器管理器 → 添加角色和功能 → Web 服务器(IIS)注意在功能列表里手动勾上应用程序开发 → ASP.NET 4.x和Web 服务器 → 常见 HTTP 功能 → HTTP 重定向Windows 10/11 走控制面板 → 程序和功能 → 启用或关闭 Windows 功能 → Internet Information Services。Win10 上装 IIS 时如果报 0x80070002通常是系统组件存储有问题先跑一遍系统更新再装。2.2 站点绑定、物理路径和应用池的配置顺序顺序很重要尤其当 Nginx 已经占了 80 和 443 的时候。先建应用池再建站点。原因是站点创建时就能直接选到正确的池省掉后面改池、回收、调权限的一串动作。应用池命名建议带上环境和项目比如MyApp_Prod后面配权限的时候用得上。端口规划上既然 Nginx 占了 80/443IIS 侧就换到 8080/8443 这类高位端口。更稳妥的做法是只绑定回环地址而不是*在编辑绑定里把 IP 地址改成127.0.0.1这样外部根本连不到 IIS 的端口等于给后端加了一层隐式的访问控制。绑定界面里 IP 地址那一栏默认是全部未分配记得手动改。物理路径的权限是最容易被忽略的一步。IIS 默认的应用池标识是ApplicationPoolIdentity这个身份的实质是IIS AppPool\池名这个虚拟账户。给站点目录授权可以用命令行比在图形界面里点鼠标清楚得多# 只读权限适合绝大多数纯展示型站点 icacls D:\sites\myapp /grant IIS AppPool\MyApp_Prod:(OI)(CI)(RX) # 需要写日志、写上传文件的目录单独给写权限 icacls D:\sites\myapp\App_Data /grant IIS AppPool\MyApp_Prod:(OI)(CI)(M)注意不要图省事直接把 Everyone 加进去也不要随手把应用池标识改成 LocalSystem。后者能解决一时的权限问题但等于把整个机器的控制权交给了一个 Web 进程后面做安全审计的时候会很被动。真要用高权限标识也应该用NetworkService或者专门的域账户而不是 LocalSystem。2.3 应用程序池权限设置失败 0x80005000 的排查链路这个报错我遇到过两次两次原因都不一样所以值得把排查过程完整写下来。现象是在 IIS 管理器里新建应用程序池或者去改应用池的标识弹出一个框说请手动为其设置 LocalSystem 权限。未知错误 (0x80005000)。0x80005000 这个 HRESULT 对应的是 ADSI 的E_ADS_BAD_PATHNAME本质上是用 ADSI 去查一个对象没查到。顺着这个思路排查路径是这样的第一步看事件查看器。打开事件查看器 → Windows 日志 → 应用程序按时间找刚才那条报错前后的记录。如果里面有 WMI 或者 IIS-W3SVC 相关的错误说明是 IIS 配置存储出了问题如果只有一条孤零零的权限报错就往标识本身查。第二步检查 applicationHost.config 的完整性。文件在C:\Windows\System32\inetsrv\config\applicationHost.config。最常见的破坏方式是有人用记事本直接编辑过它或者编辑到一半进程被杀掉文件锁没释放。判断方法很简单用管理员权限的 PowerShell 执行%windir%\system32\inetsrv\appcmd.exe list apppool如果这条命令本身报错或者输出不完整基本可以确定配置文件有问题。第三步检查是不是标识填写的路径不对。在应用程序池 → 高级设置 → 进程模型 → 标识 → 自定义账户里如果你填的是一个域账户格式必须是域名\用户名只写用户名在某些环境里就会触发 0x80005000。这是最常见的一种情况改成内置的ApplicationPoolIdentity再试一次如果这次成功了问题就定位了。第四步如果前三条都排除就走配置还原。IIS 有内置的备份机制# 列出所有备份 %windir%\system32\inetsrv\appcmd.exe list backup # 手动创建一份当前配置的备份 %windir%\system32\inetsrv\appcmd.exe add backup before_fix_20240601 # 恢复到某个备份 %windir%\system32\inetsrv\appcmd.exe restore backup before_fix_20240601养成改配置之前先 appcmd add backup的习惯这一条比什么都值钱。我现在的做法是每次动 IIS 配置之前无脑备份一次出问题直接 restore比在现场一步步推理快得多。2.4 web.config 与图形界面配置冲突的处理还有一个很隐蔽的坑同一个设置既能在 IIS 管理器里改也能在 web.config 里写两边不一致的时候以谁为准规则是站点级别的设置web.config 优先于 applicationHost.config但有些节点在 IIS 里被标记为锁定的站点级 web.config 不允许覆盖这时候直接改 web.config 会报 500.19 错误错误信息里会明确写着此配置节不能在此路径中使用。遇到 500.19 的排查顺序先把完整的错误码和文件名看清楚500.19 后面通常跟着一个具体的行号和列号对照 web.config 的对应位置如果是节被锁定要么在 IIS 管理器的配置编辑器里把该节解锁要么直接在 applicationHost.config 层面改。解锁的命令行方式是在管理员 PowerShell 里执行%windir%\system32\inetsrv\appcmd.exe unlock config /section:system.webServer/security/ipSecurity提示如果站点里出现了执行此操作时出错文件名c:\windows\system32\inetsrv\config\applicationHost.config这种提示八成是配置文件被某个进程独占了。用handle.exe或者资源监视器查一下谁在占用常见的是没退干净的记事本、还在跑的老版本 IIS 管理工具。极端情况下需要先停掉 W3SVC 服务再操作。3. Nginx 转发规则写对了才不用返工3.1 proxy_pass 结尾那个斜杠到底影响什么proxy_pass后面带不带斜杠是 Nginx 配置里最经典的看起来一样、行为完全不同的地方。规则本身不复杂但后果很直观配置写法location 匹配请求/api/user/1转发到后端结果proxy_pass http://127.0.0.1:8080;/api/http://127.0.0.1:8080/api/user/1保留原路径proxy_pass http://127.0.0.1:8080/;/api/http://127.0.0.1:8080/user/1替换掉匹配部分判断方法就一句话proxy_pass 里出现了 URI也就是域名端口后面还有内容哪怕只有一个斜杠就会做替换没有 URI就原样拼上去。这个坑的典型症状是站点首页能打开但所有 CSS、JS 全是 404或者在 Network 面板里看到请求地址变成了/css/site.css而不是/myapp/css/site.css。检查一下是不是 location 配了/myapp/而 proxy_pass 结尾多写了个斜杠。我的习惯是统一让后端应用跑在自己的根路径上也就是 IIS 站点根就是应用根Nginx 的 location 直接写location /proxy_pass 不带斜杠路径原样透传。这样前端打包时的 publicPath、后端的路由前缀都不需要额外特殊处理少一层心智负担。3.2 把真实 IP、协议和 Host 一路带回 IIS这是整套架构里最不能省的一段配置。先看 Nginx 侧怎么写upstream iis_backend { server 127.0.0.1:8080; keepalive 32; } server { listen 80; server_name www.example.com; location / { proxy_pass http://iis_backend; proxy_http_version 1.1; proxy_set_header Connection ; 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; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Port $server_port; } }几个点单独说一下。proxy_http_version 1.1加proxy_set_header Connection 是为了启用长连接复用不加的话默认走 1.0每次请求都要重新建一条到后端的连接QPS 一高就明显了。$proxy_add_x_forwarded_for会保留客户端原来的 X-Forwarded-For 并在后面追加比直接用$remote_addr更完整但要注意这个头可以被客户端伪造做审计的时候要用 Nginx 的real_ip模块剥掉不可信的层级。Nginx 侧配完了应用侧还得主动去读否则前面白做。ASP.NET Core 需要在 StartUp 里加 ForwardedHeaders 中间件app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto, // 生产环境要指明可信代理否则默认只信任回环地址 KnownProxies { IPAddress.Parse(127.0.0.1) } });.NET Framework 的项目没有现成的中间件通常是在Global.asax的Application_BeginRequest里手动把X-Forwarded-For读出来塞进HttpContext.Current.Items需要真实 IP 的地方统一从那里取。也有人用 URL Rewrite 模块的服务器变量来重写REMOTE_ADDR但那种做法在升级 IIS 之后偶尔会失效我更倾向于在代码里显式处理。注意ForwardedHeaders 中间件必须放在管道的最前面放在认证、授权、会话这些中间件之前。放错位置的表现是有些地方能拿到真实 IP有些地方拿不到非常难查。3.3 大文件上传、超时和缓冲的实际参数默认配置跑小站点没问题一遇到上传就露馅。最常见的现象是用户上传一个 30MB 的文件进度条走到一半页面报 413或者干脆卡死不返回。413 来自 Nginx 的client_max_body_size默认只有 1MB。上传大小按业务实际情况给别一次性开成 0不限制那等于给了别人一个打满磁盘的口子location /upload { proxy_pass http://iis_backend; client_max_body_size 200m; client_body_timeout 300s; proxy_read_timeout 300s; proxy_send_timeout 300s; proxy_request_buffering off; }proxy_request_buffering off这一条值得展开讲。默认情况下 Nginx 会先把整个请求体收完再转给后端对普通请求没问题对大文件上传就很浪费——数据要在 Nginx 侧落一遍盘再传一遍。关掉之后 Nginx 边收边转发内存和磁盘压力都小很多代价是如果后端先返回错误Nginx 不会提前中断客户端的上传。上传场景下这个代价可以接受。超时参数要成组地看proxy_read_timeout是 Nginx 等后端两次响应之间的间隔不是整个请求的总时长。后端在处理一个耗时 5 分钟的报表导出时如果每 10 秒能吐一个心跳那 read timeout 设 30 秒也够如果后端从头到尾憋着不出声那就必须把 read timeout 设得比处理时间更长。搞清这一点就不会出现参数都调大了还是超时的情况。IIS 侧也有对应的限制web.config里的maxAllowedContentLength默认约 30MBhttpRuntime的maxRequestLength默认 4MB。两处都要调只调一边的话会出现Nginx 放过去了、IIS 拦下来了的情况。3.4 多个 .NET 站点共用一个 Nginx 的组织方式一台机器上跑三五个 .NET 站点是很常见的组织方式建议按下面的思路来upstream site_a { server 127.0.0.1:8081; keepalive 32; } upstream site_b { server 127.0.0.1:8082; keepalive 32; } upstream site_c { server 127.0.0.1:8083; keepalive 32; } server { listen 80; server_name a.example.com; location / { proxy_pass http://site_a; include /etc/nginx/conf.d/proxy_common.conf; } } server { listen 80; server_name b.example.com; location / { proxy_pass http://site_b; include /etc/nginx/conf.d/proxy_common.conf; } }把那些每个站点都要写的 proxy_set_header、超时参数抽到一个proxy_common.conf里统一 include这是最省事的做法。以后要加一个X-Request-Id之类的头改一个文件全局生效不用在十个 server 块里同步改。我见过因为同一个头在两个 server 块里写法不一致导致只有部分站点能拿到真实 IP 的事故。对应的 IIS 侧每个站点绑定到各自的端口主机头那一栏可以留空因为走的是回环地址加端口区分也可以填域名。这里有个小细节如果 IIS 绑定时填了主机头而 Nginx 转发时proxy_set_header Host $host;传的是浏览器给的域名两者一致才能匹配上不一致的话 IIS 会返回 404。排查网站明明在跑但通过 Nginx 访问就是 404的时候先看这个。4. HTTP 与 HTTPS 并行证书、监听和跳转4.1 证书格式在 IIS 和 Nginx 之间怎么换IIS 导入证书用的是 PFX也就是 PKCS#12带私钥的容器Nginx 读的是 PEM 文本格式的证书加私钥。这两个格式之间可以互相转命令记两条就够# PFX 转 PEM私钥 证书一次导出 openssl pkcs12 -in mycert.pfx -out mycert.pem -nodes # 从 PEM 里拆出私钥和证书链 openssl pkey -in mycert.pem -out mycert.key openssl crl2pkcs7 -nocrl -certfile mycert.pem | openssl pkcs7 -print_certs -out mycert.crt-nodes这个参数的意思是导出的私钥不加密方便 Nginx 直接读取。私钥文件权限一定要收紧chmod 600 mycert.key并且确认它的属主是 Nginx 的工作账户。证书链的问题值得单独拎出来说。浏览器信任的是根证书服务器必须把站点证书 中间证书一起发给客户端缺了中间证书桌面浏览器往往能靠缓存蒙过去但手机端、App 内嵌 WebView、各种 SDK 就会报证书不受信任。判断方法是openssl s_client -connect www.example.com:443 -servername www.example.com -showcerts如果输出的证书链只有一张那就是缺中间证书。Nginx 的ssl_certificate指向的文件应该是站点证书在前、中间证书在后拼起来的 fullchain 文件顺序不能反。4.2 80 跳 443 的写法以及状态码的选择Nginx 上把 HTTP 强制跳转到 HTTPS最干净的写法是单独起一个 server 块server { listen 80; server_name www.example.com; return 301 https://$host$request_uri; }用return 301比用rewrite效率高因为它在 server 级别直接返回不走进 location 匹配。状态码的选择上有几个细节。301 是永久重定向浏览器和搜索引擎都会缓存适合确定永远跳转的场景。但如果这是 POST 请求的场景301 和 302 都会把方法改成 GET请求体丢掉要保持方法和请求体必须用 307 或 308。308 是 301 的保持方法版本同样是永久重定向。实际经验是GET 为主的普通站点用 301 就行如果站点里有 POST 到 HTTP 地址的接口比如老的 App 还在用 http 调接口那这些路径要单独排除掉跳转或者用 308否则用户会看到一堆莫名其妙的参数丢失。4.3 ERR_SSL_PROTOCOL_ERROR 和混入不安全内容的定位这两个报错几乎是配 HTTPS 时的必修课但成因完全不同处理方式也完全不同。ERR_SSL_PROTOCOL_ERROR是 TLS 握手阶段就失败了请求根本没到应用层。常见的四种原因第一种用https://去访问一个只会 HTTP 的端口。比如应用自己监听在 8889 上只提供 HTTP你直接访问https://localhost:8889/浏览器就会报这个错因为对方根本不会回应 TLS 的 ClientHello。这种最容易误导人因为它看起来像是证书配错了。第二种listen 443 ssl;里的ssl参数漏了。Nginx 在 443 端口上按明文 HTTP 解析客户端发的是 TLS 握手包两边对不上直接断开。第三种证书和私钥不匹配。可以用命令验证openssl x509 -noout -modulus -in mycert.crt | openssl md5 openssl rsa -noout -modulus -in mycert.key | openssl md5两个 MD5 必须一致不一致就是配错了文件。第四种协议版本或加密套件过老被现代浏览器拒绝。用openssl s_client -connect host:443看握手用的协议版本如果是 TLS 1.0 或者 SSLv3说明配置里还在启用已经被淘汰的协议需要显式指定ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers on;混合内容Mixed Content的表现完全不同HTTPS 页面能正常打开TLS 握手没问题但控制台会报警告或者错误说某个资源was loaded over an insecure connection, this file should be served over http然后这张图片或者这个接口请求被浏览器拦掉。原因是页面本身是 https但里面引用的资源写死了 http。处理方式分三层。最彻底的是把所有资源地址改成相对路径或者协议相对路径让浏览器跟着页面的协议走。对于后端生成的 URL就回到 3.2 节讲的X-Forwarded-Proto让框架知道外部是 HTTPS它生成的绝对地址自然就是 https。如果有些第三方资源确实只有 http那就只能通过反向代理在本地转一层或者在 CSP 里加upgrade-insecure-requests让浏览器自动把 http 请求升级成 https前提是对面也支持 https。4.4 HSTS 什么时候可以开HSTS 是让浏览器记住这个域名以后只走 HTTPS的响应头add_header Strict-Transport-Security max-age31536000; includeSubDomains always;这个头威力很大也容易害自己。一旦浏览器记住了 max-age在过期之前用户手动在地址栏输 http:// 也会被浏览器内部改成 https根本不发那一次明文请求。如果这时候你的 HTTPS 出问题了用户连退回 HTTP 应急这条路都没有。所以我的做法是分两步走。上线初期先设一个很短的 max-age比如 300 秒观察一两天确认 HTTPS 在所有目标浏览器、所有子域名上都正常稳定之后再逐步加到一天、一周、一年。includeSubDomains尤其要谨慎它会连带所有子域名一起生效如果你的证书是单域名证书加了这一条会让所有子域名直接无法访问。5. 上线前后的验证与排查链路5.1 用 curl 和 openssl 把每一跳拆开验不要一上来就用浏览器点浏览器会缓存、会自动升级协议、会自动重试看到的现象往往是被修饰过的。用命令行从最里层往外一层层验定位速度会快很多。# 第一跳直连 IIS 后端确认应用本身是活的 curl -I http://127.0.0.1:8080/ # 第二跳走 Nginx 的 HTTP 入口确认转发正常 curl -I -H Host: www.example.com http://127.0.0.1/ # 第三跳验证 80 到 443 的跳转 curl -I -H Host: www.example.com http://127.0.0.1/ # 第四跳验证 HTTPS 握手和证书链 curl -Iv https://www.example.com/ --resolve www.example.com:443:127.0.0.1 # 第五跳确认应用拿到的真实 IP 和协议 curl -H Host: www.example.com https://127.0.0.1/ -k--resolve这个参数很好用它让 curl 把域名解析到你指定的 IP同时 SNI 和 Host 头还是正常的域名可以在域名还没切过来的时候先验证服务器配置。如果应用里有一个显示当前客户端 IP的调试接口那第五跳是最有价值的——它能一次性告诉你 X-Forwarded-For 和 X-Forwarded-Proto 有没有正确传到应用层。上线前把这个接口跑一遍能省掉上线后一半的排查时间。5.2 三个日志一起看症状到日志位置的对照出问题时很多人只盯着一个日志看然后在错误的方向上越走越远。我整理了一张对照表遇到症状先按这张表定位现象优先看哪里常见原因浏览器打不开连接被拒绝netstat -ano | findstr :443端口没监听、被别的进程占用502 Bad GatewayNginx error.logIIS 站点没启动、应用池被回收、端口写错504 Gateway TimeoutNginx error.log 应用日志后端处理超时或 proxy_read_timeout 太短404 但直连后端正常Nginx access.log 对比请求路径路径被 proxy_pass 替换掉、Host 头不匹配500 错误Windows 事件查看器 ASP.NET 日志应用代码异常或 web.config 配置错误静态资源加载不出来浏览器 Network 面板混合内容拦截、路径前缀不对、MIME 类型缺失拿到的客户端 IP 都是 127.0.0.1应用侧 ForwardedHeaders 配置中间件没加或位置不对IIS 的访问日志默认在%SystemDrive%\inetpub\logs\LogFiles\W3SVC*里面的cs-host、cs-uri-stem、sc-status三列最能说明问题——cs-host如果显示的是127.0.0.1:8080而不是域名说明 Nginx 的 Host 头没传对。Nginx 侧的 access log 建议自定义格式把$http_x_forwarded_for、$upstream_response_time、$request_time都打出来出问题时能一眼看出是 Nginx 慢还是后端慢。5.3 我自己踩过的几个坑最后一个坑是端口占用。Windows 上 80 端口经常被系统自带的http.sys、SQL Server Reporting Services、或者某个装了 IIS 的旧服务占着。判断命令是netstat -ano | findstr :80 tasklist /fi pid eq 1234注意http.sys这个驱动很特殊它可能没有对应的进程名但内核态占着端口netstat里看到的是System或者 PID 4。这种情况下需要停掉依赖它的服务或者查一下有没有别的程序注册了 URL 前缀netsh http show urlacl。第二个坑是静态文件压缩被压了两遍。IIS 有静态内容压缩和动态内容压缩两个开关Nginx 也有 gzip。两层都开的时候Nginx 会先对 IIS 已经压过的内容再压一次CPU 白烧效果还更差。原则是只在一层开一般放在 Nginx 上因为 Nginx 的 gzip_static 和 gzip_types 配置更细而且它离客户端更近。第三个坑是应用池的空闲超时。IIS 应用池默认 20 分钟没有请求就回收工作进程表现是每天早上第一个访问的用户要等十几秒或者定时任务晚上不执行。在应用池的高级设置里把空闲超时改成 0不超时固定时间间隔按需要调整同时在站点的高级设置里把预加载已启用打开能明显改善冷启动体验。这个设置在开发和测试阶段完全看不出来只有上了生产、流量有波谷的时候才会暴露。再补充一个小的给 IIS 站点配了 URL Rewrite 做内部跳转之后如果规则里用了{HTTP_HOST}而访问链路上又多了一层代理规则条件可能永远不成立。处理办法是用{HTTP_X_FORWARDED_HOST}代替或者在重写的条件里同时匹配两个变量。这种问题不会报错只会规则看着对但不生效特别消耗时间。整条链路配下来其实不复杂真正花时间的是那些配置看起来没错但行为不对的部分。我的经验是每次改完配置都用 5.1 节那五条 curl 把链路完整跑一遍并且把curl -I的响应里几个关键头Server、Location、Set-Cookie、Strict-Transport-Security记下来做对比。有了这条基线下次出问题的时候一眼就能看出是哪一跳的行为变了。
返回列表