Nginx反向代理配置教程
配置Nginx作为Node.js、Ollama等后端服务的反向代理。涵盖proxy_pass、请求头转发、WebSocket支持、upstream负载均衡以及生产环境超时调优,每一步都有验证方法。
反向代理位于客户端和后端应用之间。Nginx接收传入请求,将其转发到后端服务器(Node.js、Python、Go、Ollama),然后将响应返回给客户端。这样你可以在不修改应用的情况下添加TLS终止、负载均衡、缓存和访问控制。
本教程从基础的proxy_pass开始,逐步讲解WebSocket代理、upstream负载均衡,以及生产级Ollama代理配置。
前提条件
开始之前,你需要:
- 一台运行Debian 12或Ubuntu 24.04的VPS,拥有root或sudo权限
- 已安装并运行Nginx(在Debian和Ubuntu上安装Nginx)
- 一个监听本地端口的后端应用(教程中包含测试示例)
- 了解Nginx配置文件的基本结构(Nginx配置文件结构)
验证Nginx正在运行:
sudo systemctl status nginx
输出中应显示active (running)。
如何在Nginx中配置proxy_pass?
proxy_pass指令告诉Nginx将请求转发到哪里。将它放在server块内的location块中。Nginx将客户端请求发送到指定的后端URL,并将响应流式传回。该指令接受HTTP和HTTPS URL,可以指向IP地址、域名或Unix socket。
创建新的server block文件:
sudo nano /etc/nginx/sites-available/app.conf
添加最小化的反向代理配置:
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
启用站点并测试配置:
sudo ln -s /etc/nginx/sites-available/app.conf /etc/nginx/sites-enabled/
sudo nginx -t
如果nginx -t返回syntax is ok和test is successful,重新加载:
sudo systemctl reload nginx
验证代理是否工作:
curl -I http://app.example.com
响应头应来自你的后端应用。如果看到502 Bad Gateway,说明后端没有在3000端口上运行。
proxy_pass末尾的斜杠有什么影响?
proxy_pass中的末尾斜杠控制URI重写。这是最常见的配置错误来源之一。
| 配置 | 请求 | 转发到后端 |
|---|---|---|
proxy_pass http://127.0.0.1:3000 |
/app/users |
http://127.0.0.1:3000/app/users |
proxy_pass http://127.0.0.1:3000/ |
/app/users |
http://127.0.0.1:3000/users |
proxy_pass http://127.0.0.1:3000/v2/ |
/app/users |
http://127.0.0.1:3000/v2/users |
proxy_pass http://127.0.0.1:3000/v2 |
/app/users |
http://127.0.0.1:3000/v2users |
规则:当proxy_pass包含URI(host:port后面的任何内容,即使只是/),Nginx会从请求URI中去掉匹配的location前缀,并将剩余部分追加到proxy_pass的URI后。当proxy_pass没有URI时,原始请求路径不变。
对于location /app/块,上面的示例适用。注意第四行:/v2没有末尾斜杠时,路径变成了/v2users而不是/v2/users。指定路径时务必加上末尾斜杠。
应该转发哪些请求头到后端?
如果不显式配置请求头,后端应用无法看到客户端的真实IP地址、原始协议或请求的主机名。Nginx默认会替换这些信息。你需要手动转发。
| 请求头 | 值 | 用途 |
|---|---|---|
Host |
$host |
保留原始Host头,让后端知道请求的是哪个域名 |
X-Real-IP |
$remote_addr |
将客户端IP地址传递给后端 |
X-Forwarded-For |
$proxy_add_x_forwarded_for |
将客户端IP追加到代理链中 |
X-Forwarded-Proto |
$scheme |
告诉后端原始请求使用的是HTTP还是HTTPS |
将这些请求头添加到location块中:
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;
}
重新加载Nginx后,验证请求头是否传递到后端。如果你的应用会记录传入的请求头,检查它们:
sudo systemctl reload nginx
curl -s http://app.example.com/headers
后端应看到X-Real-IP对应客户端的真实IP,而不是127.0.0.1。如果显示127.0.0.1,说明proxy_set_header指令缺失或放在了错误的上下文中。
安全提示: 对于X-Forwarded-For,使用$proxy_add_x_forwarded_for而非$remote_addr。前者会将客户端IP追加到已有的X-Forwarded-For头中。如果只设置$remote_addr,你会丢失代理链信息,这在多层代理环境中会影响IP追踪。如果Nginx是唯一的代理,两者效果相同。
如何反向代理Node.js应用?
下面是一个完整的server block,用于代理运行在3000端口的Node.js应用。这个示例包含请求头转发、WebSocket支持和版本隐藏。
创建server block:
sudo nano /etc/nginx/sites-available/nodeapp.conf
server {
listen 80;
server_name nodeapp.example.com;
# Hide Nginx version in responses
server_tokens off;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
# Header forwarding
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 support
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Timeouts
proxy_connect_timeout 60s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
}
}
启用、测试并重新加载:
sudo ln -s /etc/nginx/sites-available/nodeapp.conf /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
验证代理是否工作:
curl -I http://nodeapp.example.com
注意看:响应中不应包含Server: nginx/1.x.x这一行。server_tokens off指令隐藏了版本号。版本信息泄露会帮助攻击者针对已知漏洞发起攻击。
关于使用Let's Encrypt进行TLS终止,参见Nginx SSL/TLS与Let's Encrypt。
如何用Nginx代理WebSocket连接?
WebSocket连接以带有Upgrade头的HTTP请求开始,然后切换为持久的双向连接。Nginx默认对上游连接使用HTTP/1.0,不支持Upgrade机制。你需要三个指令来启用WebSocket:将proxy_http_version设为1.1,转发Upgrade头,并将Connection头设为"upgrade"。
对于同时提供普通HTTP和WebSocket流量的location,使用map指令有条件地设置Connection头。将以下内容添加到/etc/nginx/nginx.conf的http块中:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
然后在server block中引用$connection_upgrade:
server {
listen 80;
server_name ws.example.com;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# Prevent idle timeout killing WebSocket connections
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}
默认的proxy_read_timeout是60秒。如果连接在此期间没有数据传输,Nginx会关闭它。WebSocket连接需要设置更高的值。你的应用应以短于此超时时间的间隔发送WebSocket ping帧。
测试配置:
sudo nginx -t && sudo systemctl reload nginx
要验证WebSocket连接,安装wscat并连接:
npm install -g wscat
wscat -c ws://ws.example.com/
如果连接成功打开,说明WebSocket代理工作正常。如果收到unexpected server response (200),说明Upgrade头没有被转发。检查map指令是否在http块内,而不是在server块内。
如何为自托管AI反向代理Ollama?
Ollama默认在11434端口提供LLM推理服务,绑定到127.0.0.1。通过Nginx代理可以添加TLS、认证和访问控制,无需修改Ollama的配置。与标准代理的关键区别:LLM推理可能需要数分钟,流式响应需要禁用缓冲。
创建server block:
sudo nano /etc/nginx/sites-available/ollama.conf
server {
listen 80;
server_name ollama.example.com;
server_tokens off;
# Restrict access to specific IPs
allow 192.168.1.0/24;
allow 10.0.0.0/8;
deny all;
location / {
proxy_pass http://127.0.0.1:11434;
proxy_http_version 1.1;
# Header forwarding
proxy_set_header Host localhost:11434;
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 Connection '';
# Disable buffering for streaming responses
proxy_buffering off;
proxy_cache off;
chunked_transfer_encoding on;
# Extended timeouts for LLM inference
proxy_connect_timeout 300s;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
}
}
启用并测试:
sudo ln -s /etc/nginx/sites-available/ollama.conf /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
验证Ollama通过代理响应:
curl http://ollama.example.com/api/tags
你应看到一个列出可用模型的JSON响应。如果收到403 Forbidden,说明你的客户端IP不在allow列表中。如果收到502 Bad Gateway,说明Ollama没有运行:
sudo systemctl status ollama
测试流式生成:
curl -N http://ollama.example.com/api/generate -d '{
"model": "llama3.2",
"prompt": "Hello",
"stream": true
}'
注意看:-N标志禁用了curl的输出缓冲。你应看到token逐个到达。如果整个响应一次性到达,说明proxy_buffering off没有设置或被覆盖了。
为什么这些设置与标准代理不同:
proxy_buffering off:Nginx通常会缓冲后端响应并批量发送。对于LLM流式输出,你需要每个token立即发送给客户端。proxy_read_timeout 600s:大模型的LLM推理可能需要几分钟。默认的60秒超时会在生成过程中断开连接。proxy_set_header Host localhost:11434:Ollama会检查Host头,拒绝与其配置的绑定地址不匹配的请求。proxy_set_header Connection '':清除Connection头,防止分块流式传输中的keep-alive问题。
关于通过TLS安全地将Ollama暴露到互联网,参见Nginx SSL/TLS与Let's Encrypt。永远不要在没有访问控制的情况下暴露Ollama。生产环境中应将IP限制与HTTP Basic Auth或API key验证结合使用。
在IP限制之上添加HTTP Basic Auth:
sudo apt install apache2-utils
sudo htpasswd -c /etc/nginx/.ollama_htpasswd apiuser
sudo chmod 640 /etc/nginx/.ollama_htpasswd
sudo chown root:www-data /etc/nginx/.ollama_htpasswd
然后在ollama.conf的location块中添加:
auth_basic "Ollama API";
auth_basic_user_file /etc/nginx/.ollama_htpasswd;
验证文件权限是否正确:
ls -la /etc/nginx/.ollama_htpasswd
文件应显示-rw-r-----,所有者为root,组为www-data。限制权限可以防止服务器上的其他用户读取密码哈希。
如何配置upstream负载均衡?
upstream块定义了一组后端服务器,Nginx将请求分发到这些服务器上。默认使用加权轮询(weighted round-robin)。每个服务器按其权重比例获取请求(默认权重为1)。
在server块之前添加upstream块:
upstream app_backends {
server 127.0.0.1:3000;
server 127.0.0.1:3001;
server 127.0.0.1:3002;
}
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://app_backends;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
负载均衡方法
Round-robin(轮询)(默认):请求均匀分发到各服务器。无需额外指令。
Least connections(最少连接):将请求发送到活跃连接数最少的服务器。适合响应时间不均匀的后端:
upstream app_backends {
least_conn;
server 127.0.0.1:3000;
server 127.0.0.1:3001;
}
IP hash:将每个客户端IP绑定到特定的后端。适用于不使用sticky cookie的会话保持:
upstream app_backends {
ip_hash;
server 127.0.0.1:3000;
server 127.0.0.1:3001;
}
使用max_fails和fail_timeout进行健康检查
Nginx被动监控后端健康状态。如果服务器无法响应,Nginx会将其标记为不可用并在一段时间内停止向其发送请求。
upstream app_backends {
server 127.0.0.1:3000 max_fails=3 fail_timeout=30s;
server 127.0.0.1:3001 max_fails=3 fail_timeout=30s;
server 127.0.0.1:3002 backup;
}
| 参数 | 默认值 | 说明 |
|---|---|---|
max_fails |
1 | 在fail_timeout时间窗口内失败多少次后标记为不可用 |
fail_timeout |
10s | 统计失败的时间窗口,也是服务器被标记为不可用的持续时间 |
backup |
- | 仅当所有主服务器宕机时才接收请求 |
weight |
1 | 轮询中的请求分配比例 |
配置upstream后,测试并重新加载:
sudo nginx -t && sudo systemctl reload nginx
通过发送多个请求来验证负载均衡是否工作,检查哪个后端响应了:
for i in $(seq 1 6); do curl -s http://app.example.com/health; echo; done
如果你的后端在响应中返回标识符,你应看到请求被分发到不同的后端。
如何调优代理缓冲和超时?
Nginx默认缓冲后端响应。它将完整响应从后端读入内存(如果超出缓冲区大小则写入磁盘),然后发送给客户端。这对大多数应用高效,但不适用于流式传输、Server-Sent Events(SSE)或长轮询。
超时指令
| 指令 | 默认值 | 建议值 | 用途 |
|---|---|---|---|
proxy_connect_timeout |
60s | 5-10s | 与后端建立连接的时间。保持短值以快速失败。 |
proxy_read_timeout |
60s | 60-300s | 等待后端响应的时间。慢接口需要增大。 |
proxy_send_timeout |
60s | 60s | 向后端发送请求体的时间。大文件上传需要增大。 |
location / {
proxy_pass http://127.0.0.1:3000;
proxy_connect_timeout 10s;
proxy_read_timeout 120s;
proxy_send_timeout 60s;
}
这些超时指的是两次连续读写操作之间的间隔,而不是整个请求的总时间。一个每30秒发送一次数据的响应不会触发60秒的proxy_read_timeout。
缓冲控制
| 指令 | 默认值 | 说明 |
|---|---|---|
proxy_buffering |
on | 在发送给客户端之前缓冲完整的后端响应 |
proxy_buffer_size |
4k或8k | 响应第一部分(头部)的缓冲区 |
proxy_buffers |
8 4k或8 8k | 响应体的缓冲区数量和大小 |
proxy_busy_buffers_size |
8k或16k | 可同时向客户端发送数据的最大缓冲区大小 |
什么时候应该禁用代理缓冲?
当后端进行流式数据传输时禁用缓冲:LLM推理(Ollama、vLLM)、Server-Sent Events、类WebSocket的长响应,或任何增量发送分块数据的API。
location /stream/ {
proxy_pass http://127.0.0.1:8080;
proxy_buffering off;
}
标准请求/响应API应保持缓冲开启。缓冲可以保护后端免受慢客户端影响:Nginx快速吸收响应,然后按客户端的速度发送。没有缓冲时,慢客户端会持续占用后端连接。
关于进阶性能调优,参见。
如何排查502和504错误?
502 Bad Gateway表示Nginx无法连接到后端或后端返回了无效响应。504 Gateway Timeout表示后端在proxy_read_timeout时间内未响应。
502 Bad Gateway排查清单
- 后端是否在运行?
ss -tlnp | grep 3000
如果没有输出,说明后端没有在3000端口监听。启动它。
-
proxy_pass URL是否正确? 检查端口号或IP地址是否有拼写错误。常见错误:对只支持HTTP的后端使用了
https://。 -
SELinux是否阻止了连接?(RHEL/CentOS)
sudo setsebool -P httpd_can_network_connect 1
- 检查Nginx错误日志:
sudo tail -20 /var/log/nginx/error.log
查找connect() failed (111: Connection refused)或no live upstreams。
504 Gateway Timeout排查清单
- 增大proxy_read_timeout:
proxy_read_timeout 300s;
- 检查后端是否响应慢:
time curl http://127.0.0.1:3000/slow-endpoint
如果耗时超过你的proxy_read_timeout,增大超时值或优化后端。
- 检查upstream健康状态: 如果使用了upstream块,所有服务器可能都被标记为失败。检查错误日志中是否有
no live upstreams while connecting to upstream。
读取Nginx日志
Nginx将错误详情写入/var/log/nginx/error.log。实时监控:
sudo journalctl -u nginx -f
或直接监视错误日志:
sudo tail -f /var/log/nginx/error.log
要按站点分离日志,在server block中添加access_log和error_log指令:
server {
listen 80;
server_name app.example.com;
access_log /var/log/nginx/app-access.log;
error_log /var/log/nginx/app-error.log;
location / {
proxy_pass http://127.0.0.1:3000;
}
}
这样按应用分离日志,更容易调试特定后端的问题。
常见请求头问题
如果后端收到的客户端IP是127.0.0.1而非真实地址,说明缺少proxy_set_header X-Real-IP $remote_addr指令。如果你的应用生成的链接使用http://而应该用https://,说明X-Forwarded-Proto头没有被转发。一些框架(Express、Django、Rails)需要显式配置才能信任代理头。在Express中:
app.set('trust proxy', 1);
在Django中,设置SECURE_PROXY_SSL_HEADER:
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
没有这些设置,即使Nginx正确发送了转发头,你的应用也会忽略它们。
完整参考:代理指令
以下是不同代理场景所需的最小指令集,供快速参考:
# Standard HTTP proxy
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;
}
# WebSocket proxy
location /ws/ {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 3600s;
}
# Streaming proxy (SSE, LLM)
location /stream/ {
proxy_pass http://127.0.0.1:11434;
proxy_http_version 1.1;
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 600s;
}
关于管理多个域名的server block,参见Nginx server blocks。关于安全加固(包括速率限制和访问控制),参见。
Copyright 2026 Virtua.Cloud. All rights reserved.