)
1. 項目背景與整體部署思路拆解1.1 為什么要把Nodejs項目放到Windows服務器上很多做前端或者全棧的朋友第一反應是Nodejs項目就該跑在Linux上用pm2一掛Nginx一配完事。但現實情況是不少中小團隊、傳統(tǒng)企業(yè)的IT基礎設施就是Windows Server運維同事對IIS和Windows服務更熟悉機房里的機器也是Windows環(huán)境。你不可能因為自己習慣Linux就要求整個運維體系為你改。所以把Nodejs項目部署到Windows服務器并且用Nginx做反向代理是一個非常實際的需求。這個方案能解決什么問題簡單說就是讓你的Nodejs應用在Windows服務器上穩(wěn)定跑起來外部請求通過Nginx轉發(fā)到Node進程同時Nginx還能順便處理靜態(tài)資源、負載均衡、SSL終止這些活。適合誰參考適合手頭只有Windows服務器、又不想把Node應用直接暴露在外面的開發(fā)者也適合剛接觸服務端部署、想找一個完整可復現方案的新手。1.2 整體架構與請求鏈路先把這個方案的架構說清楚不然后面配起來容易暈。整個鏈路是這樣的用戶請求先到NginxNginx監(jiān)聽80或者443端口根據配置的規(guī)則把動態(tài)請求轉發(fā)給本機的Node進程Node進程監(jiān)聽一個內部端口比如3000或者8080。靜態(tài)文件比如圖片、CSS、JSNginx可以直接從磁盤讀不用經過Node這樣能減輕Node的壓力。為什么不讓Node直接對外原因有幾個。第一Node處理靜態(tài)文件的效率不如NginxNginx底層是事件驅動處理靜態(tài)資源非???。第二直接用Node對外端口管理、SSL證書、訪問日志這些都要自己寫Nginx現成的功能不用白不用。第三后面如果要加緩存、限流、多實例負載均衡Nginx層做比在Node里做方便得多。1.3 方案選型的幾個關鍵決策在Windows上跑Node進程管理是個繞不開的問題。Linux上有pm2、systemdWindows上也有pm2但pm2在Windows上的自啟和穩(wěn)定性需要額外處理。另一個選擇是用Windows服務的方式借助node-windows或者nssm把Node進程注冊成系統(tǒng)服務。我實測下來nssm更穩(wěn)配置也直觀后面會詳細講。Nginx在Windows上的版本和Linux版本功能基本一致但有一點要注意Windows版的Nginx不支持epoll用的是select模型高并發(fā)下性能不如Linux。不過對于中小流量場景完全夠用。另外Windows版Nginx沒有官方的服務封裝需要自己用nssm或者winsw把它注冊成服務否則服務器重啟后Nginx不會自動起來。提示如果你的服務器流量很大比如日均幾十萬請求建議還是優(yōu)先考慮Linux環(huán)境。Windows方案更適合內部系統(tǒng)、中小型對外服務、或者運維體系已經固定為Windows的場景。2. 環(huán)境準備與核心工具選型2.1 Nodejs運行環(huán)境的安裝與版本選擇第一步是在Windows服務器上裝Nodejs。直接去官網下載LTS版本的msi安裝包雙擊安裝就行。安裝路徑建議不要放在C盤默認的Program Files下因為后面配置服務的時候路徑里有空格會比較麻煩。我一般裝在D:\nodejs這種沒有空格的路徑下。安裝完成后打開cmd或者PowerShell執(zhí)行node -v和npm -v確認版本。這里有個坑如果你服務器上之前裝過舊版本最好先卸載干凈再裝新版本。不同大版本之間的Nodenpm包兼容性可能有問題尤其是涉及原生模塊編譯的時候。版本選擇上建議用當前LTS版本比如18.x或者20.x。不要追最新的奇數版本那些是實驗性的生產環(huán)境求穩(wěn)。裝完之后把npm的源換成國內鏡像不然裝依賴的時候會慢到懷疑人生。命令是npm config set registry https://registry.npmmirror.com這個操作能省很多時間。2.2 Nginx for Windows的下載與目錄規(guī)劃Nginx官網有Windows版的zip包下載下來解壓到一個固定目錄比如D:\nginx。解壓后的目錄結構里conf放配置文件logs放日志html放默認靜態(tài)頁。你不需要安裝解壓就能用但需要手動啟動。目錄規(guī)劃上我建議把Nginx和Node項目分開放比如Nginx在D:\nginxNode項目在D:\apps\myproject。這樣后面配路徑的時候清晰備份和遷移也方便。Nginx的配置文件主要改conf\nginx.conf但更好的做法是在conf下新建一個vhost目錄把每個站點的配置單獨放一個文件然后在nginx.conf里用include引入。這樣以后加站點不用動主配置。注意Windows版Nginx的路徑分隔符用正斜杠/或者雙反斜杠\\不要用單反斜杠否則會解析出錯。這是新手最容易踩的坑之一。2.3 進程管理工具nssm的引入理由前面說了Windows上要讓Node進程穩(wěn)定跑最好注冊成系統(tǒng)服務。nssm是一個輕量的服務封裝工具可以把任意exe注冊成Windows服務支持自動重啟、日志重定向、啟動參數配置。相比pm2nssm的優(yōu)勢是它本身就是Windows服務機制不依賴額外的守護進程服務器重啟后自動拉起。下載nssm后解壓得到nssm.exe放到一個固定目錄比如D:\tools\nssm。然后把這個目錄加到系統(tǒng)環(huán)境變量Path里這樣在任意位置都能調用nssm命令。后面配置Node服務和Nginx服務都會用到它。2.4 工具選型對比表工具用途優(yōu)勢注意事項Nodejs LTS運行環(huán)境穩(wěn)定、生態(tài)成熟路徑避免空格換國內源Nginx Windows版反向代理配置靈活、靜態(tài)資源高效需手動注冊服務不支持epollnssm服務封裝輕量、自動重啟、日志重定向路徑加入環(huán)境變量pm2進程管理功能豐富、跨平臺Windows自啟需額外配置這張表是我自己在選型時整理的核心邏輯就是能用系統(tǒng)原生機制就用原生nssm比pm2在Windows上更貼合系統(tǒng)。當然如果你團隊已經熟悉pm2繼續(xù)用也行只是自啟那塊要多做一步。3. Nodejs項目的部署與實操配置3.1 項目上傳與依賴安裝把Node項目傳到服務器上可以用Git克隆也可以直接壓縮包上傳。我一般用Git方便后續(xù)更新。項目放到D:\apps\myproject然后在項目根目錄打開cmd執(zhí)行npm install --production。加--production是為了不裝devDependencies減少體積和潛在的安全風險。安裝過程中如果遇到原生模塊編譯失敗比如node-sass、bcrypt這類需要先裝Windows Build Tools。命令是npm install --global windows-build-tools但這個包已經廢棄了現在更推薦裝Visual Studio Build Tools勾選C編譯工具。這一步比較耗時但一次裝好后面就省心了。安裝完成后先本地跑一下node app.js或者npm start確認項目能正常啟動監(jiān)聽端口比如3000。如果啟動報錯先解決代碼層面的問題不要急著配服務。我見過有人服務配了半天最后發(fā)現是項目本身跑不起來白白浪費時間。3.2 用nssm把Node進程注冊成Windows服務確認項目能跑之后開始注冊服務。打開cmd執(zhí)行nssm install MyNodeApp會彈出一個圖形界面。在Path里選node.exe的完整路徑比如D:\nodejs\node.exe。在Startup directory里填項目根目錄D:\apps\myproject。在Arguments里填入口文件比如app.js或者server.js。切換到I/O標簽頁配置日志輸出。Output填D:\logs\mynode_out.logError填D:\logs\mynode_err.log。這樣Node的console輸出和錯誤都會寫到文件里排查問題的時候非常有用。再切換到Exit actions標簽頁設置重啟延遲比如Restart delay填5000毫秒這樣進程意外退出后5秒自動重啟。配置完成后點Install service然后執(zhí)行nssm start MyNodeApp啟動服務。用nssm status MyNodeApp查看狀態(tài)顯示SERVICE_RUNNING就說明成功了。這時候打開瀏覽器訪問http://localhost:3000應該能看到你的應用。實操心得nssm的圖形界面雖然方便但如果你要批量部署或者寫自動化腳本可以用命令行方式nssm install MyNodeApp D:\nodejs\node.exe app.js然后nssm set MyNodeApp AppDirectory D:\apps\myproject。命令行方式更適合寫進部署腳本。3.3 環(huán)境變量與端口配置的注意事項Node項目里經常用環(huán)境變量來區(qū)分開發(fā)和生產比如process.env.NODE_ENV和process.env.PORT。用nssm注冊服務的時候默認不會帶上你cmd里設置的環(huán)境變量。需要在nssm的Environment標簽頁里手動添加每行一個格式是KEYVALUE。比如添加NODE_ENVproduction和PORT3000。這樣Node進程啟動時就能讀到正確的配置。如果你項目里用了.env文件那要確保dotenv這類庫能正確加載路徑要對。我建議生產環(huán)境盡量用系統(tǒng)環(huán)境變量而不是.env文件因為.env文件容易被誤提交到代碼倉庫有安全風險。端口選擇上Node監(jiān)聽一個內部端口比如3000、8080、5000不要用80或者443那些留給Nginx。端口號在項目配置里寫死或者通過環(huán)境變量傳入都行關鍵是要和后面Nginx配置里的轉發(fā)目標一致。3.4 驗證Node服務是否穩(wěn)定運行服務注冊好之后不要急著配Nginx先觀察一下Node服務是否穩(wěn)定??梢宰鰩讉€測試第一用nssm restart MyNodeApp重啟服務看能否正常拉起。第二用任務管理器殺掉node進程看nssm是否自動重啟。第三重啟整個服務器看服務是否自動啟動。這三個測試都通過說明Node服務這塊沒問題了。我遇到過nssm配置了自動重啟但沒生效的情況后來發(fā)現是Exit actions里的Restart選項沒選對默認是No action需要手動改成Restart application。這個細節(jié)很容易忽略但很關鍵。4. Nginx反向代理的配置與調優(yōu)4.1 Nginx核心配置文件的編寫Nginx的反向代理配置是整個方案的核心。打開D:\nginx\conf\nginx.conf找到http塊在里面加一個server塊。最基礎的配置大概是這樣server { listen 80; server_name your_domain.com; location / { proxy_pass http://127.0.0.1:3000; proxy_http_version 1.1; 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; } }這幾行配置的意思是Nginx監(jiān)聽80端口收到請求后轉發(fā)給本機3000端口的Node服務。proxy_set_header那幾行是把原始請求的信息傳給Node不然Node拿到的客戶端IP全是127.0.0.1日志和業(yè)務邏輯都會出問題。proxy_http_version 1.1這行也建議加上因為HTTP/1.1支持長連接性能比1.0好。如果你后面要用WebSocket還需要加proxy_set_header Upgrade $http_upgrade和proxy_set_header Connection upgrade。4.2 靜態(tài)資源分離與緩存策略如果項目里有靜態(tài)文件比如圖片、CSS、JS建議讓Nginx直接處理不要轉發(fā)給Node。配置大概是這樣location /static/ { root D:/apps/myproject/public; expires 30d; add_header Cache-Control public, immutable; } location / { proxy_pass http://127.0.0.1:3000; ... }expires 30d是設置瀏覽器緩存30天Cache-Control里的immutable告訴瀏覽器這個文件內容不會變可以放心緩存。這樣用戶第二次訪問的時候靜態(tài)資源直接從本地緩存讀不用再請求服務器速度飛快。注意Windows下Nginx的root路徑要用正斜杠比如D:/apps/myproject/public。如果用反斜杠需要寫成雙反斜杠D:\\apps\\myproject\\public否則會報錯。4.3 反向代理的關鍵參數調優(yōu)默認的代理配置在高并發(fā)或者慢請求場景下可能會出問題需要調幾個參數。第一個是proxy_connect_timeout設置Nginx和后端Node建立連接的超時時間默認60秒可以改成10秒避免請求堆積。第二個是proxy_read_timeout設置讀取后端響應的超時時間如果Node處理某個請求特別慢這個值要調大否則Nginx會提前斷開。第三個是proxy_buffer_size和proxy_buffers控制Nginx緩存后端響應的緩沖區(qū)大小。如果后端返回的響應頭很大比如帶了很多Cookie默認的緩沖區(qū)可能不夠會報upstream sent too big header錯誤。這時候需要調大proxy_buffer_size 16k; proxy_buffers 4 32k; proxy_busy_buffers_size 64k;這幾個值不是越大越好要根據實際響應大小來調。我一般先按默認跑遇到報錯再針對性調整。4.4 把Nginx注冊成Windows服務Nginx本身不會自動啟動需要注冊成服務。用nssm執(zhí)行nssm install Nginx在Path里選D:\nginx\nginx.exeStartup directory填D:\nginx。Arguments留空就行。然后在Exit actions里設置重啟。這里有個細節(jié)Nginx在Windows上啟動后會在logs目錄下生成nginx.pid文件。如果Nginx異常退出這個pid文件可能殘留導致下次啟動失敗。可以在nssm的Exit actions里加一個腳本啟動前先刪除pid文件或者手動處理。我一般是在服務啟動前用taskkill確保沒有殘留的nginx進程。配置完成后啟動服務訪問http://localhost如果能看到你的Node應用說明反向代理配通了。4.5 配置驗證與常見錯誤排查配完Nginx后先執(zhí)行nginx -t檢查配置文件語法。這個命令會告訴你配置文件有沒有語法錯誤以及錯誤在哪個文件的哪一行。確認無誤后再nginx -s reload重載配置不要直接重啟reload是平滑的不會中斷現有連接。如果訪問報502 Bad Gateway說明Nginx連不上Node服務。排查步驟第一確認Node服務在跑nssm status MyNodeApp看狀態(tài)。第二確認端口對netstat -ano | findstr 3000看端口是否監(jiān)聽。第三確認防火墻沒攔Windows防火墻默認可能攔了3000端口但因為是本機轉發(fā)一般不受影響。如果報404說明Nginx轉發(fā)過去了但Node沒有對應的路由。檢查proxy_pass的路徑和Node路由是否匹配。如果報504說明Node處理超時檢查proxy_read_timeout設置和Node本身的性能。5. 常見問題與排查技巧實錄5.1 服務啟動失敗與端口占用排查Windows上端口占用是高頻問題。Node服務啟動失敗日志里寫EADDRINUSE說明端口被占了。用netstat -ano | findstr 3000找到占用端口的進程PID然后tasklist | findstr PID看是什么程序。如果是之前殘留的node進程用taskkill /F /PID xxx殺掉。Nginx啟動失敗常見原因是80端口被IIS或者其它Web服務占了。Windows Server默認可能裝了IISIIS會監(jiān)聽80端口。要么停掉IIS要么把Nginx換成其它端口。我一般建議直接停掉IIS因為既然用Nginx了IIS就沒必要留著。還有一種情況是nssm注冊服務時路徑填錯服務啟動后立即停止。這時候去Windows事件查看器里看應用程序日志nssm會把錯誤寫進去。或者直接看nssm配置的Error日志文件里面會有詳細報錯。5.2 反向代理后獲取真實IP失敗的處理前面配置里加了X-Real-IP和X-Forwarded-For但Node代碼里要正確讀取才行。Express框架下需要設置app.set(trust proxy, true)然后用req.ip或者req.headers[x-forwarded-for]獲取。如果不設置trust proxyExpress會把代理IP當成客戶端IP。如果用了其它框架原理一樣就是信任代理頭。但要注意X-Forwarded-For是可以偽造的如果直接暴露給公網惡意用戶可以偽造IP。所以這個頭只在Nginx轉發(fā)時設置并且Nginx要配置proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for這樣Nginx會把真實客戶端IP追加進去。5.3 靜態(tài)資源404與路徑大小寫問題Windows文件系統(tǒng)不區(qū)分大小寫但Nginx配置里的路徑和URL是區(qū)分大小寫的。如果請求的URL是/Static/logo.png而實際目錄是static在Windows上可能能讀到但Nginx的location匹配是按配置來的如果location寫的是/static/那/Static/就匹配不上會走到默認的代理規(guī)則然后Node返回404。解決辦法是統(tǒng)一URL和目錄的大小寫規(guī)范location匹配用~*做不區(qū)分大小寫的正則匹配比如location ~* ^/static/。但正則匹配性能不如前綴匹配所以更好的做法是前端代碼里統(tǒng)一用小寫路徑。5.4 服務自動重啟與日志切割nssm的自動重啟前面說了但日志文件會一直增長時間長了占滿磁盤。需要做日志切割。nssm本身不支持按大小切割可以用Windows的任務計劃程序每天定時把日志文件重命名歸檔然后重啟服務生成新日志?;蛘哂靡恍┤罩竟芾砉ぞ?。我自己的做法是寫一個bat腳本每天凌晨執(zhí)行把mynode_out.log重命名為mynode_out_日期.log然后清空原文件。Nginx的日志切割類似Nginx支持nginx -s reopen重新打開日志文件配合任務計劃可以實現切割。5.5 常見問題速查表問題現象可能原因排查方法解決方案502 Bad GatewayNode服務未啟動或端口不對檢查nssm狀態(tài)和netstat端口啟動服務或修正端口504 Gateway TimeoutNode處理超時查看Node日志和proxy_read_timeout調大超時或優(yōu)化Node性能404 Not Found路由不匹配或靜態(tài)路徑錯誤檢查Nginx location和Node路由修正配置或路徑端口占用殘留進程或其它服務占用netstat -ano查PID殺掉進程或換端口服務不自啟nssm配置錯誤查看事件查看器和nssm日志修正Path和Exit actions真實IP獲取失敗未設置trust proxy檢查Node代碼和代理頭設置trust proxy并讀X-Forwarded-For這張表是我在實際部署中反復用到的基本上覆蓋了80%的問題。遇到新問題先按這個表排查大部分都能解決。5.6 幾個容易被忽略的實操細節(jié)第一個細節(jié)Windows Server的防火墻默認可能攔了外部對80端口的訪問。需要在防火墻入站規(guī)則里加一條允許80和443端口。這個不配的話本機訪問正常但外部訪問不通很容易誤判為Nginx配置問題。第二個細節(jié)Nginx的worker_processes在Windows上建議設為1。因為Windows版Nginx的多進程模型和Linux不同設多了反而可能有問題。默認配置里是worker_processes 1保持默認就行。第三個細節(jié)Node項目里的文件路徑要用path.join或者path.resolve不要硬編碼/或者\。Windows和Linux的路徑分隔符不同硬編碼會導致跨平臺問題。雖然部署在Windows上但開發(fā)環(huán)境可能是Mac或者Linux代碼要兼容。第四個細節(jié)如果項目用了WebSocketNginx配置里除了Upgrade和Connection頭還要注意proxy_read_timeout要設大一點因為WebSocket是長連接默認60秒會被斷開??梢栽O成3600秒或者更長。6. 性能監(jiān)控與后續(xù)擴展思路6.1 基礎監(jiān)控指標的采集服務跑起來之后要知道它健不健康。最基礎的監(jiān)控指標包括CPU和內存占用、Node進程的響應時間、Nginx的活躍連接數、錯誤日志里的報錯頻率。Windows自帶的性能監(jiān)視器可以看CPU和內存但不夠細。我一般會在Node里加一個健康檢查接口比如/health返回{status: ok, uptime: process.uptime()}。然后用一個定時任務每隔幾分鐘請求一次記錄響應時間。如果響應時間突然變長或者請求失敗就說明有問題。這個接口也可以給Nginx的upstream健康檢查用。Nginx的stub_status模塊可以暴露一些基礎指標比如活躍連接數、總請求數。在配置里加一個locationlocation /nginx_status { stub_status; allow 127.0.0.1; deny all; }這樣本機訪問http://127.0.0.1/nginx_status就能看到Nginx的運行狀態(tài)。注意要限制訪問來源不要暴露到公網。6.2 多實例部署與負載均衡的擴展如果單實例Node扛不住流量可以在同一臺服務器上起多個Node實例監(jiān)聽不同端口然后用Nginx做負載均衡。配置大概是這樣upstream node_cluster { server 127.0.0.1:3000; server 127.0.0.1:3001; server 127.0.0.1:3002; } server { listen 80; location / { proxy_pass http://node_cluster; } }每個Node實例用單獨的nssm服務管理端口通過環(huán)境變量區(qū)分。這樣一臺服務器上可以跑多個實例充分利用多核CPU。Nginx默認的負載均衡策略是輪詢也可以改成least_conn或者ip_hash根據業(yè)務需求選。提示多實例部署時如果Node項目里有本地文件上傳或者session存儲要注意多實例之間的數據同步問題。文件上傳建議放到共享存儲或者對象存儲session建議用Redis集中管理不要用內存session。6.3 HTTPS配置與證書管理現在網站基本都要HTTPSNginx配置SSL很簡單。拿到證書文件后在server塊里加server { listen 443 ssl; server_name your_domain.com; ssl_certificate D:/nginx/ssl/your_domain.crt; ssl_certificate_key D:/nginx/ssl/your_domain.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ... }同時把80端口的請求重定向到443server { listen 80; server_name your_domain.com; return 301 https://$host$request_uri; }證書文件路徑同樣用正斜杠。證書到期前要記得續(xù)期可以寫個腳本定時檢查證書有效期快到期時自動更新并reload Nginx。6.4 部署流程的自動化思路手動部署容易出錯尤其是多臺服務器的時候??梢詫懸粋€簡單的部署腳本用bat或者PowerShell把Git拉取、npm install、nssm restart這幾步串起來。每次更新代碼執(zhí)行一下腳本就行。更進一步可以用CI/CD工具比如Jenkins或者GitLab CI自動構建和部署。但對于Windows服務器CI/CD的配置相對復雜中小團隊用腳本就夠了。關鍵是腳本要能回滾如果新版本啟動失敗能自動切回舊版本。這個可以在腳本里加一個判斷啟動后請求健康檢查接口失敗就回滾。6.5 我個人的一些經驗體會這套方案我在好幾個項目里用過整體是穩(wěn)的。最大的坑其實不在Nginx配置而在Windows的服務管理和路徑處理上。nssm的配置一定要仔細尤其是Path和Startup directory填錯了服務起不來報錯還不明顯。日志文件一定要配不然出了問題兩眼一抹黑。另外Windows Server的更新有時候會自動重啟服務器如果服務沒配自啟那就全掛了。所以nssm的自動啟動和自動重啟一定要測試通過。我一般會在部署完成后手動重啟一次服務器確認所有服務都能自動起來這樣才放心。最后再分享一個小技巧Nginx的配置文件改完后不要直接nginx -s reload先用nginx -t檢查語法。這個習慣能避免很多因為配置寫錯導致的服務中斷。而且reload之前最好備份一下當前配置萬一新配置有問題能快速回滾。