
一个场景我想大家都不陌生后端服务明明在本机跑得好好的浏览器输http://127.0.0.1:8080也都能正常打开结果同事一访问就抓瞎或者前端同学拿着接口文档对接时发现不同环境接口地址换来换去联调效率低到爆炸。这时候最顺手的解决办法就是拿 nginx 来做一个转发层把外部请求统一接进来再转给“那个本来就能正常访问的网站”。这篇文章就围绕“nginx 转发”这一个核心需求来拆从最小可用配置开始到路径匹配、请求头传递、HTTPS、WebSocket 转发这些避不开的细节最后整理一份常见问题排查清单。内容不绕弯子全部是实际配置中能直接抄走的方案。适合正在折腾 nginx 转发的后端、前端、运维同学也适合刚接手服务器需要快速上手的准同行。1. 项目需求与实现思路拆解1.1 这个需求到底在解决什么问题先看标题关键词——“转发指向一个可以正常访问的网站”。这里有两层信息第一后端服务本身是好的、可访问的。它可能是你本机起的localhost:3000也可能是内网某台服务器上的192.168.1.10:8080甚至是另一个域名下的线上服务。问题从来不出在“服务起没起”而出在“别人怎么访问它”。第二nginx 在这里承担的角色不是网站的最终目的服务器而是一个中转站。用户先访问 nginxnginx 再把请求转给后方那个真实服务拿到响应后再原样返回给用户。对于用户来说他看到的始终是 nginx 的地址完全感知不到后端真实位置。所以这个需求本质上是在解决四类问题端口收敛后端服务监听在 8080、3000、9090 等各类端口对外统一走 80 或 443网址干净好记。跨域与联调前后端分离的项目里前端请求/api后端服务在另一个端口通过 nginx 路径转发可以优雅地抹平端口差异。域名绑定一台服务器上有多个服务用不同子域名或不同路径区分nginx 根据server_name或location决定转发到哪个后端。协议转换比如 HTTP 和 WebSocket 并存的服务nginx 可以同时完成普通请求转发和协议升级转发。理解到这一层你就知道标题里“可以正常访问的网站”并不是废话它决定了转发配置的基调nginx 只是中间层真正干活的是后面那个服务所以你的配置重点应该放在“请求怎么进、怎么转、参数怎么保留”上而不是去纠结后端业务逻辑。1.2 为什么要用 nginx 做转发而不是别的方案说实话能实现转发的东西不少。比如 Java 系的网关、Node.js 里的http-proxy、Python 的gunicorn前置甚至光用 iptables 做端口映射也能达到类似效果。但 nginx 在“转发”这个场景里几乎是默认首选项原因我觉得可以归结为三点。第一是性能。nginx 基于事件驱动架构一个 worker 进程可以处理成千上万并发连接静态资源处理和反向代理效率极高。很多高并发场景下瓶颈根本不在 nginx而在后端应用本身。第二是配置灵活。server、location、upstream三层结构可以组合出各种各样的转发规则。同一台机器同一个 80 端口可以按域名分、按路径分、按参数分甚至按请求头分。改配置不需要动业务代码nginx -s reload一条命令生效。第三是生态成熟。网上能搜到的案例、踩坑分享、排错经验非常丰富遇到问题基本都能找到答案。相比之下自己写代理逻辑或者用冷门工具出了问题排查成本高很多。当然nginx 也不是万能的。它更擅长做七层转发如果要做四层端口转发需要用到stream模块如果要做的转发规则特别复杂、需要大量业务逻辑判断那可能得上 API 网关。但就“指向一个可以正常访问的网站”这种典型需求来说nginx 是最省心、最不容易出错的方案。1.3 一次完整转发涉及的配置项全景在动手写配置之前有必要把 nginx 处理一次转发请求的完整链路搞清楚。一次请求从用户浏览器到后端服务至少经过以下节点浏览器 - nginx server 块listen 端口 server_name 匹配 - location 块路径匹配规则 - proxy_pass转发目标 - 后端服务每个环节都有对应的配置项listennginx 监听的端口默认 80。server_name虚拟主机名用来匹配请求的域名。locationURI 路径匹配规则支持前缀匹配、正则匹配、精确匹配。proxy_pass设置后端服务的地址是整个转发的核心指令。proxy_set_header转发时修改或追加请求头比如Host、X-Real-IP。proxy_redirect处理后端返回的重定向响应头。proxy_connect_timeout/proxy_read_timeout控制与后端连接及读取响应的超时时间。把这一套链路想明白了后面所有配置的修改都是在往这个骨架里填细节。很多人配置转发出了问题根因就是没有理解请求是怎么一路走过去的只盯着某一个指令改来改去结果越改越乱。2. 环境准备与基础转发配置2.1 安装 nginx主流系统的操作示例不同服务器系统安装方式略有差异但总体都不复杂。这里列几个代表性系统的安装命令覆盖最常见的部署环境。Debian / Ubuntu 系sudo apt update sudo apt install nginx -y sudo systemctl enable nginx sudo systemctl start nginxCentOS / AlmaLinux 9 系sudo dnf install epel-release -y sudo dnf install nginx -y sudo systemctl enable nginx sudo systemctl start nginxWindows 系统则是另一种玩法直接到 nginx 官网下载 Windows 版本压缩包解压后进入目录执行start nginx.exe或者在前台运行方便调试nginx.exeWindows 下没有systemctl日常管理靠nginx.exe -s reload、nginx.exe -s stop这些命令。安装完成后验证是否成功nginx -v curl -I http://127.0.0.1能看到HTTP/1.1 200 OK说明 nginx 已经正常工作了。这个默认页面是后面一切配置的基础不要急着删掉先用它验证转发是否生效。实际经验提醒一下生产服务器的 80 端口很可能被其他进程占用安装前先用ss -lntp | grep :80查一下端口情况避免装完才发现端口冲突。2.2 最小可用转发配置从 80 转到 8080假设你现在有一个服务跑在本机 8080 端口访问http://127.0.0.1:8080一切正常。现在想让它通过 80 端口对外提供服务nginx 配置可以这样写server { listen 80; server_name example.com; # 换成你的域名或服务器IP location / { 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; } }把这段配置放进/etc/nginx/conf.d/forward.confUbuntu 系也可以放/etc/nginx/sites-available/并做软链接然后执行nginx -t # 检查语法 nginx -s reload # 平滑重载配置再用curl -I http://127.0.0.1测试如果返回的响应头和后端服务一致说明转发已生效。这里有几个细节值得注意。proxy_pass后面不加路径时nginx 会把原始的请求 URI 原样传给后端。比如你请求http://127.0.0.1/api/usernginx 会转给http://127.0.0.1:8080/api/user路径保持不变。这个特性在后面配置路径转发时特别重要。另外proxy_set_header Host $host;这行保证后端收到的请求头里的 Host 是用户访问的域名而不是127.0.0.1:8080。很多后端框架会根据 Host 生成链接或做域名校验少了这一行就会出现各种诡异问题。2.3 listen 端口与 server_name 匹配规则server块里最容易混淆的就是监听端口和域名匹配的关系。一个 nginx 可以同时配置多个server块分别监听不同端口或绑定不同域名。当请求到达 nginx 时它会先匹配listen端口再匹配server_name。server_name支持三种写法精确匹配server_name example.com;通配符server_name *.example.com;正则匹配server_name ~^www\d\.example\.com$;当多个server块都能匹配同一个请求时优先级从高到低是精确匹配 通配符前缀匹配 通配符后缀匹配 正则匹配。如果都匹配不上则使用默认 server也就是listen标记了default_server的那个块或者配置文件里第一个server块。面试里经常问的“nginx 如何决定把请求交给哪个 server”答案就在这里。实际配置多个域名转发时只要记住每加一个域名就建一个独立server块并在里面配好对应的server_name不要把所有规则揉在一起。3. 核心细节与原理解析3.1 proxy_pass 带不带路径的区别最容易踩坑的地方这是 nginx 转发配置里最经典的坑没有之一。proxy_pass后面的 URL 是否包含 URI 路径会直接影响最终的转发行为。先看不带路径的情况location /api/ { proxy_pass http://127.0.0.1:8080; }此时客户端请求/api/usernginx 会原封不动地把完整 URI 传给后端后端收到的还是/api/user。再看带路径的情况location /api/ { proxy_pass http://127.0.0.1:8080/; }当proxy_pass带有 URI 部分时nginx 会执行“替换”操作匹配location的路径部分会被替换成proxy_pass里的路径。所以客户端请求/api/usernginx 转发给后端的是/user——/api/这一截被换成了/。这个特性在一些场景下非常有用。比如后端服务根本没有/api前缀的概念前端请求统一走/apinginx 只需要把前缀剥掉再转发即可。但如果你没搞清楚这个规则配置里多写或少写一个斜杠结果就是接口全部 404。我自己的经验是在配置转发时先明确要不要保留原始路径。如果后端接口本身就带/api前缀就写不带斜杠的proxy_pass http://127.0.0.1:8080;如果后端接口不带前缀再用带斜杠的写法做替换。另外location用做精确匹配时proxy_pass带不带路径只影响静态资源这类无需转发的场景转发场景里用的最多的是前缀匹配。3.2 请求头信息传递Host 和真实客户端 IP 怎么保留转发配置里有一行容易被忽略但极其重要的设置——请求头传递。默认情况下nginx 转发请求时会把客户端的一些头信息覆盖掉导致后端拿不到真实 IP、获取不到原始 Host最终影响日志分析和业务逻辑。核心的三组配置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后端在处理重定向、生成安全 cookie 时经常会用到。这里要注意一个细节$proxy_add_x_forwarded_for这个变量会把你手动设置的X-Forwarded-For值和$remote_addr拼在一起。如果上游已经传了X-Forwarded-Fornginx 会保留它并追加当前客户端地址这样后端能拿到完整的链路信息。如果你把后端服务放在 nginx 后面但日志里看到的 IP 全是127.0.0.1不用怀疑就是少了X-Real-IP或X-Forwarded-For的传递配置。3.3 HTTPS 转发与自签名证书的配置方法现在很多服务强制要求 HTTPSnginx 做转发时也需要把 443 端口的流量接进来再用proxy_pass转发给后端的 HTTP 服务。这就要求 nginx 在收到 HTTPS 请求后先解密再以 HTTP 方式请求后端整个过程对用户完全透明。首先得有一张证书。正式环境当然推荐用权威 CA 签发的证书但在内网测试或开发环境自签名证书是更灵活的选择而且完全免费。生成自签名证书可以完全用交互式命令完成openssl req -x509 -nodes -newkey rsa:2048 -keyout server.key -out server.crt -days 365执行后按提示依次输入国家、省份、城市、组织名、通用名Common Name 一定要填你访问时用的域名或 IP一个多小时的有效证书就生成了。补充说明一下-nodes表示生成的私钥不加密这样 nginx 启动时不用输入密码适合服务器上无人值守运行-days 365可根据需求调整有效期。然后配置 HTTPS server 块server { listen 443 ssl; server_name example.com; ssl_certificate /etc/nginx/ssl/server.crt; ssl_certificate_key /etc/nginx/ssl/server.key; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; } }同样先nginx -t检查语法再 reload。这时访问https://example.com浏览器会提示证书不受信任选择“继续前往”即可验证 HTTPS 转发链路是否通畅。另外如果后端服务本身也需要 HTTPS比如后端是https://127.0.0.1:8443你需要额外处理证书校验的问题常见做法是在 location 里设置proxy_ssl_verify off;内网环境通常可接受关闭校验但生产环境不建议这样操作最好让 nginx 信任后端的证书避免安全风险。3.4 WebSocket 转发freeswitch ws 端口场景普通 HTTP 转发只处理请求响应就完事了但 WebSocket 是长连接协议转发配置有特殊要求。典型场景如 freeswitch 的 WebSocket 端口默认 5066要让浏览器通过 nginx 去连接它必须把连接升级相关的请求头正确传给后端。WebSocket 建立连接时客户端会发送两个特殊请求头Upgrade: websocket和Connection: Upgrade。如果 nginx 转发时把这俩头丢掉后端就不知道这是一个需要升级协议的请求连接就建立不起来。标准的 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:5066; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_read_timeout 3600s; } }这里面关键的几个点第一proxy_http_version 1.1必须设置。WebSocket 协议基于 HTTP/1.1默认的 HTTP/1.0 不支持 Upgrade 头。第二用map指令定义一个变量connection_upgrade当客户端请求里有Upgrade头时Connection也设为upgrade没有时则设为close避免影响普通 HTTP 请求。第三proxy_read_timeout要适当延长。WebSocket 连接建立后可能长时间没有数据传输默认的 60 秒超时会导致连接被 nginx 掐断。实际测试中建议至少设置 3600 秒根据业务需要甚至可以更长。freeswitch 的具体场景里除了 WebSocket 本身的转发还要注意媒体流是否需要经过 nginx。如果是 WebRTC 呼叫场景SIP 信令走 WebSocket 没问题但 RTP 媒体流是 UDP 包nginx 默认模块不处理 UDP 转发这种情况就需要用 stream 模块或让媒体流直连后端。4. 常见问题排查与经验优化4.1 转发后返回各种状态码怎么快速定位配置完转发最常遇到的是一堆看不懂的 HTTP 状态码。这里整理一张速查表遇到问题时先对照一下状态码含义常见原因与排查方向502 Bad Gatewaynginx 连不上后端后端服务没启动IP/端口写错后端监听在127.0.0.1而 nginx 通过内网 IP 访问504 Gateway Timeout后端响应超时后端处理时间过长调大proxy_read_timeout或后端线程池已满403 Forbidden没有访问权限后端禁止了该来源 IPnginx 配置了allow/deny规则404 Not Found路径不匹配proxy_pass带路径替换导致前缀丢失location匹配规则和预期不一致502 后面跟着connection refused端口根本没监听检查后端进程和端口ss -lntp看一下排查的第一步永远不是改配置而是看日志。nginx 的错误日志默认在/var/log/nginx/error.log访问日志在/var/log/nginx/access.log。一条典型的 502 错误日志长这样[error] 12345#0: *678 connect() failed (111: Connection refused) while connecting to upstream, client: 192.168.1.100, server: example.com, request: GET / HTTP/1.1, upstream: http://127.0.0.1:8080/日志会清清楚楚告诉你连不上127.0.0.1:8080原因是被拒。这个时候再去检查后端进程还活着没有、端口有没有监听效率比瞎猜高得多。4.2 转发后页面打不开但接口却正常问题在静态资源这是一个很典型的场景从 nginx 访问门户页面接口调用都正常但页面样式全丢、图片全挂控制台一堆资源 404。原因多半是页面里的静态资源引用了绝对路径或者错误路径。比如后端页面里写了一行link relstylesheet href/static/css/main.css如果 nginx 只把/api/路径转发给后端而没有处理/static/路径浏览器请求/static/css/main.css时就会打到一个不存在的 location 上最终返回 404。解决方案有三种按推荐程度排序方案一把静态资源路径也转发给后端location /static/ { proxy_pass http://127.0.0.1:8080; }方案二让 nginx 直接托管静态文件。适用于前端构建后的产物已经在服务器上不需要经过后端的情况location /static/ { alias /var/www/project/static/; expires 7d; }方案三修改后端页面里的资源引用为相对路径或统一的 CDN 路径但这种方式侵入性强一般不推荐。另外还有一个很容易被忽略的点cookie 的 path 和 domain。如果后端设置了 cookie 的作用域只在/api下浏览器可能不会把 cookie 带上如果设置了 domain 为后端域名那 nginx 域名下访问时 cookie 也会失效。这类问题表现很隐蔽用户登录状态反复丢失排查时重点看Set-Cookie响应头里的Path和Domain属性。4.3 高并发场景下转发性能的基本优化一台 nginx 默认配置下性能已经很可观但如果你把它用在并发量较高的场景几个基础参数值得调整。第一步是调整 worker 进程数和连接数。worker 进程数建议设为 CPU 核心数可以用auto让 nginx 自动检测worker_processes auto; worker_connections 10240;第二步是开启 keepalive 连接复用避免每次转发都重新建立到后端的 TCP 连接upstream backend { server 127.0.0.1:8080; keepalive 32; } server { location / { proxy_pass http://backend; proxy_http_version 1.1; proxy_set_header Connection ; } }注意这里用了upstream块定义后端好处是后续加机器、做负载均衡都不需要改主配置并且在连接池里维护一定数量的空闲连接性能提升明显。第三步是开启 gzip 压缩减少传输量gzip on; gzip_types text/plain text/css application/json application/javascript; gzip_min_length 1024;还需要注意系统层面的限制。Linux 默认的文件描述符限制可能不够需要调整ulimit -n 65535并在 nginx 主配置里加上worker_rlimit_nofile 65535;nginx 的优化是系统工程远不止这几个参数。但对于转发场景来说先把 worker 数、keepalive、文件描述符这几个调好并发处理能力会有肉眼可见的提升。其余的优化点如缓存、日志切割、内核参数可以在实际压力测试后按需补充。4.4 我常用的排查技巧与习惯最后分享几个我在实际工作中形成的习惯谈不上高大上但确实能省不少时间。第一个习惯是“先确认目标再检查路径”。遇到转发故障我先手动请求一遍后端服务确认后端本身没问题再走 nginx 链路。用curl -H Host: example.com http://127.0.0.1模拟带域名的请求就能判断是不是server_name匹配出了问题。这个习惯帮我过滤掉了至少一半的“伪故障”很多问题根本不是 nginx 配置错了而是后端本身就在报错。第二个习惯是“每次改配置后都做语法检查再 reload”。nginx -t便宜得很但能挡住大多数低级错误。我有一次改了 upstream 名称忘了同步到proxy_pass里nginx -t直接报错省了一次线上事故。第三个习惯是“善用curl -v看全链路”。转发问题不确定是 nginx 的问题还是后端的问题时-v参数能显示 nginx 返回的响应头比如Server: nginx/1.24.0后面跟的具体状态、Via头等信息量比浏览器开发者工具还大。第四个习惯是“保持配置文件模块化”。我习惯一个服务一个配置文件放在/etc/nginx/conf.d/下文件名和功能对应比如portal.conf、api.conf、ws.conf。出问题时直接看对应文件不用在几百行的主配置里大海捞针。配置里加上注释写清楚这个 server 块是转发给谁、解决什么问题三个月后再看依然能一眼看懂。结合这个“nginx转发指向一个可以正常访问的网站”的需求我个人最大的体会是转发本身不难难的是把细节想清楚——路径要不要保留、Host 要不要改写、WebSocket 需要哪些额外头、超时时间设多长。把这些细节一条条理清楚你的 nginx 转发配置才能既稳定又省心。最后再补一个小技巧转发配置完成后用curl -I http://你的域名/和curl -I http://你的域名/api/health各测一遍再对比后端直连的返回结果确认头信息和状态码一致这个项目才算真正交付完成。