服务器怎么配置反向代理

结论: 服务器配置反向代理最简单可靠的方式是用 Nginx:在 server 块中用 proxy_pass 把请求转发到后端服务地址,并补上 Host、X-Real-IP、X-Forwarded-For、X-Forwarded-Proto 四个转发头,业务端才能拿到真实客户端 IP 与协议。Nginx 1.26 上完成基础配置只需 15 行,WebSocket 需额外加 Upgrade 头,多后端则用 upstream 做负载均衡,配置后用 nginx -t 校验并 systemctl reload nginx 生效(截至 2026 年,Nginx 稳定主线为 1.26.x/1.28.x)。

为什么要在服务器上配置反向代理

很多人的服务器一开始是直接把 Tomcat、Node.js、Python 应用监听在 80 或 443 端口对外提供服务。这种直连方式很快会遇到问题:一台机器要跑三个服务,端口怎么分?SSL 证书要在每个应用里配一遍吗?后端进程崩了谁来兜底返回友好页?

反向代理(Reverse Proxy)就是解决这类问题的标准答案。它站在客户端和后端服务之间,对外统一暴露 80/443 端口,对内按域名或路径把请求转发给不同的后端进程。客户端只认识这台 Nginx,完全不知道后端跑的是什么、在哪台机器、几个实例。

具体能拿到四个直接收益:一是端口收敛,对外只有一个入口,防火墙只需放行 80 和 443;二是 SSL 终结(TLS Termination),证书只在 Nginx 上配一次,后端走明文 HTTP,省掉各语言重复配置证书的麻烦;三是横向扩展,upstream 里加几行就能把流量分给多个后端实例;四是缓冲与容错,Nginx 会先把后端响应缓冲完再慢慢发给慢速客户端,避免后端进程被慢连接拖死。

和它容易混淆的是正向代理(Forward Proxy):正向代理代理的是客户端,服务端不知道真实客户是谁;反向代理代理的是服务端,客户端不知道真实服务是谁。 CDN、API 网关、Ingress Controller 本质上都是反向代理的特化形态。

配置前的准备工作

动手前先确认四件事,绝大多数"配完不生效"的问题都出在这四步没做扎实。

  • Nginx 已安装且版本明确:执行 nginx -v,截至 2026 年主流发行版仓库提供 1.24~1.28,Ubuntu 24.04 LTS 默认源为 1.24,官方源可装到 1.26 以上。
  • 后端服务已在本地监听:反向代理只转发,不负责拉起后端。先用 ss -lntp | grep 8080 确认后端在 127.0.0.1:8080 正常监听。
  • 后端建议只监听回环地址:配好代理后,后端应改监听 127.0.0.1,避免用户绕过 Nginx 直接访问源端口。
  • 防火墙与安全组放行 80/443:云服务器还要在控制台安全组里放行,只改系统防火墙不够。

安装 Nginx 的命令如下:

bash
# Ubuntu / Debian 24.04 / 12
sudo apt update && sudo apt install -y nginx

# RHEL / Rocky / AlmaLinux 9
sudo dnf install -y nginx

# 启动并设为开机自启
sudo systemctl enable --now nginx

# 确认版本与运行状态
nginx -v
systemctl status nginx --no-pager

Ubuntu 系的主配置在 /etc/nginx/nginx.conf,站点配置放在 /etc/nginx/conf.d/*.conf 或 /etc/nginx/sites-available/(软链到 sites-enabled/);RHEL 系主配置在 /etc/nginx/nginx.conf,站点通常直接放 /etc/nginx/conf.d/。本文示例以 /etc/nginx/conf.d/ 为例。

最小可用的反向代理配置

结论先行:一个能跑的基础反向代理配置只需要一个 server 块加一个 location 块里的 proxy_pass。先把最小版本跑通,再逐步加转发头、超时和负载,是排查成本最低的路径。

假设后端是一个跑在 127.0.0.1:8080 的 Java 应用,对外域名 app.example.com:

nginx
# /etc/nginx/conf.d/app.conf
server {
    listen 80;
    server_name app.example.com;

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

写完校验并重载:

bash
sudo nginx -t                      # 语法校验,出现 syntax is ok 才继续
sudo systemctl reload nginx        # 热重载,不断连接
curl -I http://app.example.com     # 验证返回头

这里有一个新手必踩的坑:proxy_pass 后面带不带路径,行为完全不同。

写法请求 /api/user转发给后端的路径说明
proxy_pass http://127.0.0.1:8080;/api/user/api/user不带 URI,路径原样透传
proxy_pass http://127.0.0.1:8080/;/api/user/api/user尾部只有斜杠,等价于替换 location 前缀后拼接
proxy_pass http://127.0.0.1:8080/v1/;/api/user/v1/user带 URI 时,location 匹配部分被替换为 /v1/

location /api/ { proxy_pass http://127.0.0.1:8080/; } 这种写法会把 /api/user 变成 /user 再转发——想保留 /api 前缀就别在 proxy_pass 末尾加斜杠,或者用 rewrite。这是"配完返回 404"最常见的原因。

必须补的四个转发头

结论先行:不加转发头,后端应用看到的来源 IP 全是 127.0.0.1,HTTPS 页面里生成的链接会变成 http://,这两个问题只有靠 proxy_set_header 解决。

Nginx 默认只转发两个头(Host 和 Connection),其余需显式声明。生产环境的标准做法是先在 http 块里定义一个可复用的配置文件:

nginx
# /etc/nginx/conf.d/proxy_headers.conf  放在 http 块内被 include
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 X-Forwarded-Host  $host;
proxy_set_header Connection        "";

然后在站点里引用:

nginx
server {
    listen 80;
    server_name app.example.com;
    include /etc/nginx/conf.d/proxy_headers.conf;

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

各参数含义如下:

  • Host $host:把原始域名传给后端。后端做虚拟主机路由、生成绝对 URL 都依赖它,不传则后端收到的是 127.0.0.1:8080。
  • X-Real-IP $remote_addr:直连 Nginx 的客户端 IP。只有一级代理时它等于真实用户 IP。
  • X-Forwarded-For $proxy_add_x_forwarded_for:追加式链路记录,格式为"客户端IP, 代理1, 代理2",多级代理下取第一个逗号前的值才是真实 IP。
  • X-Forwarded-Proto $scheme:告知后端用户用的是 http 还是 https。Spring Boot 需要配合 server.forward-headers-strategy=framework,Nginx 侧还需告诉 PHP $_SERVER['HTTPS']=on 或用 fastcgi_param。

后端取真实 IP 的语言实现差别较大,举两例:

php
python
# Django settings.py:需声明可信代理跳数,否则 X-Forwarded-For 不生效
USE_X_FORWARDED_HOST = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

WebSocket、超时与 upstream 负载均衡

结论先行:WebSocket 和长连接必须显式发送 Upgrade/Connection 头并调大超时,否则必然握手失败或 60 秒断连;多副本后端则用 upstream 定义服务器组。

WebSocket 代理需要识别客户端发来的 Upgrade 请求头:

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

server {
    listen 80;
    server_name ws.example.com;
    include /etc/nginx/conf.d/proxy_headers.conf;

    location /ws {
        proxy_pass http://127.0.0.1:9000;
        proxy_set_header Upgrade    $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_read_timeout 3600s;      # WebSocket 空闲超时,默认 60s 会频繁断开
        proxy_send_timeout 3600s;
    }
}

超时参数必须单独调,Nginx 默认 proxy_read_timeout 为 60 秒,导出报表、AI 推理这类慢接口到点就被切断并返回 504:

nginx
location / {
    proxy_pass http://127.0.0.1:8080;
    proxy_connect_timeout 10s;   # 与后端建连超时,内网建议 5~10s
    proxy_send_timeout    60s;   # 向后端发送请求超时
    proxy_read_timeout    120s;  # 等待后端响应超时,慢接口按需调大
    proxy_buffering       on;    # 缓冲后端响应,避免慢客户端拖住后端进程
    proxy_buffer_size     16k;
    proxy_buffers         8 16k;
}

多台后端时用 upstream 做负载均衡(Load Balancing):

nginx
upstream app_backend {
    least_conn;                                  # 最少连接,长连接场景优于轮询
    server 127.0.0.1:8081 weight=3 max_fails=3 fail_timeout=30s;
    server 127.0.0.1:8082 weight=1 max_fails=3 fail_timeout=30s;
    server 10.0.0.11:8080 backup;                # 备用机,主节点全挂才启用
    keepalive 32;                                # 到后端的长连接池
}

server {
    listen 80;
    server_name app.example.com;
    include /etc/nginx/conf.d/proxy_headers.conf;

    location / {
        proxy_pass http://app_backend;           # 注意:这里写 upstream 名称
        proxy_next_upstream error timeout http_502 http_503 http_504;
        proxy_next_upstream_tries 2;
    }
}

调度策略选型:

策略指令适用场景
轮询round_robin(默认)后端规格一致、请求耗时均匀
加权轮询weight=n后端配置不等,按能力分配
最少连接least_conn长短请求混杂,如 WebSocket、文件上传
IP 哈希ip_hash未做会话共享的老系统临时方案
一致性哈希hash $request_uri consistent缓存类反向代理,降低回源

配置 HTTPS 反向代理的正确姿势

结论先行:SSL 证书配在 Nginx 上终止 TLS,后端继续走 HTTP,这是反向代理的标准形态;配好后建议加 HTTP 到 HTTPS 的 301 跳转。

nginx
server {
    listen 80;
    server_name app.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    http2 on;
    server_name app.example.com;

    ssl_certificate     /etc/letsencrypt/live/app.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_session_cache   shared:SSL:10m;

    include /etc/nginx/conf.d/proxy_headers.conf;

    location / {
        proxy_pass http://app_backend;
    }
}

用 Certbot 签发免费证书(有效期 90 天,Certbot 会自动配置续期定时器):

bash
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d app.example.com
sudo certbot renew --dry-run    # 验证自动续期链路是否通畅

常见误区 / 排错提示

  1. proxy_pass 尾部斜杠导致路径丢失:location /api/ { proxy_pass http://127.0.0.1:8080/; } 会把 /api/user 改写成 /user。保留前缀的写法是不加尾部斜杠。
  2. 转发头只在 server 层写却被子 location 覆盖:子块一旦出现任何 proxy_set_header,父级同名指令全部失效,必须在该 location 里重新声明一遍。这是"后端永远只拿到 127.0.0.1"的头号原因。
  3. proxy_pass 写 hostname 但没配 resolver:proxy_pass http://backend.internal; 中的域名只在 Nginx 启动时解析一次,后端 IP 变了就一直失败。应写 IP,或配 resolver 10.0.0.2 valid=30s; 并用变量触发动态解析。
  4. 忘了 SELinux:RHEL 系上 Nginx 转发到本地端口会被 SELinux 拦截,日志出现 Permission denied。放行命令:setsebool -P httpd_can_network_connect 1。
  5. 改完直接 restart:systemctl restart nginx 会断开存量连接,reload 才是热生效。但改了监听端口必须 restart。
  6. 忽略后端健康检查:upstream 默认只是被动摘除(靠 max_fails),首次失败仍会打到坏节点。OpenResty 或 Nginx Plus 才有主动健康检查,开源版建议配外部探活脚本。