ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Nginx多端口配置实战:从server块到proxy_pass的完整指南

Nginx多端口配置实战:从server块到proxy_pass的完整指南 1. 多端口访问到底在解决什么问题1.1 什么时候需要给 nginx 开多端口先聊一个非常典型的场景你手头只有一台服务器上面跑着好几个项目——一个公司官网、一个后台管理系统、一个对外提供数据的接口服务。三个东西技术栈不一样部署目录也不一样更要命的是它们各自需要独立的更新节奏。以前偷懒的做法是什么把三个项目全塞到同一个目录下靠路径去区分比如/website、/admin、/api。刚开始还行但项目一旦开始频繁迭代问题就来了后台管理系统改个接口不小心把官网的静态资源路径写错了两个项目互相踩踏排查起来极其痛苦。而且不同项目用的框架不同有的需要 PHP 解析有的只需要静态托管有的要走 Tomcat挤在同一个 server 块里配置会越来越乱。这时候就该考虑多端口方案给每个项目分配一个独立端口nginx 根据请求进来的端口把流量分发到对应的项目目录或后端服务上。比如8080端口 → 官网静态站8081端口 → 后台管理系统8082端口 → 接口服务这样做的直接好处是物理隔离。每个 server 块都是独立的配置空间互不干扰谁挂了也不影响别人。想更新后台直接替换后台的目录或重启后台的服务就行官网那边完全无感知。1.2 多端口不是唯一的方案但大部分场景下最优有人可能会问用同一个端口、不同域名server_name不也能区分吗确实可以比如a.example.com指向项目 Ab.example.com指向项目 B。但域名方案有个前提你得有域名而且还要做 DNS 解析。那用同一个端口、不同路径location呢比如/a走项目 A/b走项目 B。这个方案也能用但缺点前面说了项目之间共享同一个 server 上下文配置一旦复杂起来location 之间的优先级冲突会让人头大。特别是不同项目的 URL 前缀如果有重叠匹配规则会让你怀疑人生。多端口方案最大的优势在于配置简单、隔离彻底、不依赖额外资源。只要服务器防火墙放行对应端口外部就能直接通过IP:端口访问内网调试、临时给客户看效果、压测分流都非常方便。所以我个人的习惯是没有域名约束、项目间完全独立、需要互不影响的场景优先选多端口有域名且需要统一 80/443 入口的再考虑多 server_name 或多 location。本文后面所有配置都围绕多端口这个核心来展开。2. 核心配置拆解端口和路径是怎么关联起来的2.1 端口的载体server 块和 listen 指令nginx 里每个server块就相当于一个虚拟主机它定义了一套独立的监听规则和处理逻辑。而listen指令就是这套规则的入口——告诉 nginx 这个 server 块负责处理哪个端口进来的请求。一个最简的多端口配置长这样server { listen 8080; server_name localhost; root /opt/www/site1; index index.html; } server { listen 8081; server_name localhost; root /opt/www/site2; index index.html; }这里有几个细节值得展开讲。listen后面可以直接跟端口也可以跟IP:端口的组合比如listen 192.168.1.100:8080;。如果你这台服务器绑定了多个 IP想限定某个端口只能通过特定 IP 访问就这么写。不写 IP 的话默认监听服务器上的所有 IP 地址。同一台服务器上可以同时存在多个 server 块监听同一个端口这时候 nginx 会通过server_name来做进一步区分。但如果我们做多端口方案每个端口只对应一个 server 块server_name其实写不写都行养成写上localhost的习惯就好一方面格式完整另一方面万一以后加了域名方案迁移成本低。还有一个非常实用的参数listen 8080 default_server;。default_server表示这个 server 块作为该端口的默认处理者。当某个端口的请求在多个 server 块之间匹配不到合适的server_name时就会落到这个默认块上。如果只有一个 server 块监听该端口加不加这个参数效果一样但加上之后语义更明确也方便以后扩展。2.2 location 匹配规则路径分流的基础配置完端口接下来就是路径的处理。nginx 的路径匹配是靠location指令实现的。很多人在这里栽跟头是因为没搞明白 location 的匹配优先级。nginx 的 location 匹配规则按优先级从高到低排列如下匹配方式写法示例优先级说明精确匹配location /api最高只有请求路径完全等于/api时才命中前缀匹配带^~location ^~ /static/高一旦匹配成功不再检查正则正则匹配区分大小写location ~ \.php$中按书写顺序从上到下匹配第一个命中的生效正则匹配不区分大小写location ~* \.jpg$中同上但不区分大小写普通前缀匹配location /api/低最长匹配原则匹配到最长的那个前缀兜底匹配location /最低以上都没命中时使用举个例子。如果配置了location /api { return 200 exact; } location /api/ { return 200 prefix; } location ~ ^/api/v[0-9] { return 200 regex; }请求/api会命中第一条请求/api/users会先看正则如果正则不匹配再落到前缀匹配/api/如果请求是/api/v2/users正则命中返回regex。理解这个优先级非常重要因为多端口方案里经常会发生这样的情况明明配置了一个 location 想处理某个路径结果请求进来后命中的却是另一个 location导致返回结果完全不对。后面排查部分我会专门讲这个问题。2.3 root、alias、proxy_pass三种路径指向方式location 匹配到之后真正把请求交给谁、指向哪个路径取决于root、alias和proxy_pass这三个指令。它们的区别如果不搞清楚配置起来就是玄学。root是最常用的它的语义是把 location 匹配到的完整路径拼接在 root 指定的目录后面。比如location /blog/ { root /opt/www; }请求/blog/post.html时nginx 会去/opt/www/blog/post.html找文件。注意root 是不舍弃 location 前缀的路径是直接拼上去的。alias的语义则是用 alias 指定的路径替换掉 location 匹配到的前缀。比如location /blog/ { alias /opt/www/static/; }请求/blog/post.html时nginx 会去/opt/www/static/post.html找文件。/blog/这个前缀被完整替换掉了。proxy_pass则完全不同它不涉及本地文件系统而是把请求转发给后端服务。比如location /api/ { proxy_pass http://127.0.0.1:8082/; }最关键的区别在于proxy_pass后面带不带斜杠。带斜杠http://127.0.0.1:8082/表示转发时把 location 匹配到的前缀去掉不带斜杠http://127.0.0.1:8082则表示保留完整路径。这个细节是项目中最常见的路径变长、404的元凶后面我会用案例详细说明。简而言之三者的选择逻辑是托管静态文件优先用root直观、好理解想把请求映射到另一段完全不同的目录用alias把请求转给后端服务Java、Python、Node用proxy_pass3. 从零搭建多端口多站点实操全过程3.1 项目规划与目录结构设计理论说了不少现在来点实际的。我以一个非常常见的服务器场景为例一台 Linux 服务器CentOS 系IP 假设是192.168.1.100上面跑了三个项目规划如下项目端口类型访问入口公司官网8080纯静态站http://192.168.1.100:8080后台管理系统8081静态页面 调用后端接口http://192.168.1.100:8081用户接口服务8082Spring Boot 应用http://192.168.1.100:8082目录设计为/opt/www/ ├── site1/ # 官网静态文件 │ ├── index.html │ └── assets/ ├── site2/ # 后台管理系统 │ ├── index.html │ └── static/ └── app/ # 存放 Spring Boot 包和运行脚本 └── user-api.jar这里有个建议把所有站点都归拢到一个统一目录下比如/opt/www或者/data/www按站点名分子目录。这样做的好处是备份、迁移、权限管理都方便。不要东一个目录西一个目录用的时候找半天。然后启动后端服务。Spring Boot 默认监听8080端口但我们要让 nginx 的 8082 转发过去所以这里后端服务改成监听127.0.0.1:18082这样既不影响 nginx 对外提供 8082 端口又避免了直接暴露后端端口意会一下这个内部服务不对外的设计java -jar /opt/app/user-api.jar --server.port18082当然也可以让后端直接监听 8082nginx 用 8082 转发到本机 8082有点多此一举更稳妥的方式是后端只监听内网地址外部流量统一走 nginx 这个口子。这个设计在后续维护中会省很多事。3.2 编写多端口 nginx 配置nginx 的配置目录结构一般是/etc/nginx/主配置文件是nginx.conf。在nginx.conf的http块里默认会有一行include /etc/nginx/conf.d/*.conf;这个include指令非常关键——它把conf.d目录下所有以.conf结尾的文件都并入了主配置。我们管理多站点时最好不要把全部东西堆在nginx.conf里而是按站点拆分配置文件维护起来清爽得多。比如我为上述三个项目建一个/etc/nginx/conf.d/multi-site.conf内容如下server { listen 8080; server_name localhost; root /opt/www/site1; index index.html; location / { try_files $uri $uri/ 404; } } server { listen 8081; server_name localhost; root /opt/www/site2; index index.html; location / { try_files $uri $uri/ 404; } # 后台系统需要调接口统一转发到后端服务 location /api/ { proxy_pass http://127.0.0.1:18082; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } server { listen 8082; server_name localhost; location / { proxy_pass http://127.0.0.1:18082; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }逐个拆解一下。官网8080就是一个标准的静态站点配置try_files $uri $uri/ 404这句的作用是先尝试找对应文件找不到再尝试找目录都找不到就返回 404。这个写法比直接index index.html更严谨能避免一些目录穿越或空白页面的问题。后台管理系统8081除了托管静态文件还要处理接口请求。假设后台页面的 JS 里所有请求都发到/api/xxx那么 nginx 就把这个路径转发给后端18082。这里proxy_pass后面没有带斜杠所以转发时保留了/api/前缀后端接口如果定义的是/api/user/list就能正确拿到路径。接口服务8082就是把所有请求全盘转发给后端。这个端口一般只对内网开放或者只给特定的调用方使用配置上就一个location /proxy_pass非常纯粹。另外如果你发现自己的场景里 8081 后台系统并不需要单独对外暴露接口端口也就是第三个 server 块反而希望后台所有接口都走 8081 的/api/那第三个 server 块可以完全删掉只保留 8080 和 8081 两个 server 块就行。这里我保留 8082 是为了展示一个端口对应一个 server 块的完整形态方便读者做变形。3.3 语法检查与重载改动配置后的必备操作配置文件写完第一步不是重启而是检查语法。nginx 提供了非常贴心的检查命令nginx -t如果配置正确输出是nginx: configuration file /etc/nginx/nginx.conf test is successful如果配置有问题它会明确告诉你哪个文件哪一行出错。我见过太多人直接nginx -s reload结果配置写错了reload 失败服务没挂但新配置没生效一脸懵。凡是改配置先nginx -t再 reload这个习惯值万金。检查通过后执行平滑重载nginx -s reloadreload是平滑重载不会中断正在处理的请求nginx 会先把新的配置解析好、加载好老的工作进程处理完手头的请求后自动退出。所以这个操作是安全的不用怕影响线上业务。重载完成后验证一下端口监听状态netstat -tlnp | grep nginx或者用新版一点的工具ss -tlnp | grep nginx正常应该看到三个端口都在 LISTENtcp LISTEN 0 511 0.0.0.0:8080 0.0.0.0:* users:((nginx,pid1234,fd6)) tcp LISTEN 0 511 0.0.0.0:8081 0.0.0.0:* users:((nginx,pid1234,fd7)) tcp LISTEN 0 511 0.0.0.0:8082 0.0.0.0:* users:((nginx,pid1234,fd8))然后本地先验证一下静态站能不能访问curl -I http://127.0.0.1:8080/ curl -I http://127.0.0.1:8081/返回200 OK说明静态站没问题。再测接口转发curl http://127.0.0.1:8082/api/user/list如果后端服务正常就能看到 JSON 数据。本地测通了再去外部访问这样能把问题限定在nginx 配置层面还是防火墙网络层面排查效率会高很多。3.4 别忘了防火墙和云平台安全组这一步是很多人忽略的。本地 curl 通了但外部浏览器访问不了十有八九是防火墙没放行端口。CentOS 7/8 上默认用的是 firewalld添加端口的命令是firewall-cmd --permanent --add-port8080/tcp firewall-cmd --permanent --add-port8081/tcp firewall-cmd --permanent --add-port8082/tcp firewall-cmd --reload如果服务器用的 iptables则类似iptables -A INPUT -p tcp --dport 8080 -j ACCEPT iptables -A INPUT -p tcp --dport 8081 -j ACCEPT iptables -A INPUT -p tcp --dport 8082 -j ACCEPT service iptables save另外如果服务器买的是云厂商的 ECS比如阿里云、腾讯云、华为云你还要去云控制台的安全组里放行对应端口。这个非常重要因为云安全组的优先级高于操作系统防火墙安全组不放行系统防火墙开了也没用。我踩过一次坑在腾讯云的一台服务器上折腾了半天防火墙规则外部就是访问不了 8081 端口最后发现是安全组只放行了 80/443/22压根没加 8081。所以遇到本地通、外网不通的问题先检查安全组。4. 多端口环境下的路径与访问排查手册4.1 端口不通的三个排查层级多端口方案上线后最常见的故障就是某个端口访问不了。这类问题的排查思路我习惯分成三个层级按顺序来。第一层进程层面。先确认 nginx 是否真的监听了这个端口。ss -tlnp | grep 8080如果没有任何输出说明 nginx 根本没监听这个端口。可能原因配置没生效忘了 reload、配置里 listen 写错了、或者这个端口被别的进程占用了。nginx -t加nginx -s reload先走一遍。第二层防火墙层面。进程在监听但本机 curl 都通不过curl -v http://127.0.0.1:8080/如果 127.0.0.1 能通但服务器公网 IP 访问不通不是云安全组就是系统防火墙。查看系统防火墙规则firewall-cmd --list-ports云安全组的检查就只能登录云控制台去看了。第三层网络层面。本地通、服务器上通、但其他机器就是访问不了那就要查路由和云厂商的网络策略。这种情况相对少见真遇到了结合telnet或nc探测一下telnet 你的服务器IP 8080如果连接超时基本可以确定是网络策略拦截如果连接被拒绝说明端口没在监听或被防火墙挡了。按照这个层级一步步查绝大多数问题 10 分钟内能定位。4.2 403 与 404静态站最常遇到的两个状态码端口通了但打开页面报 403 或者 404这是多端口静态站点最常遇到的问题。403 Forbidden 的常见原因有三类第一类是目录权限不够。nginx 默认以nginx用户运行如果站点目录的属主不是你手动改过的某个用户而nginx用户对该目录没有读权限就会 403。检查目录权限ls -ld /opt/www/site1如果目录权限是drwxr-xr-x属主是root那nginx用户属于nginx组是可以读的。但如果你用了chmod 700那就只有属主能访问nginx用户直接 403。解决办法很简单把目录属主改成nginx或调整权限为 755chown -R nginx:nginx /opt/www/site1第二类是目录下没有 index 文件。index index.html;配置了首页文件但目录里实际没有index.htmlnginx 又找不到可列目录的权限默认禁止目录列表于是返回 403。这个很好排查去目录里ls一下就知道了。第三类是SELinux 拦截。CentOS 上 SELinux 默认开启nginx 对某些目录的访问可能被 SELinux 策略拦截。如果上面两个原因都排除了试试临时关闭 SELinuxsetenforce 0再访问一次如果好了说明就是 SELinux 的问题。要么永久关闭/etc/selinux/config里将SELINUXenforcing改为disabled要么给目录打正确的 SELinux 标签。生产环境我建议打标签而不是静默关闭毕竟安全机制还是有用的chcon -R -t httpd_sys_content_t /opt/www/site1404 的问题则多半是root路径配置不对。举个例子你的文件实际在/opt/www/site1/index.html配置里却写成了server { listen 8080; location / { root /opt/www/site1/static; # 路径多了一层 } }那 nginx 会去/opt/www/site1/static/index.html找找不到自然返回 404。这种问题通过nginx -t是查不出来的因为语法没问题纯粹是路径写错了。最快的排查方法nginx -V 21 | grep prefix # 查看 nginx 安装前缀以及打开错误日志tail -f /var/log/nginx/error.log错误日志里会明确写出open() /opt/www/site1/static/index.html failed (2: No such file or directory)这就是 404 的直接原因。看日志永远是排查路径问题的最优解。4.3 proxy_pass 斜杠陷阱一个斜杠引发的路径丢失在多端口方案里我们经常用反向代理把请求转发给后端。这里有一个高频坑proxy_pass后面到底加不加斜杠效果完全不同。记住一个简化版的规则proxy_pass http://127.0.0.1:18082;不带斜杠请求路径原样转发前端访问/api/user后端收到的也是/api/user。proxy_pass http://127.0.0.1:18082/;带斜杠请求路径中匹配 location 的那一段被去掉前端访问/api/user后端收到的是/user。举个例子如果你后端接口定义的是GetMapping(/user)但你 nginx 配置的是location /api/ { proxy_pass http://127.0.0.1:18082/; }而前端发的请求是/api/user那么 nginx 转发给后端的路径是/user这刚好和后端接口定义一致——看起来歪打正着能用。但如果后端接口定义的是/api/user这个配置就会 404因为后端收到的路径变成了/user。我见过最魔幻的连续踩坑是运维觉得带斜杠是官方推荐于是把所有proxy_pass都加了斜杠。结果一个后端接口路径写的是/api/userlocation 也是/api/加斜杠后转发到后端的路径成了/user后端返回 404运维排查了半天最后发现是斜杠的问题。所以在实际工作中我的建议是如果后端接口本身就带/api这个前缀就用不带斜杠的写法原样透传。如果后端接口的路径和前端路径不一致比如前端发/api/user后端接口是/user则用带斜杠的写法去掉前缀再转发。务必跟后端开发对齐接口路径的定义不然这个斜杠问题就会周期性地折磨你。4.4 多说几个隐蔽的坑除了上面三类典型问题多端口配置中还有几个比较隐蔽的坑值得单独拿出来说。第一个include的文件名后缀问题。默认配置里写的是include /etc/nginx/conf.d/*.conf;如果你新建的文件叫multi-site.conf.bak或者111它不会被加载。很多人把文件从 Windows 传上来后缀变成.conf.txt结果 nginx 半天没反应检查/etc/nginx/conf.d/目录下文件后缀才发现问题。一个朴素又有效的习惯建配置文件名一律小写后缀严格用.conf。第二个备份文件被 include 进去。与上面相反如果你在conf.d目录下用cp multi-site.conf multi-site.conf.bak做备份而这个目录的 include 是*通配有些人改成include /etc/nginx/conf.d/*;不带.conf后缀那么.bak文件也会被加载。如果这个备份里监听了同样的端口就会和正式配置冲突reload 时直接报duplicate listen port。所以尽量把备份文件放到conf.d目录外或者用vhost目录单独管理避免误加载。第三个多 server 块同端口时server_name匹配导致串站。如果你的配置中有两个 server 块都listen 8080只是server_name不同当通过 IP 访问时nginx 会把请求交给default_server或第一个匹配的 server 块。这时候如果两个 server 块之间根路径一样但目录不同就可能出现打开 A 站点显示的是 B 站点的内容这种诡异现象。排查方法是在每个 server 块里加一个唯一的响应头标签add_header X-Server-Tag site1 always;然后curl -I http://127.0.0.1:8080/看返回的头就能确认请求到底落到哪个 server 块了。这个方法在多 server 环境排错时极其好用。第四个location 里不写结尾斜杠导致路径拼接错误。比如location /blog { root /opt/www; }访问/blog时nginx 会拼出/opt/www/blog但如果访问/blogabc也会匹配这个 location拼出/opt/www/blogabc。所以如果你的本意是只匹配/blog这一个目录记得写成location /blog/带上结尾斜杠避免把其他路径也吞进来。这是一个很多人不会注意但实际影响很大的细节。5. 多端口方案还能怎么扩展多端口访问的配置骨架搭好之后扩展性是非常强的。我在这里补充几个进阶用法让这套方案能适应更多业务场景。场景一HTTPS 多端口。现在网站基本都要上 HTTPS。如果你的多端口方案要兼容 HTTPS只需要在对应 server 块里加上证书配置server { listen 8443 ssl; server_name localhost; ssl_certificate /etc/nginx/ssl/example.crt; ssl_certificate_key /etc/nginx/ssl/example.key; root /opt/www/site1; index index.html; }配置和 80 端口几乎一样只是listen加上了ssl参数然后指定证书路径。注意每个想启用 HTTPS 的端口都需要单独指定证书如果多个端口共用一套证书也是允许的。完备的证书配置还涉及ssl_protocols、ssl_ciphers等调优项但基础配置就是上面这两行。场景二同端口多域名区分服务。如果你手头有域名更推荐的方案是同一端口比如 80 或 443上通过server_name区分不同站点。这样外部访问更友好不需要记端口号。配置非常直观server { listen 80; server_name site1.example.com; root /opt/www/site1; } server { listen 80; server_name site2.example.com; root /opt/www/site2; }这种方案和本文的多端口方案可以混用。比如我用 80 端口跑正式站点用 8081 端口跑内部管理系统就是域名 端口两手抓。场景三可视化配置工具。如果你觉得手写 nginx 配置还是麻烦现在有一些开源的可视化工具可以辅助比如 Nginx Proxy Manager、nginxWebUI 之类的。它们通常提供网页界面图形化添加站点、反向代理、SSL 证书底层自动生成配置并 reload。对新手非常友好也适合管理站点数量较多的场景。不过我的建议是先手写配置搞懂原理再用可视化工具提效。因为工具生成的东西出了问题你还是得回到配置文件去排查不懂原理会非常被动。这个方案后续扩展的思路也很多比如给 8080 和 8081 分别配置访问日志access_log各写各的文件比如用upstream给 8082 这个端口背后挂多个后端实例做负载均衡再比如通过limit_req对某个端口做请求速率限制防止接口被刷。多端口的架构本质上就是一个 server 块一个服务在这个框架内你想做什么都很自然。说到底nginx 多端口配置本身并不难难的是把它放进一个清晰、可维护的结构里。我这几年的实际体会就是配置越规整问题越少每条配置都清楚为什么这么写线上排障的时间就能缩短一大半。希望这篇文章能帮你在遇到多端口需求的时候少走一些我当年走过的弯路。
返回列表