器對比實(shí)戰(zhàn))
簡介面向需要在 Windows 環(huán)境下快速搭建輕量 Web 服務(wù)、尤其是調(diào)試 ASP 頁面的用戶這份小型工具包提供了一套替代 IIS 的簡易思路。它圍繞可執(zhí)行服務(wù)程序、頁面樣式表與說明網(wǎng)頁組織適合個人開發(fā)者、教學(xué)演示或舊項目維護(hù)場景用于低成本啟動本地站點(diǎn)并驗證動態(tài)頁面運(yùn)行效果。壓縮包共 3 個文件主要為 exe、css 與 htm 類型體積約 1.01MB結(jié)構(gòu)精簡下載與部署負(fù)擔(dān)較小。已有 436 人學(xué)習(xí)或下載說明其對 IIS 替代方案與 ASP 運(yùn)行環(huán)境感興趣的讀者具有一定參考價值。讀者可借此了解輕量服務(wù)器工具的組成方式結(jié)合說明頁理解其配置方式并用于 ASP 頁面本地測試、簡易站點(diǎn)發(fā)布或在選型時與 Nginx、Apache、Caddy 等常見方案作入門對比輔助判斷工具是否契合自身開發(fā)與維護(hù)需求。1. 換掉 IIS 之前你要換的不是軟件是它替你兜住的那層默認(rèn)行為去年接手一個跑了七八年的老站點(diǎn)機(jī)房要回收那臺 Windows 主機(jī)我第一反應(yīng)是趕緊找個代替 IIS 的軟件越快越好。第一次遷移就翻了車首頁能開后臺上傳接口全掛日志里連一行有用的信息都沒有。查了大半天才發(fā)現(xiàn)問題根本不在新服務(wù)器軟件上而在 IIS 里那個我從沒正眼看過的默認(rèn)請求體大小限制它一直默默兜著這個站點(diǎn)。這件事之后我改了判斷順序先摸清你在 IIS 上依賴了什么再談?chuàng)Q誰。這篇寫給正在做這類決策的人手里可能有靜態(tài)站、有跑了很多年的 ASP.NET 應(yīng)用、也有新寫的接口服務(wù)想知道幾種替代方案各適合誰、動手前要先盤哪些東西、哪一步最容易出事。2. 選型維度給代替 IIS 的軟件排序先定這五把尺子選型最怕上來就比性能跑分。靜態(tài)文件誰快誰慢在你實(shí)際并發(fā)量級下基本看不出差別真正讓你半夜爬起來的是證書過期、路徑 404、上傳失敗這類瑣事。我一般按五個維度排托管模型、配置遷移成本、TLS 與可觀測性、生態(tài)與人力最后才是性能。下面逐個說清楚怎么判斷。2.1 托管模型站點(diǎn)是跑在服務(wù)器里還是服務(wù)器本身就是站點(diǎn)IIS 是典型的容器式模型一臺機(jī)器上裝一個服務(wù)配置里掛若干個站點(diǎn)每個站點(diǎn)綁主機(jī)頭、端口、證書應(yīng)用池負(fù)責(zé)隔離和回收。Nginx、Apache、Caddy 都是同一套思路只是換了個配置文件格式。Kestrel 恰好反過來——你的程序本身就是服務(wù)器一個進(jìn)程通常就是一個站點(diǎn)。這個差別決定了遷移工作量。一臺 Windows 主機(jī)上掛著七八個站點(diǎn)、共用不同主機(jī)頭換成 Kestrel 就意味著七個進(jìn)程、七份配置、七套守護(hù)運(yùn)維復(fù)雜度直接翻倍反過來如果你手上只有兩三個 .NET 應(yīng)用用 Kestrel 加前置反向代理反而比配置一個通用 Web 服務(wù)器更省事。還有一條更硬的判斷線有沒有 Windows 專有依賴。Windows 集成身份驗證、COM 組件、注冊表讀寫、依賴文件鎖的舊代碼、本地消息隊列只要命中其中一項跨平臺這條路基本就堵死了剩下能選的是繼續(xù)留在 Windows 上只是把 IIS 換成別的服務(wù)器軟件。我見過太多團(tuán)隊在這一點(diǎn)上判斷失誤方案選得漂亮最后卡在一個調(diào)不動的老 DLL 上。2.2 配置遷移成本web.config 里那些沒人看的規(guī)則才是大頭web.config 是一個容易被低估的坑。它表面上是個配置文件實(shí)際上承載了這個站點(diǎn)多年的歷史請求體大小限制、URL 重寫規(guī)則、自定義響應(yīng)頭、默認(rèn)文檔、MIME 類型補(bǔ)充、錯誤頁、模塊注冊、授權(quán)規(guī)則一層套一層很多條目連原作者都說不清為什么在那兒。判斷成本的方法很土但有效把 web.config 全文撈出來按節(jié)點(diǎn)分類計數(shù)。URL 重寫規(guī)則超過二十條就要單獨(dú)排期因為重寫規(guī)則的語義在 Nginx 的 rewrite、Caddy 的 try_files 之間并不一一對應(yīng)正則寫法、大小寫敏感、尾部斜杠處理都會產(chǎn)生行為差異得逐條對著測。請求體大小、默認(rèn)文檔、MIME 補(bǔ)充、自定義頭這幾類倒是很快基本是查表替換的活兒。真正花時間的往往不是規(guī)則本身而是規(guī)則背后的隱含假設(shè)。比如一條重寫規(guī)則把/api/xxx轉(zhuǎn)到/api/xxx.ashx在 IIS 上跑得好好的換到新服務(wù)器上因為大小寫敏感或者路徑拼接方式不同直接 404。這種問題不看原始配置根本猜不到。2.3 TLS 與進(jìn)程可觀測性證書續(xù)期和日志這兩件事最容易欠債IIS 的證書綁定是手工活續(xù)期靠人記著這是很多團(tuán)隊的隱性技術(shù)債。換成 Nginx證書還是要自己配、自己續(xù)通??慷〞r任務(wù)加一個重載命令Caddy 把這件事直接內(nèi)建了只要域名解析正確、80 和 443 可達(dá)簽發(fā)和續(xù)期它自己管。如果你的站點(diǎn)數(shù)量多、證書來源雜這一項的權(quán)重應(yīng)該往上調(diào)??捎^測性是另一處容易被忽略的差異。IIS 有 W3C 格式的訪問日志還有失敗請求跟蹤這種能抓到具體模塊和耗時的手段。Nginx 的日志默認(rèn)字段和 IIS 完全不是一套$remote_addr、$request_time、$upstream_response_time這些字段需要自己組織成log_format否則你原來那套日志分析腳本全部失效。進(jìn)程守護(hù)也一樣IIS 有應(yīng)用池自動重啟和快速失敗保護(hù)Nginx 是主進(jìn)程帶工作進(jìn)程工作進(jìn)程掛了會自動拉起而 Kestrel 單獨(dú)跑的話沒有任何守護(hù)必須靠 systemd 或容器編排兜底。我把這塊叫黑匣子問題。遷移前先問自己一句出問題時我手上有什么證據(jù)。答不上來就先別切流量。2.4 生態(tài)與人力冷門方案省下的性能要用人力還模塊生態(tài)這一項Apache 底子最厚幾十年的模塊積累幾乎任何奇怪需求都能找到現(xiàn)成方案Nginx 的資料量和招人容易度最高出問題搜一下基本有答案Caddy 的配置極簡但遇到冷門需求時文檔深度不如前兩者Kestrel 只在 .NET 圈子里出了圈基本沒人認(rèn)。還有一條經(jīng)常被忽略Windows 上的 Nginx 是模擬多工作進(jìn)程的官方文檔也說明了它在 Windows 上的表現(xiàn)不適合高并發(fā)生產(chǎn)場景。如果你的結(jié)論是繼續(xù)留在 Windows 主機(jī)上那 Nginx 未必是首選反而 Apache 或者干脆保留 IIS 換掉站點(diǎn)架構(gòu)更實(shí)際。方案選型必須和宿主機(jī)綁定在一起考慮脫離部署環(huán)境談優(yōu)劣沒有意義。2.5 五個維度擺在一起看的對照表把上面幾條落成一張表選型會快很多。下表的判斷基于常見的中小型站點(diǎn)遷移場景并發(fā)量級在幾百到幾千 QPS 以內(nèi)超大流量場景需要重新評估。維度NginxApache HTTP ServerCaddyKestrel 反向代理托管模型站點(diǎn)式配置驅(qū)動站點(diǎn)式配置驅(qū)動站點(diǎn)式配置驅(qū)動進(jìn)程即站點(diǎn)跨平臺能力強(qiáng)但 Windows 版不建議生產(chǎn)強(qiáng)強(qiáng)強(qiáng)依賴 .NET 運(yùn)行時配置遷移成本中重寫規(guī)則需逐條翻譯中重寫語法相對接近低配置項少高需改代碼與部署方式證書自動化需自行實(shí)現(xiàn)續(xù)期需自行實(shí)現(xiàn)續(xù)期內(nèi)建自動簽發(fā)續(xù)期依賴前置代理日志與排查字段靈活格式需自配字段成熟模塊豐富格式簡潔夠用依賴應(yīng)用日志與前置代理生態(tài)與資料量大大且老牌中僅限 .NET 生態(tài)適合的站點(diǎn)類型靜態(tài)資源、反向代理入口老應(yīng)用兼容、模塊依賴重站點(diǎn)多、證書雜純 .NET 服務(wù)這張表只是起點(diǎn)具體項目里我會把權(quán)重按實(shí)際情況調(diào)。證書數(shù)量多、運(yùn)維人手少就把自動化那一列權(quán)重加倍老應(yīng)用多、重寫規(guī)則復(fù)雜就把遷移成本那一列權(quán)重加倍。3. 四類方案逐項對比Nginx、Apache、Caddy、Kestrel 各能接住什么四個方案不是同一層的東西硬拉在一起比容易失真。準(zhǔn)確地說Nginx、Apache、Caddy 是通用 Web 服務(wù)器可以獨(dú)立承接站點(diǎn)Kestrel 是應(yīng)用內(nèi)服務(wù)器通常要和前三種之一搭配使用。下面按能接住什么、接不住什么分別說。3.1 Nginx靜態(tài)資源和反向代理入口的默認(rèn)答案大部分從 IIS 遷走的項目最后都落在 Nginx 上原因很實(shí)在靜態(tài)文件處理干凈反向代理配置直觀資源占用低出了問題網(wǎng)上能找到答案。它對應(yīng)的 IIS 能力大致是站點(diǎn)綁定對應(yīng) server 塊URL 重寫對應(yīng) rewrite 與 try_files請求過濾對應(yīng) client_max_body_size 一類指令自定義響應(yīng)頭對應(yīng) add_header。它的短板也很明確。第三方模塊大多需要重新編譯動態(tài)模塊支持是后來才有的不如 Apache 那種加載即用的體驗配置語法對新手有門檻一個分號漏了就是 500Windows 版本不適合生產(chǎn)證書續(xù)期得自己搭。如果你的站點(diǎn)以靜態(tài)資源加反向代理為主這些短板基本碰不到如果重寫規(guī)則特別復(fù)雜翻譯工作量會比想象中大。3.2 Apache HTTP Server老站點(diǎn)和 .htaccess 慣性的退路如果原站點(diǎn)重度依賴 .htaccess 或者大量 Apache 風(fēng)格的重寫規(guī)則遷移到 Apache 反而最省事。它的模塊生態(tài)最成熟認(rèn)證、改寫、壓縮、限流都有現(xiàn)成方案配置項的可讀性也比 Nginx 好一些出了錯日志提示相對友好。代價是資源和調(diào)優(yōu)復(fù)雜度。默認(rèn)的多進(jìn)程模型在高并發(fā)下內(nèi)存占用比 Nginx 高需要根據(jù)場景在 prefork、worker、event 幾種模式之間選這個選擇對新手不友好。另外如果原來的站點(diǎn)是經(jīng)典 ASP 或者老式 WebFormsApache 也接不住那部分只能靠單獨(dú)的應(yīng)用服務(wù)器或者整體重寫別指望換個 Web 服務(wù)器就解決。3.3 Caddy把證書這件事從運(yùn)維流程里拿掉Caddy 最大的價值是把 TLS 這件事從運(yùn)維任務(wù)變成默認(rèn)行為。配置文件里寫個域名證書簽發(fā)和續(xù)期它自己完成不需要你寫定時任務(wù)也不會有某張證書忘了續(xù)導(dǎo)致凌晨告警這種事。配置語法也短一個反向代理加靜態(tài)文件服務(wù)幾行就寫完對站點(diǎn)數(shù)量多、證書來源雜的環(huán)境特別友好。限制在于生態(tài)。它的模塊數(shù)量比不上前兩者遇到非常規(guī)需求時可能得自己寫插件日志格式雖然能配但周邊分析工具的支持度不如 Nginx 成熟團(tuán)隊里如果有人只熟悉 Nginx接手 Caddy 需要一點(diǎn)適應(yīng)時間。我的判斷是如果你的團(tuán)隊規(guī)模小、站點(diǎn)多、沒人愿意天天盯證書Caddy 的性價比很高。3.4 Kestrel 加反向代理.NET 應(yīng)用最省心的組合如果站點(diǎn)本身就是 .NET 應(yīng)用最干凈的做法是讓 Kestrel 只監(jiān)聽回環(huán)地址公網(wǎng)流量統(tǒng)一由前置的 Nginx 或 Caddy 進(jìn)來。好處是應(yīng)用和服務(wù)器職責(zé)分離TLS、靜態(tài)資源、限流、壓縮交給前置業(yè)務(wù)邏輯只關(guān)心請求升級部署也只是重啟一個進(jìn)程。關(guān)鍵點(diǎn)有三個。第一監(jiān)聽地址必須是127.0.0.1否則外部可以繞過前置代理直連應(yīng)用端口。第二請求體大小限制要在 Kestrel 里顯式設(shè)置IIS 的默認(rèn)值和 Kestrel 的默認(rèn)值并不一致直接搬過來很可能上傳失敗。第三必須啟用轉(zhuǎn)發(fā)頭處理否則應(yīng)用里拿到的客戶端 IP 永遠(yuǎn)是127.0.0.1日志和風(fēng)控全部失真。下面這段配置把這三件事一次做完。var builder WebApplication.CreateBuilder(args); builder.WebHost.ConfigureKestrel(options { // 只監(jiān)聽回環(huán)地址公網(wǎng)流量必須經(jīng)過前置代理防止繞過 options.ListenLocalhost(5000); // 對應(yīng) IIS 的 maxAllowedContentLength默認(rèn)值往往撐不住大文件上傳 options.Limits.MaxRequestBodySize 100 * 1024 * 1024; // 長上傳或慢客戶端場景下默認(rèn)的頭部超時可能不夠 options.Limits.RequestHeadersTimeout TimeSpan.FromSeconds(60); }); builder.Services.ConfigureForwardedHeadersOptions(options { options.ForwardedHeaders ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; // 代理與應(yīng)用同機(jī)部署時清空已知網(wǎng)絡(luò)跨機(jī)部署改成代理所在的內(nèi)網(wǎng)段 options.KnownNetworks.Clear(); options.KnownProxies.Clear(); }); var app builder.Build(); // 必須放在任何讀取 RemoteIpAddress 的中間件之前 app.UseForwardedHeaders(); app.Run();ListenLocalhost決定了應(yīng)用端口的暴露范圍改成ListenAnyIP就等于把應(yīng)用直接掛到公網(wǎng)這一步別圖省事。MaxRequestBodySize的單位是字節(jié)和 IIS 里按 KB 計的maxRequestLength換算容易出錯遷移時對著原配置逐項核一遍。UseForwardedHeaders的位置極其關(guān)鍵放在鑒權(quán)中間件之后的話限流和審計拿到的仍然是代理地址。3.5 同一個站點(diǎn)在三種方案下的配置對照光看文字不直觀把同一個站點(diǎn)——靜態(tài)資源加/api/反向代理——分別寫成 Nginx 和 Caddy 的配置差異一眼就能看出來。# Nginx站點(diǎn)綁定 靜態(tài)資源 反向代理 server { listen 80; server_name app.internal.example; # 換成你自己的域名 return 301 https://$host$request_uri; } server { listen 443 ssl; http2 on; server_name app.internal.example; ssl_certificate /etc/ssl/site/fullchain.pem; ssl_certificate_key /etc/ssl/site/privkey.pem; # IIS 的請求體限制在這里沒有等價默認(rèn)值必須顯式放寬 client_max_body_size 100m; client_body_timeout 120s; root /srv/app/wwwroot; index index.html; # 對應(yīng) IIS 里 SPA 的 URL 重寫回退規(guī)則 location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:5000; proxy_http_version 1.1; # 這幾行決定了后端能不能拿到真實(shí)客戶端信息 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; # WebSocket 與長連接場景必須顯式聲明 proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_read_timeout 120s; } }# Caddy同樣的站點(diǎn)證書由它自己申請和續(xù)期 app.internal.example { encode gzip zstd root * /srv/app/wwwroot # 反向代理規(guī)則寫在前面命中 /api/ 的請求不進(jìn)靜態(tài)文件邏輯 handle /api/* { reverse_proxy 127.0.0.1:5000 { header_up X-Real-IP {remote_host} } } # SPA 回退找不到文件時交給 index.html try_files {path} /index.html file_server }兩份配置的差別不只是行數(shù)。Nginx 版本里證書路徑、續(xù)期任務(wù)、日志格式、請求體限制全部要人寫Caddy 版本里證書那一段直接消失了請求體限制默認(rèn)就足夠?qū)捤纱鷥r是你對細(xì)節(jié)的掌控少一些。遷移時我的習(xí)慣是先寫 Caddy 版本驗證業(yè)務(wù)能不能跑通確認(rèn)沒問題再決定要不要換成 Nginx 做精細(xì)調(diào)優(yōu)這樣能快速排掉是不是服務(wù)器軟件的問題這類干擾。4. 遷移實(shí)戰(zhàn)從 IIS 站點(diǎn)到新服務(wù)器的四步走方案定下來之后真正的活兒才開始。整個遷移我會拆成四步盤點(diǎn)、分類、改寫、切換。順序不能亂跳過盤點(diǎn)直接改配置大概率會在某個不起眼的模塊上翻車。4.1 遷移前盤點(diǎn)五張清單缺一不可盤點(diǎn)做得越細(xì)后面返工越少。我一般會整理出五張清單缺一張都會在切換當(dāng)天出問題。清單要記錄的內(nèi)容漏了會怎樣站點(diǎn)與應(yīng)用池站點(diǎn)名、物理路徑、應(yīng)用池身份、32/64 位設(shè)置、啟動模式權(quán)限不對導(dǎo)致 500或舊組件加載失敗綁定信息協(xié)議、端口、主機(jī)頭、證書、是否需要 SNI多站點(diǎn)共用 IP 時綁錯串站配置文件web.config 全文、重寫規(guī)則、模塊注冊、自定義頭重寫規(guī)則漏翻頁面 404外部依賴數(shù)據(jù)庫連接、文件共享、定時任務(wù)、第三方接口白名單切換后接口全超時可寫目錄上傳目錄、日志目錄、緩存目錄上傳失敗且日志里看不出原因第一張清單里最容易漏的是應(yīng)用池身份。IIS 里站點(diǎn)默認(rèn)以某個應(yīng)用池身份運(yùn)行這個身份對物理路徑、對數(shù)據(jù)庫、對共享目錄都有授權(quán)換成新服務(wù)器后進(jìn)程用戶變了權(quán)限全斷。第二張里最容易漏的是 SNI一臺機(jī)器多個證書時沒有 SNI 會返回錯誤的證書。4.2 三類站點(diǎn)的搬遷路徑不同類型站點(diǎn)的做法差別很大混在一起談容易誤導(dǎo)。純靜態(tài)站最簡單文件拷過去注意保留目錄結(jié)構(gòu)和文件名大小寫寫一個 server 塊或者一段 Caddyfile加上緩存頭和壓縮基本就完事。唯一要小心的是中文文件名和原來依賴的默認(rèn)文檔順序。.NET 應(yīng)用分兩種。較新的框架版本本身跨平臺直接把發(fā)布產(chǎn)物拷到目標(biāo)機(jī)器用 Kestrel 跑起來前置加一層反向代理配置文件里的連接字符串和路徑改成新環(huán)境的。老一些的 WebForms 或者重度依賴 Windows 組件的項目不要硬遷讓它留在 Windows 上只是把 IIS 換成反向代理 應(yīng)用內(nèi)服務(wù)器的組合先把入口層統(tǒng)一應(yīng)用本身慢慢重寫。經(jīng)典 ASP 這類老技術(shù)?;緵]有官方跨平臺方案?,F(xiàn)實(shí)做法是繼續(xù)保留一臺 Windows 主機(jī)專門跑它外面統(tǒng)一用一層反向代理把流量分過來等業(yè)務(wù)下線或者重寫到新框架再徹底關(guān)掉。這個決定不好看但比強(qiáng)行遷移再回滾要省事得多。4.3 配置改寫對照從 web.config 到新配置改寫階段最實(shí)用的是一張對照表照著填比憑記憶靠譜。IIS 配置位置作用新方案對應(yīng)做法httpRuntime maxRequestLength請求體大小KBNginxclient_max_body_sizeKestrelMaxRequestBodySizerequestFiltering maxAllowedContentLength請求體大小字節(jié)同上注意單位換算rewrite/rulesURL 重寫Nginxrewrite/try_filesCaddyrewrite/try_fileshttpProtocol customHeaders自定義響應(yīng)頭Nginxadd_headerCaddyheaderdefaultDocument默認(rèn)文檔順序NginxindexstaticContent mimeMap補(bǔ)充 MIME 類型Nginxtypes塊Caddy 多數(shù)內(nèi)置httpErrors自定義錯誤頁Nginxerror_pageapplicationInitialization應(yīng)用預(yù)熱systemd 啟動順序容器健康檢查security/authenticationWindows 集成認(rèn)證需單獨(dú)方案通常改為統(tǒng)一認(rèn)證網(wǎng)關(guān)重寫規(guī)則是最容易出錯的一塊。IIS 的規(guī)則里常見^$、^(.)$這類寫法配合{R:1}做反向引用翻譯到 Nginx 時要換成$1同時注意rewrite默認(rèn)在 server 上下文里對 URI 做匹配而location匹配的是解碼后的路徑。尾部斜杠尤其坑人IIS 默認(rèn)對目錄補(bǔ)斜杠Nginx 需要try_files $uri $uri/才會做同樣的事少寫一個$uri/目錄訪問就變成 404。4.4 切換與驗證灰度、回滾、驗收項切換那天不要一次性把流量全切過去。我的做法是讓新舊兩套并行跑一段時間先通過內(nèi)網(wǎng) hosts 或者測試域名驗證功能再按比例把流量引到新服務(wù)器觀察至少一個完整業(yè)務(wù)周期。日志里的 5xx 比例、接口平均耗時、上傳成功率這三個指標(biāo)是核心觀察項?;貪L開關(guān)必須提前準(zhǔn)備好而且要是停流量而不是停服務(wù)——舊服務(wù)器保持運(yùn)行狀態(tài)只是不接新流量出問題時改一行上游配置就能切回來。這一條是后悔藥希望你用不上但一定要有。驗收項至少覆蓋首頁與深層鏈接、登錄與鑒權(quán)、文件上傳下載含大文件、帶中文的參數(shù)與文件名、靜態(tài)資源緩存命中、WebSocket 或長輪詢連接、錯誤頁返回碼、以及日志里客戶端 IP 是否真實(shí)。每一條都實(shí)際點(diǎn)一遍別只看監(jiān)控面板上的曲線。5. 避坑與排查遷移路上最容易翻車的五個點(diǎn)下面這五條是我自己或者身邊人真實(shí)踩過的按「現(xiàn)象 → 原因 → 解決」寫遇到同類問題時可以直接對照。5.1 上傳接口集體失敗返回 413 或 500現(xiàn)象站點(diǎn)其他功能正常只有上傳接口掛掉小文件能傳超過幾兆就失敗日志里要么是 413要么是一句沒有上下文的 500。原因IIS 里maxRequestLength和maxAllowedContentLength兩個限制同時存在一個按 KB 一個按字節(jié)很多人只調(diào)了其中一個。換到新服務(wù)器后這兩個限制都不存在了取而代之的是 Nginx 的client_max_body_size默認(rèn)值很小和 Kestrel 的MaxRequestBodySize默認(rèn)也有上限任何一層沒放開都會攔下來。解決把鏈路每一層的限制都顯式寫出來Nginx 的client_max_body_size、Kestrel 的MaxRequestBodySize、以及應(yīng)用框架自身如果有獨(dú)立限制也要一起改。改完用一個剛好超過原限制的文件實(shí)測別用幾十 K 的小文件自欺欺人。另外client_body_timeout也要跟著調(diào)大文件慢速上傳時默認(rèn)超時會提前斷開。5.2 日志里客戶端 IP 全變成 127.0.0.1現(xiàn)象切換后風(fēng)控規(guī)則全部失效日志里所有請求的來源地址都是本機(jī)回環(huán)地址限流和黑名單形同虛設(shè)。原因請求經(jīng)過了前置反向代理應(yīng)用看到的是代理的地址。IIS 時代如果啟用了反向代理模塊它會幫忙處理轉(zhuǎn)發(fā)頭換成 Nginx 加 Kestrel 之后轉(zhuǎn)發(fā)頭需要顯式設(shè)置和顯式信任缺任何一環(huán)都不生效。解決代理側(cè)加proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for和X-Forwarded-Proto應(yīng)用側(cè)啟用轉(zhuǎn)發(fā)頭中間件并把它放在管道最前面。如果是跨機(jī)部署還要把代理所在的內(nèi)網(wǎng)段加進(jìn)已知網(wǎng)絡(luò)否則框架出于安全考慮會忽略這些頭。驗證方法很直接隨便發(fā)一個請求看應(yīng)用日志里記錄的 IP 是不是你自己的出口地址。5.3 Linux 上路徑大小寫不一致導(dǎo)致 404現(xiàn)象頁面能打開但部分圖片、樣式、接口 404開發(fā)環(huán)境完全正常。原因原來跑在 Windows 上文件系統(tǒng)對大小寫不敏感代碼里寫~/Content/Site.css還是~/content/site.css都能找到文件換到大小寫敏感的文件系統(tǒng)后只要有一個字母對不上就是 404。反斜杠也是同理\在 Windows 里是路徑分隔符在別的系統(tǒng)里不是。解決遷移前用腳本掃一遍代碼和配置里的靜態(tài)資源引用把大小寫和分隔符統(tǒng)一。已經(jīng)上線的環(huán)境可以用訪問日志里的 404 路徑反查實(shí)際文件名逐個修正。長期做法是在構(gòu)建流程里加一步校驗避免以后再犯。5.4 中文文件名和 URL 參數(shù)出現(xiàn)亂碼現(xiàn)象下載文件名變成一串百分號編碼或者帶中文的查詢參數(shù)到后端變成問號。原因文件系統(tǒng)編碼、HTTP 響應(yīng)頭編碼、頁面編碼三者不一致。IIS 環(huán)境下很多項目默認(rèn)走的是本地編碼遷到新環(huán)境后統(tǒng)一成了 UTF-8原來恰好能跑的代碼就暴露了問題。下載場景里常見的是Content-Disposition只寫了filename沒有按規(guī)范補(bǔ)filename*。解決統(tǒng)一全鏈路 UTF-8包括文件系統(tǒng)、應(yīng)用配置、響應(yīng)頭、數(shù)據(jù)庫連接。下載接口按規(guī)范同時輸出filename和filename*兩個參數(shù)兼容舊客戶端。URL 里的中文參數(shù)在代理層不要做二次編碼Nginx 默認(rèn)會對 URI 做一次解碼配置里如果開了二次解碼反而會亂。5.5 WebSocket 和長連接在代理后面連不上現(xiàn)象實(shí)時推送功能在測試環(huán)境正常切換后一直 502 或者連接幾秒就斷。原因WebSocket 需要Upgrade和Connection兩個頭透傳Nginx 默認(rèn)不透傳這兩項另外長連接的空閑超時默認(rèn)值偏短連接被代理主動斷掉。如果用了平滑重載工作進(jìn)程退出時也會把長連接一起切斷。解決代理的 location 里顯式加proxy_set_header Upgrade $http_upgrade和proxy_set_header Connection upgrade把proxy_read_timeout調(diào)到業(yè)務(wù)能接受的范圍。重載時設(shè)置工作進(jìn)程的優(yōu)雅退出時間讓存量連接自然結(jié)束再退出。切換前一定要單獨(dú)測一遍實(shí)時功能這類問題不會在普通頁面上暴露出來。6. 按場景落地三個答案加一條驗證命令把上面的內(nèi)容收成三個可執(zhí)行的判斷你對號入座就行。你的場景推薦方案核心理由站點(diǎn)多、證書雜、運(yùn)維人手少Caddy 獨(dú)立承接證書自動簽發(fā)續(xù)期配置量最小以靜態(tài)資源和反向代理為主團(tuán)隊熟悉主流方案Nginx 獨(dú)立承接資料多、招人容易、調(diào)優(yōu)空間大純 .NET 服務(wù)追求部署簡單Kestrel 加 Nginx 或 Caddy 前置應(yīng)用與服務(wù)器職責(zé)分離升級只重啟進(jìn)程依賴 Windows 組件或經(jīng)典 ASP保留 Windows 主機(jī)入口層換反向代理硬遷成本遠(yuǎn)高于分層過渡定完方案之后別急著切流量先跑一條最樸素的驗證命令。下面這段用 curl 把整條鏈路的關(guān)鍵行為一次性驗一遍包括大文件上傳、轉(zhuǎn)發(fā)頭、錯誤頁返回碼。# 1. 大文件上傳驗證請求體限制是否放開構(gòu)造 50MB 隨機(jī)文件 head -c 52428800 /dev/urandom /tmp/probe.bin curl -s -o /dev/null -w upload:%{http_code}\n \ -F file/tmp/probe.bin https://app.internal.example/api/upload # 2. 轉(zhuǎn)發(fā)頭確認(rèn)后端拿到的客戶端地址不是 127.0.0.1 curl -s https://app.internal.example/api/whoami # 3. 深層鏈接確認(rèn) SPA 回退規(guī)則生效返回 200 而不是 404 curl -s -o /dev/null -w spa:%{http_code}\n \ https://app.internal.example/some/deep/route # 4. 錯誤頁請求不存在的接口確認(rèn)返回碼和自定義頁都符合預(yù)期 curl -s -o /dev/null -w error:%{http_code}\n \ https://app.internal.example/api/not-exist四個檢查項分別對應(yīng)四類最容易出問題的地方。第一項失敗說明請求體限制沒改全第二項失敗說明轉(zhuǎn)發(fā)頭鏈路斷了第三項失敗說明重寫或回退規(guī)則翻錯了第四項失敗多半是錯誤頁配置把返回碼一并改成了 200而返回碼被改寫會直接影響監(jiān)控告警和搜索引擎行為比頁面顯示錯誤更隱蔽。說實(shí)話我最早做這類遷移時吃過最大的虧不是技術(shù)選型選錯而是沒把「原環(huán)境到底依賴了什么」當(dāng)回事。那次上傳翻車之后我給自己定了個死規(guī)矩不管站點(diǎn)多小動手前必須把 web.config 從頭到尾讀一遍把每一條限制、每一條重寫規(guī)則都抄到一張表上逐條標(biāo)注在新環(huán)境怎么落地。這個動作看著笨但它把不確定性從切換當(dāng)天才發(fā)現(xiàn)挪到了動手之前就清楚后面所有事都輕松了。希望這些踩坑記錄能幫你少走一段彎路。本文還有配套的精品資源點(diǎn)擊獲取