Node.js应用通常运行在3000、8080这样的高位端口上,而面向用户的请求往往需要经过80或443端口。直接让Node.js监听80端口会带来权限问题,也容易让应用暴露在复杂的网络环境中。使用Nginx作为反向代理,可以优雅地解决这些问题。Nginx接收外部请求,根据配置将请求分发到Node.js进程,并隐藏后端的内部地址和端口。这种方式不仅能提升安全性,还能利用Nginx处理静态资源、负载均衡和SSL终止,让Node.js专注于业务逻辑。

一、为什么需要Nginx反向代理Node.js?
直接让Node.js对外提供服务并不是一个理想的生产方案。虽然Node.js基于事件循环和异步I/O,在高并发场景下表现不错,但它在处理静态文件、连接保持和防护恶意请求时,还是不如Nginx这样专精的Web服务器。静态文件读取会占用Node.js的线程资源,如果大量请求都是图片、CSS或JavaScript,Node.js不得不频繁进行文件系统操作,影响业务接口的响应速度。Nginx处理静态文件时使用sendfile和高效的内存缓存,性能远优于Node.js的流式读取。
端口权限也是紧约束。在Linux系统上,监听1024以下的端口需要root权限。如果让Node.js直接监听80端口,应用进程就必须以root身份运行。一旦应用存在安全漏洞,攻击者可能获得服务器最高权限。通过Nginx监听80端口,Node.js仍然运行在3000这类高位端口上,使用普通用户身份即可。Nginx将请求转发给后端时,两者的通信完全在内网进行,外部无法直接访问Node.js端口。
扩展性方面,Nginx的反向代理让横向扩容变得非常方便。当业务量增长时,可以启动多个Node.js实例,然后通过Nginx的upstream模块将请求分散到不同的实例上。这样做既能提升吞吐量,又能在某个实例宕机时自动摘除,保证服务可用性。如果不经过Nginx,多实例间的流量分配就需要自己实现,维护成本会高很多。
二、Nginx反向代理的基础配置
假设你有一个Node.js应用,监听在127.0.0.1:3000上。默认的Nginx配置位于/etc/nginx/nginx.conf,但更常见的做法是在/etc/nginx/conf.d/目录下为每个网站创建独立的配置文件。以下是一个最基础的反向代理配置,将所有请求转发给Node.js的3000端口。
server {
listen 80;
server_name ipipp.com;
location / {
proxy_pass http://127.0.0.1:3000;
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;
}
}
这段配置的核心是location /块中的proxy_pass指令。proxy_pass告诉Nginx将匹配到的请求转发到指定的后端地址。这里使用的是http://127.0.0.1:3000,表示将请求原样转发给本机的3000端口。值得注意的是,proxy_pass后可以带路径也可以不带。不带路径时,原始URI会被完整传递给后端;带路径时,Nginx会根据location前缀和proxy_pass路径的拼接规则重组URI,使用时需要格外小心。
后面的四个proxy_set_header指令同样重要。Node.js应用如果直接读取请求的IP,拿到的是Nginx的服务器地址而不是客户端的真实IP。通过X-Real-IP和X-Forwarded-For,后端应用就能获得客户端的真实IP。而X-Forwarded-Proto则用来告诉后端用户请求使用的是HTTP还是HTTPS,便于应用生成正确的回调地址。如果你需要在Node.js中获取客户端协议和IP,一定要保留这些头信息。
配置完成后,需要重新加载Nginx使修改生效:sudo nginx -t先检查语法,然后执行sudo nginx -s reload。此时访问域名就会发现,Nginx已经将请求成功转发到了Node.js应用。
三、配置负载均衡与多实例部署
单个Node.js实例能够处理的并发连接始终有限,当需要提高系统吞吐量时,最直接的办法是部署多个Node.js实例。Nginx的upstream模块正为此设计。下面是一个使用两台Node.js进程的负载均衡配置。
upstream node_backend {
server 127.0.0.1:3000 weight=1;
server 127.0.0.1:3001 weight=1;
keepalive 32;
}
server {
listen 80;
server_name ipipp.com;
location / {
proxy_pass http://node_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}
在upstream块中,定义了名为node_backend的后端组,包含两个Node.js实例。默认情况下,Nginx采用轮询算法将请求依次分发到每个实例上,这种方式简单且平均。如果服务器性能不一致,可以通过weight参数调整权重,例如给高性能服务器设置weight=2,让它接收两倍的流量。另一种常用的算法是ip_hash,它会根据客户端IP的哈希值分配后端,保证来自同一IP的请求始终落在同一个实例上,适合需要保存本地会话状态的应用。
这里还设置了keepalive 32,表示Nginx会为每个Worker进程保留最多32个与后端的长连接。启用长连接能减少频繁建立TCP连接带来的开销。但要注意,keepalive需要配合proxy_http_version 1.1使用,并且将默认的Connection头清空,否则后端可能无法识别HTTP/1.1长连接协议。
那么如何启动多个Node.js实例呢?最简单的方式是使用Node.js内置的cluster模块,它可以在同一端口上创建多个Worker进程共享CPU资源。也可以借助PM2进程管理器,使用pm2 start app.js -i 4启动4个实例,然后在Nginx的upstream中分别指向不同的端口。实际操作中,通常会为每个实例指定独立的端口,或者让PM2通过fork模式以不同端口启动多个进程,再由Nginx统一代理。
四、WebSocket、超时与常见优化
如果你的Node.js应用使用了WebSocket(例如聊天服务或实时通知),反向代理配置需要额外处理协议升级。WebSocket连接建立时,客户端会发送带Upgrade: websocket的HTTP请求,Nginx必须将这个头和Connection头一起转发给后端,否则握手会失败。示例配置如下:
location /ws {
proxy_pass http://node_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
这段配置中,proxy_set_header Upgrade $http_upgrade将客户端的Upgrade头原样透传给后端,proxy_set_header Connection "upgrade"则告诉后端需要建立持久连接。如果应用同时存在普通HTTP请求和WebSocket连接,建议为WebSocket单独设置一个location路径,避免影响其他请求的代理行为。
超时设置是另一个容易出问题的地方。Nginx默认的proxy_read_timeout是60秒,如果Node.js处理某个请求超过60秒还没有返回数据,Nginx会中断连接。对于需要长时间处理的API接口,或者WebSocket的长连接,显式调大超时时间非常必要。上面的配置将读写超时都设置成了3600秒。此外,proxy_connect_timeout也需要合理设置,过短会导致后端短暂繁忙时连接失败,过长则会让Nginx的Worker进程长时间等待。
生产环境还可以考虑静态资源分离。如果Node.js应用提供静态文件,可以让Nginx直接处理静态请求,而只把动态请求转发给Node.js。例如在配置中加入以下规则,当用户请求/static/下的文件时,Nginx直接从本地目录读取,根本不经过Node.js。
location /static/ {
alias /var/www/html/static/;
expires 30d;
}
这样既能减轻Node.js的负载,又能通过Nginx的expires指令为静态资源设置浏览器缓存,提升用户访问速度。反向代理的配置并不复杂,关键在于理解每个指令的作用以及各个场景下的最佳实践。通过合理的配置,Nginx完全可以成为Node.js应用的强力盾牌,为系统的稳定运行提供基础保障。