Nginx反向代理配置教程

3 分钟阅读·Matthieu|

配置Nginx作为Node.js、Ollama等后端服务的反向代理。涵盖proxy_pass、请求头转发、WebSocket支持、upstream负载均衡以及生产环境超时调优,每一步都有验证方法。

反向代理位于客户端和后端应用之间。Nginx接收传入请求,将其转发到后端服务器(Node.js、Python、Go、Ollama),然后将响应返回给客户端。这样你可以在不修改应用的情况下添加TLS终止、负载均衡、缓存和访问控制。

本教程从基础的proxy_pass开始,逐步讲解WebSocket代理、upstream负载均衡,以及生产级Ollama代理配置。

前提条件

开始之前,你需要:

验证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 oktest 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.confhttp块中:

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.conflocation块中添加:

    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排查清单

  1. 后端是否在运行?
ss -tlnp | grep 3000

如果没有输出,说明后端没有在3000端口监听。启动它。

  1. proxy_pass URL是否正确? 检查端口号或IP地址是否有拼写错误。常见错误:对只支持HTTP的后端使用了https://

  2. SELinux是否阻止了连接?(RHEL/CentOS)

sudo setsebool -P httpd_can_network_connect 1
  1. 检查Nginx错误日志:
sudo tail -20 /var/log/nginx/error.log

查找connect() failed (111: Connection refused)no live upstreams

504 Gateway Timeout排查清单

  1. 增大proxy_read_timeout:
proxy_read_timeout 300s;
  1. 检查后端是否响应慢:
time curl http://127.0.0.1:3000/slow-endpoint

如果耗时超过你的proxy_read_timeout,增大超时值或优化后端。

  1. 检查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_logerror_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.

准备好亲自尝试了吗?

几秒内部署您自己的服务器。支持 Linux、Windows 或 FreeBSD。

查看 VPS 方案