跳到主要内容

反向代理

反向代理是 Nginx 最重要的功能之一。本章将详细介绍如何配置 Nginx 作为反向代理服务器。

什么是反向代理?

正向代理 vs 反向代理

正向代理(Forward Proxy):
客户端 → 代理服务器 → 目标服务器

例如:VPN、翻墙工具
- 客户端知道目标服务器
- 目标服务器不知道真实客户端
- 代理代表客户端

反向代理(Reverse Proxy):
客户端 → 反向代理服务器 → 后端服务器

例如:Nginx、CDN
- 客户端不知道后端服务器
- 后端服务器知道真实客户端(通过头部传递)
- 代理代表服务器

解释

  • 正向代理:代理客户端,帮助客户端访问无法直接访问的资源
  • 反向代理:代理服务器,隐藏后端服务器,提供负载均衡、缓存等功能

反向代理的优势

  1. 安全:隐藏后端服务器的真实地址
  2. 负载均衡:分发请求到多个后端服务器
  3. 缓存:缓存静态内容,减少后端压力
  4. SSL 终端:集中处理 HTTPS,简化后端配置
  5. 压缩:减少传输数据量
  6. 静态资源分离:处理静态文件,减轻应用服务器负担

基本配置

proxy_pass 指令

proxy_pass 是反向代理的核心指令:

server {
listen 80;
server_name example.com;

location / {
proxy_pass http://127.0.0.1:8080;
}
}

解释

  • 所有访问 example.com 的请求都会被转发到 http://127.0.0.1:8080
  • 客户端看到的是 example.com,不知道实际请求的是 :8080 端口

代理到不同路径

location /api/ {
# 请求 /api/users 会转发到 http://backend/api/users
proxy_pass http://backend;
}

location /app/ {
# 请求 /app/users 会转发到 http://backend/users(去掉 /app 前缀)
proxy_pass http://backend/;
}

解释

  • 如果 proxy_pass 有 URI(如 /),会替换匹配的路径
  • 如果没有 URI,会保留原始路径

常用代理头设置

传递客户端信息

location / {
proxy_pass http://backend;

# 传递客户端真实 IP
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;
}

解释

  • Host:原始请求的主机名
  • X-Real-IP:客户端真实 IP
  • X-Forwarded-For:代理链,记录客户端和各级代理的 IP
  • X-Forwarded-Proto:原始请求的协议(http/https)

完整的代理头配置

location / {
proxy_pass http://backend;

# 基本代理头
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 X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;

# 连接信息
proxy_set_header Connection "";
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

# Nginx 版本
proxy_hide_header X-Powered-By;
proxy_hide_header Server;
}

超时配置

连接和响应超时

location / {
proxy_pass http://backend;

# 连接后端的超时时间
proxy_connect_timeout 60s;

# 读取响应的超时时间
proxy_read_timeout 60s;

# 发送请求的超时时间
proxy_send_timeout 60s;
}

解释

  • proxy_connect_timeout:建立连接的超时,通常设置较短
  • proxy_read_timeout:等待后端响应的超时,根据业务调整
  • proxy_send_timeout:向后端发送请求的超时

长连接配置

upstream backend {
server 127.0.0.1:8080;

# 保持长连接
keepalive 32;
}

location / {
proxy_pass http://backend;

# 启用 HTTP/1.1 长连接
proxy_http_version 1.1;
proxy_set_header Connection "";
}

解释

  • keepalive:保持到后端的长连接数量
  • proxy_http_version 1.1:使用 HTTP/1.1 支持长连接
  • Connection "":清除 Connection 头,允许长连接

缓冲配置

代理缓冲

location / {
proxy_pass http://backend;

# 开启代理缓冲
proxy_buffering on;

# 缓冲区数量和大小
proxy_buffers 8 16k;

# 单个缓冲区大小(用于响应头)
proxy_buffer_size 4k;

# 忙碌时的缓冲区大小
proxy_busy_buffers_size 32k;

# 临时文件大小限制
proxy_max_temp_file_size 256m;
}

解释

  • proxy_buffering on:开启后,Nginx 会缓冲后端响应
  • proxy_buffers:第一个参数是数量,第二个是每个大小
  • 缓冲可以提高性能,但增加内存使用

实际场景配置

场景 1:代理 Node.js 应用

server {
listen 80;
server_name example.com;

# 静态文件直接由 Nginx 处理
location /static/ {
alias /var/www/static/;
expires 30d;
}

# 动态请求代理到 Node.js
location / {
proxy_pass http://127.0.0.1:3000;

proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header 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;
proxy_set_header X-Forwarded-Proto $scheme;

proxy_connect_timeout 60s;
proxy_read_timeout 60s;
}
}

场景 2:代理 Python Flask/Django 应用

server {
listen 80;
server_name example.com;

# 静态文件
location /static/ {
alias /var/www/static/;
expires 30d;
}

# 上传文件大小限制
client_max_body_size 10M;

# 代理到 Gunicorn/uWSGI
location / {
proxy_pass http://unix:/tmp/gunicorn.sock;

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;
}
}

场景 3:代理 Spring Boot 应用

server {
listen 80;
server_name example.com;

# 健康检查
location /health {
proxy_pass http://127.0.0.1:8080;
access_log off;
}

# API 代理
location /api/ {
proxy_pass http://127.0.0.1:8080;

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;

# Spring Boot 特定
proxy_set_header X-Request-ID $request_id;
}
}

场景 4:代理 WebSocket

map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}

server {
listen 80;
server_name example.com;

location /ws/ {
proxy_pass http://127.0.0.1:8080;

# WebSocket 支持
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;

# WebSocket 超时设置
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}

解释

  • WebSocket 需要 UpgradeConnection
  • 使用 map 指令处理不同的连接类型
  • WebSocket 连接可能保持很长时间,需要设置较长的超时

场景 5:代理 GraphQL

server {
listen 80;
server_name example.com;

location /graphql {
proxy_pass http://127.0.0.1:4000;

# GraphQL 请求可能很大
client_max_body_size 10M;

proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;

# 禁用缓存(如果需要)
proxy_buffering off;
}
}

高级配置

请求重写

location /old-api/ {
# 重写 URL
rewrite ^/old-api/(.*)$ /api/v1/$1 break;
proxy_pass http://backend;
}

location /api/v1/ {
# 移除前缀
rewrite ^/api/v1/(.*)$ /$1 break;
proxy_pass http://backend;
}

条件代理

# 根据 User-Agent 代理到不同后端
location / {
if ($http_user_agent ~* "mobile") {
proxy_pass http://mobile-backend;
}
proxy_pass http://desktop-backend;
}

# 根据请求头代理
location / {
set $backend "default";
if ($http_x_backend = "new") {
set $backend "new-backend";
}
proxy_pass http://$backend;
}

限制代理

# 限制上传大小
location /api/upload {
client_max_body_size 50M;
proxy_pass http://backend;
}

# 限制请求方法
location /api/ {
limit_except GET POST {
deny all;
}
proxy_pass http://backend;
}

故障处理

超时和错误处理

location / {
proxy_pass http://backend;

# 连接失败时返回 502/503/504
proxy_next_upstream error timeout http_502 http_503 http_504;

# 超时时间
proxy_connect_timeout 5s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
}

自定义错误页面

# 自定义 502 错误页面
error_page 502 /502.html;
location = /502.html {
root /usr/share/nginx/errors;
internal;
}

# 自定义 504 错误页面
error_page 504 /504.html;
location = /504.html {
root /usr/share/nginx/errors;
internal;
}

调试和日志

代理日志

log_format proxy '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'upstream: $upstream_addr '
'response_time: $upstream_response_time '
'status: $upstream_status';

server {
access_log /var/log/nginx/proxy.log proxy;
}

调试信息

location / {
proxy_pass http://backend;

# 添加调试头
add_header X-Backend $upstream_addr;
add_header X-Response-Time $upstream_response_time;
add_header X-Request-ID $request_id;
}

安全建议

1. 隐藏后端信息

# 移除敏感头
proxy_hide_header X-Powered-By;
proxy_hide_header Server;

# 设置自定义 Server 头
more_set_headers 'Server: Web Server';

2. 限制请求方法

location /api/ {
# 只允许 GET 和 POST
if ($request_method !~ ^(GET|POST)$) {
return 405;
}
proxy_pass http://backend;
}

3. 验证请求来源

location /internal/ {
# 只允许内网访问
allow 10.0.0.0/8;
allow 172.16.0.0/12;
allow 192.168.0.0/16;
deny all;

proxy_pass http://internal-backend;
}

小结

本章我们学习了:

  1. 反向代理的概念和优势
  2. proxy_pass 基本配置
  3. 代理头设置(Host、X-Real-IP、X-Forwarded-For)
  4. 超时和缓冲配置
  5. 各种实际场景的配置示例
  6. WebSocket 代理
  7. 高级配置技巧
  8. 安全建议

练习

  1. 配置 Nginx 代理一个本地 Node.js 应用
  2. 配置 WebSocket 代理
  3. 设置自定义错误页面
  4. 添加调试头信息

延伸阅读