1. 为什么要把Node服务放到Windows Server上跑
很多人一听Node.js部署,第一反应是Linux加pm2,觉得Windows Server跑Node属于"歪门邪道"。但现实情况是,不少中小团队、传统企业的内部系统、甚至一些政企项目,服务器清一色是Windows Server——运维熟悉Windows、采购流程只认Windows授权、已有系统全是IIS和SQL Server,你不可能为了一个Node服务把整个基础设施换掉。所以把Node项目稳稳当当地部署到Windows Server上,并且用Nginx做反向代理,是一项非常实用的技能。
这篇内容适合三类人:一是手里有Windows服务器、需要部署Node后端或前端SSR服务的开发者;二是习惯了Linux部署、第一次接触Windows Server的运维;三是想把本地跑通的Node项目正式上线、但卡在"怎么让它一直活着、怎么让外网访问"这一步的独立开发者。核心要解决的问题就三个:让Node进程在Windows上稳定常驻、让Nginx把外部请求正确转发给Node、让整个链路在服务器重启后能自动恢复。
我踩过的坑主要集中在进程守护和路径处理上。Windows没有Linux那套systemd、supervisor,早期用forever、pm2都遇到过开机不自启、日志乱码、进程莫名消失的问题。Nginx在Windows上的行为和Linux也有差异,比如不支持某些模块、路径分隔符要用反斜杠、worker进程模型不同。下面我把整套流程拆开讲,包括每一步为什么这么做、参数怎么算、以及实测中真正会卡住你的地方。
2. 部署前的环境盘点与版本选择
2.1 Node运行时到底装哪个版本
Windows Server上装Node,第一件事是选版本。我的建议是选当前LTS版本,并且和开发机保持一致。原因很直接:Node的大版本之间原生模块(native addon)ABI不兼容,如果你开发机用Node 20,服务器装Node 18,某些依赖了C++扩展的包(比如某些数据库驱动、图像处理库)在服务器上npm install会直接编译失败或者运行时报错。
具体操作上,去Node官网下载Windows的.msi安装包,不要用zip绿色版。msi安装会自动配置环境变量、注册卸载信息,后续升级也方便。安装时勾选"Add to PATH",这样在任意目录的cmd或PowerShell里都能直接调node和npm。
装完验证:
node -v npm -v如果公司网络访问外网受限,npm装包慢,可以配置国内镜像源:
npm config set registry https://registry.npmmirror.com注意:镜像源只影响下载速度,不影响包内容。但生产环境建议在项目里锁定
package-lock.json,保证每次安装的依赖树完全一致,避免"开发机能跑、服务器报错"。
2.2 Windows Server版本与权限准备
Windows Server 2016及以上都可以,推荐2019或2022。低版本(2012 R2)虽然能跑,但TLS支持和PowerShell版本偏老,装某些工具会麻烦。
权限方面,不要用Administrator账户直接跑Node服务。正确做法是新建一个普通用户(比如叫nodeuser),把项目目录的读写权限授予它,然后用这个账户来运行服务。这样即使服务被攻破,攻击者拿到的也只是低权限账户,不会直接拿到系统控制权。这一点在Linux上大家很自觉,到了Windows反而经常被忽略。
项目目录建议放在非系统盘,比如D:\apps\your-project。系统盘(C盘)容易因为日志、临时文件膨胀导致空间不足,而且系统盘权限管理更严格,容易踩到"拒绝访问"的坑。
2.3 防火墙与端口规划
部署前先规划好端口,避免和已有服务冲突。典型规划:
| 用途 | 端口 | 说明 |
|---|---|---|
| Node应用监听 | 3000 | 只监听127.0.0.1,不对外暴露 |
| Nginx监听 | 80 / 443 | 对外提供HTTP/HTTPS |
| 远程管理 | 3389 | 系统自带,注意限制来源IP |
关键点:Node应用只监听127.0.0.1,不要监听0.0.0.0。因为外部流量统一走Nginx,Node只需要接受本机Nginx的转发。这样即使Node端口没做鉴权,外网也访问不到。在Windows防火墙里,只需要放行80和443,3000端口根本不用开。
3. 让Node进程在Windows上真正"常驻"
3.1 为什么直接node app.js不行
在cmd里敲node app.js,服务确实跑起来了,但只要你关掉这个cmd窗口,或者远程桌面断开,进程就没了。更麻烦的是服务器重启后,没有任何机制把它拉起来。所以必须有一个"进程守护"方案。
Windows上常见的几种方案对比:
| 方案 | 开机自启 | 崩溃重启 | 日志管理 | 推荐度 |
|---|---|---|---|---|
| 直接node | 否 | 否 | 手动 | 不推荐 |
| pm2 | 需额外配置 | 是 | 完善 | 推荐 |
| nssm注册为服务 | 是 | 是 | 一般 | 推荐 |
| Windows任务计划 | 是 | 部分 | 差 | 备选 |
| iisnode | 是 | 是 | 一般 | 特定场景 |
我的首选是pm2 + pm2-windows-startup,或者nssm把Node注册成Windows服务。两者各有取舍,下面分别讲。
3.2 用pm2管理进程的正确姿势
pm2是Node生态里最成熟的进程管理器,跨平台。在Windows上装:
npm install -g pm2 npm install -g pm2-windows-startup启动项目:
cd D:\apps\your-project pm2 start app.js --name "my-node-app"但这里有个大坑:pm2默认不会开机自启。Linux上执行pm2 startup会生成systemd配置,Windows上没这套。需要借助pm2-windows-startup:
pm2-startup install pm2 savepm2 save会把当前进程列表保存下来,pm2-startup install会注册一个开机启动项,重启后自动恢复pm2 save时的进程列表。每次新增或删除进程后,都要重新执行pm2 save,否则重启后恢复的是旧列表,这是最容易忘的一步。
pm2常用命令:
pm2 list # 查看进程 pm2 logs my-node-app # 查看日志 pm2 restart my-node-app pm2 stop my-node-app pm2 delete my-node-app pm2 monit # 实时监控日志默认在C:\Users\你的用户名\.pm2\logs下。Windows上日志文件偶尔会有中文乱码,如果项目日志里有中文,建议在代码里统一用UTF-8输出,或者用pm2-logrotate做日志切割,避免单个日志文件无限增长。
3.3 用nssm把Node注册成系统服务
如果你不想依赖pm2,或者运维团队更习惯"服务"这个概念,nssm是更好的选择。nssm(Non-Sucking Service Manager)能把任意可执行程序包装成Windows服务。
下载nssm后,在命令行执行:
nssm install MyNodeApp会弹出一个图形界面,填三个关键项:
- Path:
C:\Program Files\nodejs\node.exe - Startup directory:
D:\apps\your-project - Arguments:
app.js
然后在"I/O"标签页里配置stdout和stderr的日志文件路径,比如D:\logs\node-out.log和D:\logs\node-err.log。在"Exit actions"里设置崩溃后自动重启。
装好后:
nssm start MyNodeApp nssm status MyNodeAppnssm的好处是它就是一个标准的Windows服务,可以在"服务"管理器里看到,可以设置"自动"启动类型,服务器重启后自动拉起。缺点是日志管理不如pm2灵活,需要自己配合日志切割工具。
实操心得:nssm配置服务时,"Startup directory"一定要填对。很多人只填了Path和Arguments,结果Node启动时找不到相对路径下的配置文件、静态资源,报一堆ENOENT错误。这个目录就是进程的工作目录,等价于你在cmd里先
cd到项目目录再执行node。
3.4 环境变量与配置文件的管理
生产环境不要把数据库密码、密钥硬编码在代码里。Windows上设置环境变量的方式有几种:
- 系统级:
系统属性 -> 高级 -> 环境变量,或者用setx命令 - 服务级:nssm可以在"Environment"标签页里单独给这个服务设环境变量
- 项目级:用
.env文件配合dotenv包
我推荐服务级 + .env结合。敏感信息放服务级环境变量,普通配置放.env。用nssm时,在Environment标签页里填:
NODE_ENV=production DB_PASSWORD=your_password注意setx设置的环境变量对已经运行的服务不生效,必须重启服务。而且setx有长度限制(1024字符),超长会截断,这点要留意。
4. Nginx在Windows上的安装与反向代理配置
4.1 Windows版Nginx的下载与目录结构
Nginx官方提供Windows版,下载zip包解压即可,不需要安装。解压到D:\nginx,目录结构大致是:
nginx/ conf/ nginx.conf html/ logs/ nginx.exe启动、停止、重载:
cd D:\nginx start nginx # 启动 nginx -s reload # 重载配置 nginx -s stop # 快速停止 nginx -s quit # 优雅停止Windows版Nginx有几个必须知道的限制:它用的是select模型而不是epoll,高并发性能不如Linux版;不支持某些Linux专属模块;worker进程数配置意义不大,Windows版实际上以单进程多线程方式工作。所以如果你的场景是超高并发,Windows Nginx只能作为中小流量的反向代理,真扛不住要考虑上Linux或者用其他方案。
4.2 反向代理配置逐行拆解
打开conf/nginx.conf,核心配置如下:
worker_processes 1; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; upstream node_app { server 127.0.0.1:3000; } server { listen 80; server_name your-domain.com; location / { proxy_pass http://node_app; 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; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } } }逐项解释为什么这么配:
upstream node_app:把后端地址抽象成一个名字,后面proxy_pass引用它。好处是以后要加多个Node实例做负载均衡,只改upstream块就行。proxy_http_version 1.1:默认是1.0,改成1.1才能支持长连接和WebSocket。如果你的项目用了WebSocket(比如实时通信、推送),这一行不加就会连接失败。proxy_set_header Host $host:把原始请求的Host传给Node。很多框架靠Host判断域名、生成回调地址,不传的话Node收到的是127.0.0.1:3000,可能导致跳转错误。X-Real-IP和X-Forwarded-For:把真实客户端IP传给Node。不加的话Node日志里记录的IP全是127.0.0.1,排查问题时会抓瞎。Upgrade和Connection:WebSocket握手必需。
4.3 静态资源交给Nginx直接返回
Node处理静态文件效率不高,图片、CSS、JS这些应该让Nginx直接返回,减轻Node压力:
location /static/ { alias D:/apps/your-project/public/static/; expires 7d; add_header Cache-Control "public"; }注意Windows下路径用正斜杠/或者双反斜杠\\,单反斜杠会被当成转义字符。alias和root的区别要搞清楚:root会把location的路径拼接到后面,alias则是直接替换。上面这个配置,访问/static/logo.png,Nginx会去读D:/apps/your-project/public/static/logo.png。
4.4 配置HTTPS与证书
如果对外提供HTTPS,需要配置证书:
server { listen 443 ssl; server_name your-domain.com; ssl_certificate D:/nginx/conf/cert/your-cert.pem; ssl_certificate_key D:/nginx/conf/cert/your-key.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; location / { proxy_pass http://node_app; # ... 同上 } }证书路径同样用正斜杠。配置完执行nginx -t测试语法,通过后再nginx -s reload。每次改配置都要先nginx -t,直接reload一个语法错误的配置会导致Nginx停止响应,这个习惯能救命。
5. 从本地到上线的完整部署链路
5.1 代码上传与依赖安装
把代码传到服务器,常见方式有Git拉取、FTP、远程桌面复制。生产环境推荐用Git,方便版本管理和回滚:
cd D:\apps git clone your-repo-url your-project cd your-project npm install --production--production只装dependencies,跳过devDependencies,减少体积和潜在风险。如果项目用了构建步骤(比如TypeScript编译、前端打包),需要在本地或CI里构建好,把产物传上去,而不是在服务器上装一堆构建工具。
5.2 启动顺序与依赖关系
正确的启动顺序是:先起Node,确认3000端口能通,再起Nginx。因为Nginx启动时如果upstream不可达,虽然不会报错,但请求会返回502。反过来,如果Node先起来,Nginx后起,一切正常。
验证Node是否正常:
curl http://127.0.0.1:3000Windows 10/Server 2019以上自带curl。如果返回了预期内容,说明Node没问题。然后再启动Nginx,访问http://服务器IP,看是否能正常代理。
5.3 服务器重启后的自动恢复验证
部署完最重要的一步是重启服务器,验证所有服务自动恢复。这是区分"能跑"和"能上线"的关键。
重启后依次检查:
- Node进程是否在(
pm2 list或nssm status) - 3000端口是否监听(
netstat -ano | findstr 3000) - Nginx是否在(任务管理器看nginx.exe)
- 外网访问是否正常
如果Node没起来,检查pm2的pm2 save是否执行过、nssm服务的启动类型是否是"自动"。如果Nginx没起来,Windows版Nginx默认不会注册为服务,需要手动把它做成服务,或者用任务计划在开机时执行start nginx。
把Nginx做成服务,同样可以用nssm:
nssm install Nginx D:\nginx\nginx.exe nssm set Nginx AppDirectory D:\nginx这样Nginx也能开机自启,和Node服务一样受服务管理器管理。
6. 那些只有踩过才知道的坑
6.1 路径分隔符与中文路径
Windows路径用反斜杠,但Node和Nginx配置里混用正反斜杠很容易出问题。我的经验是:代码里统一用path.join处理路径,配置里统一用正斜杠。另外,项目路径绝对不要包含中文或空格。D:\我的项目\app这种路径,在某些工具链里会因为编码问题导致找不到文件,排查起来极其痛苦。
6.2 端口被占用与PID排查
启动Node报EADDRINUSE,说明3000端口被占了。查是哪个进程:
netstat -ano | findstr :3000最后一列是PID,然后:
tasklist | findstr PID找到进程名后决定是杀掉还是换端口。Windows上有个坑:TIME_WAIT状态的连接会占用端口一段时间,频繁重启服务时容易遇到。可以在代码里设置server.listen(3000, '127.0.0.1'),或者等一会儿再启动。
6.3 Nginx 502与504的排查思路
502 Bad Gateway通常是Nginx连不上Node,原因可能是Node没启动、端口不对、防火墙拦截。504 Gateway Timeout是Node处理太慢,超过了Nginx的proxy_read_timeout(默认60秒)。如果是导出大文件、跑长任务,需要调大:
proxy_read_timeout 300s; proxy_send_timeout 300s;排查502时,先curl 127.0.0.1:3000确认Node活着,再看Nginx的error.log(在logs/error.log),里面会写具体原因。
6.4 日志乱码与编码问题
Windows默认编码是GBK,Node和Nginx输出UTF-8日志时,用记事本打开会乱码。解决办法是用支持UTF-8的编辑器(VS Code、Notepad++)打开,或者在PowerShell里先执行chcp 65001切换到UTF-8代码页。这个问题不影响功能,但排查问题时看乱码日志会让人抓狂。
6.5 权限问题导致的诡异失败
用nssm注册服务时,默认以LocalSystem账户运行,权限很高但可能访问不了网络映射盘。如果项目要读写某个目录,确保运行服务的账户对该目录有读写权限。我遇到过一次服务启动后写日志失败,就是因为日志目录只给了Administrator权限,服务账户写不进去。用icacls命令授权:
icacls D:\logs /grant nodeuser:(OI)(CI)F(OI)(CI)F表示对文件夹及其子文件、子目录授予完全控制权限。
7. 性能调优与长期维护建议
7.1 Node进程数与CPU核心的匹配
单进程Node只能用一个CPU核心。如果服务器是多核,可以用pm2的cluster模式榨干多核性能:
pm2 start app.js -i max --name "my-node-app"-i max会按CPU核心数启动对应数量的实例,pm2自动做负载均衡。但要注意:cluster模式下多个进程不共享内存,如果项目里用了内存缓存、内存session,会出现数据不一致。这种情况要么改用Redis等外部存储,要么老老实实单进程。
7.2 Nginx缓存与压缩
开启gzip压缩能显著减少传输体积:
gzip on; gzip_types text/plain text/css application/json application/javascript; gzip_min_length 1024;gzip_min_length设成1024,小于1KB的文件不压缩,因为压缩本身也有开销,小文件压缩反而得不偿失。
7.3 定期检查与日志轮转
长期运行的服务,日志会不断增长。pm2用pm2-logrotate:
pm2 install pm2-logrotate pm2 set pm2-logrotate:max_size 10M pm2 set pm2-logrotate:retain 7Nginx的日志轮转在Windows上比较麻烦,因为不像Linux有logrotate。可以写一个PowerShell脚本,定期把日志改名归档,然后用任务计划定时执行。或者干脆在Nginx配置里把access_log关掉,只保留error_log,减少磁盘压力。
7.4 监控与告警的最小方案
没有监控的部署等于裸奔。最小方案:写一个定时任务,每隔几分钟curl一下健康检查接口,失败就发邮件或企业微信告警。Node项目里加一个/health接口返回200即可。这个方案土但有效,比出了问题等用户反馈强得多。
整套流程走下来,核心就三件事:进程守护保证Node不死、Nginx反向代理保证外部能访问、开机自启保证重启后自动恢复。把这三件事做扎实,Windows Server上跑Node项目完全可以做到和Linux一样稳定。我在实际运维中,用nssm + Nginx这套组合跑过连续几个月不重启的服务,只要日志和磁盘空间管理好,Windows并不是Node的禁区。