1. 从一个真实需求说起:为什么要在同一台机器上跑两个网站
很多人第一次接触 Nginx,都是从"装完、改个配置、能访问"开始的。但真正到了实际场景里,最常撞上的问题不是"怎么装",而是"我手上有两个完全不同的站点,但只有一台服务器、一个公网入口,怎么让它们互不打架"。
这个需求听起来简单,实际拆开来看,涉及的东西一点都不少:端口怎么分配、请求怎么区分、静态资源和后端服务怎么各自独立、日志怎么分开排查、配置写在哪一层才不会互相污染。我见过太多人一开始图省事,把两个站点的配置全塞进一个server块里,用if判断路径来分流,结果上线没两天就出现资源串台、缓存互相覆盖、日志混在一起根本没法定位问题。
所以这篇内容,我想用一个最朴素的实验场景,把"双端口代理不同网站"这件事从头到尾讲透。所谓双端口,就是让 Nginx 同时监听两个不同的端口,比如 8081 和 8082,访问 8081 走 A 站点,访问 8082 走 B 站点,两个站点在配置层面完全隔离,互不干扰。这个实验麻雀虽小,但把 Nginx 里最核心的几个概念——server块、listen指令、location匹配、root与proxy_pass的区别、日志分离——全都串了一遍。
适合谁来读?如果你是刚学 Nginx、只会改默认配置的新手,这篇能帮你建立"一个 server 块就是一个独立站点"的正确心智模型;如果你已经用过 Nginx 但总是靠复制粘贴配置,这篇能帮你搞清楚每一行到底在干什么,出问题该往哪查。下面我按实际动手的顺序,一步步拆。
2. 实验整体设计与思路拆解
2.1 为什么选"双端口"而不是"双域名"
分流两个网站,常见做法有三种:一是用不同域名,靠server_name区分;二是用不同路径前缀,靠location区分;三是用不同端口,靠listen区分。这三种没有绝对优劣,关键看场景。
用域名区分最"正规",但前提是你得有域名解析权限,本地实验还得改 hosts 文件,多一层心智负担。用路径区分最省事,但两个站点的资源路径会互相干扰,比如 A 站的/static和 B 站的/static会撞车,而且很多前端项目默认从根路径加载资源,改路径前缀要动构建配置,很麻烦。
双端口的优势在于:隔离最彻底,配置最直观,实验成本最低。每个端口对应一个独立的server块,两个站点在 Nginx 眼里就是两个毫不相干的虚拟主机,root目录、日志文件、代理目标全都可以各管各的。本地做实验时,浏览器直接输localhost:8081和localhost:8082就能切换,不需要动任何系统配置。这也是我推荐新手从这个方式入手的原因——先把"一个 server 块 = 一个站点"这个模型刻进脑子里,后面再学域名分流就是水到渠成。
2.2 两个站点的角色设计
为了让实验覆盖更全,我特意让两个站点走不同的技术路线,而不是两个都做静态页。这样能顺带把 Nginx 两种最典型的用法都练到:
- 站点 A(端口 8081):纯静态站点,用
root指令直接指向一个本地目录,Nginx 自己把文件读出来返回。这对应的是官网、文档站、前端打包产物这类场景。 - 站点 B(端口 8082):反向代理站点,用
proxy_pass把请求转发给一个跑在本地其他端口的后端服务。这对应的是 API 服务、动态应用这类场景。
这么设计的好处是,你能在同一份配置里同时看到root和proxy_pass的写法差异,理解"Nginx 自己处理"和"Nginx 只做转发"这两种模式的本质区别。很多人分不清这两个指令,其实一句话:root是 Nginx 亲自去磁盘找文件,proxy_pass是 Nginx 把请求原样丢给后面的服务,自己只当中间人。
2.3 端口选择的讲究
端口不是随便挑的。1024 以下是系统保留端口,普通用户没权限监听,所以实验一律用 1024 以上。8081、8082 这类 8 字头端口是社区约定俗成的"备用 HTTP 端口",不容易和系统服务冲突。要避开的是 3306(数据库)、6379(缓存)、27017 这类常见服务的默认端口,免得以后装东西时打架。
另外提醒一句,选端口前最好确认一下没被占用。Linux 下用ss -tlnp | grep 8081,macOS 下用lsof -i :8081,Windows 下用netstat -ano | findstr 8081。这个习惯能帮你省掉后面"启动报错 address already in use"的排查时间。
3. 环境准备与基础配置落地
3.1 安装与目录规划
Nginx 的安装各平台差异不大,包管理器一把梭就行。Ubuntu/Debian 系用apt install nginx,CentOS/RHEL 系用yum install nginx,macOS 用brew install nginx。装完之后先别急着改配置,跑一下nginx -v确认版本,再跑nginx -t确认默认配置没语法错误,这是排查问题的基线。
目录规划这块,我强烈建议不要把所有站点文件都堆在默认的/usr/share/nginx/html里。默认目录是给"欢迎页"用的,你往里塞业务文件,时间一长自己都分不清哪个是哪个。我的习惯是单独建一个实验根目录,比如/data/www/,下面按站点分子目录:
sudo mkdir -p /data/www/site-a sudo mkdir -p /data/www/site-b站点 A 是静态站,直接往/data/www/site-a里放一个index.html就行。站点 B 是代理站,本身不需要放文件,但为了实验完整,我会在本地起一个简单的后端服务来当被代理对象。
3.2 配置文件该放哪:sites-available 还是 conf.d
这是新手最容易迷糊的地方。Nginx 主配置nginx.conf里通常有一行include,把某个目录下的所有.conf文件都加载进来。不同发行版这个目录不一样:Debian/Ubuntu 系习惯用/etc/nginx/sites-available/存配置、sites-enabled/放软链接;CentOS 系习惯直接用/etc/nginx/conf.d/。
我的建议是:实验阶段直接用conf.d目录,一个站点一个文件。比如建site-a.conf和site-b.conf,各自独立。这样做的好处是,改 A 站不会误伤 B 站,删掉某个文件就等于下线某个站点,管理起来清清爽爽。Debian 系如果默认没有conf.d,可以在nginx.conf的http块里手动加一行include /etc/nginx/conf.d/*.conf;。
注意:改配置前先备份原文件,
cp nginx.conf nginx.conf.bak。我踩过的坑是改错了include路径导致整个 Nginx 起不来,有备份能秒回滚。
3.3 站点 A 的静态配置
站点 A 的配置非常短,但每一行都值得说清楚:
server { listen 8081; server_name localhost; root /data/www/site-a; index index.html; access_log /var/log/nginx/site-a.access.log; error_log /var/log/nginx/site-a.error.log; location / { try_files $uri $uri/ =404; } }listen 8081是核心,它决定了这个 server 块只响应 8081 端口的请求。root指定文件根目录,index指定默认首页。try_files $uri $uri/ =404这行是精髓:它按顺序尝试"请求的原始路径 → 当作目录再找一次 → 都没有就返回 404",比单纯用root更严谨,能避免一些路径穿越的意外行为。
日志分离是我特别想强调的一点。默认情况下所有站点日志都写进同一个access.log,两个站点的请求混在一起,排查问题时根本分不清哪条是哪个站的。给每个站点单独指定日志文件,是专业配置的基本素养。
3.4 站点 B 的代理配置与后端准备
站点 B 需要一个后端服务。为了不引入额外依赖,我用 Python 自带的 HTTP 服务来模拟,一条命令就能起:
cd /data/www/site-b python3 -m http.server 9001这条命令会在 9001 端口起一个静态文件服务,我们就把它当作"后端应用"。然后 Nginx 这边配置成代理:
server { listen 8082; server_name localhost; access_log /var/log/nginx/site-b.access.log; error_log /var/log/nginx/site-b.error.log; location / { proxy_pass http://127.0.0.1:9001; 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_pass指向后端地址,后面三行proxy_set_header是把客户端的真实信息透传给后端。很多人代理配完发现后端拿到的 IP 全是127.0.0.1,就是因为漏了这几行。X-Real-IP传真实客户端 IP,X-Forwarded-For传代理链路上的 IP 列表,Host传原始请求的域名。这三件套几乎是反向代理的标配。
4. 核心细节解析与实操要点
4.1 listen 指令背后的匹配逻辑
listen不只是"监听哪个端口"这么简单,它其实定义了 Nginx 的请求分发规则。当请求进来时,Nginx 先按"IP + 端口"找到对应的监听套接字,再在这个套接字下按server_name匹配具体的 server 块。如果两个 server 块监听了同一个端口但server_name不同,Nginx 会按名字精确匹配;如果名字都匹配不上,就落到该端口下的"默认 server"(第一个定义的,或者显式标了default_server的那个)。
我们这个实验里两个 server 监听不同端口,所以匹配逻辑退化成最简单的"端口对端口",不存在歧义。但理解上面这套机制很重要,因为一旦你以后要在一个端口上挂多个域名,靠的就是server_name的匹配顺序。有个细节:listen 8081等价于listen *:8081,也就是监听所有网卡的 8081。如果你只想监听本机,可以写listen 127.0.0.1:8081,这样外部访问不进来,安全性更高。实验环境无所谓,生产环境要按需收紧。
4.2 root 与 alias 的区别,别用错
讲静态站点就绕不开root和alias这对容易混淆的指令。它们的区别在于路径拼接方式:
root /data/www/site-a;配合location /img/,访问/img/a.png时,Nginx 找的是/data/www/site-a/img/a.png,也就是root 路径 + 完整请求路径。alias /data/www/images/;配合location /img/,访问/img/a.png时,Nginx 找的是/data/www/images/a.png,也就是alias 路径替换掉 location 匹配的部分。
一句话记忆:root是"在根目录后面接上请求路径",alias是"用别名目录替换掉匹配前缀"。用错了的典型症状就是 404,而且路径看起来"就差一层目录"。我建议新手实验阶段统一用root,等真正需要把某个 URL 前缀映射到完全不同的目录时再上alias。
4.3 proxy_pass 结尾带不带斜杠,差别巨大
这是反向代理里最经典的坑,没有之一。proxy_pass的 URL 结尾有没有/,行为完全不同:
| 配置写法 | location | 请求路径 | 转发到后端的路径 |
|---|---|---|---|
proxy_pass http://127.0.0.1:9001; | /api/ | /api/user | /api/user |
proxy_pass http://127.0.0.1:9001/; | /api/ | /api/user | /user |
规律是:结尾不带斜杠,原始路径原样透传;结尾带斜杠,location 匹配到的前缀会被替换掉。这个差异导致过无数"本地好好的,一上代理就 404"的事故。我的经验是,如果后端服务的路由和前端请求路径一致,就不带斜杠;如果后端服务是独立部署、路由里没有那个前缀,就带斜杠把它剥掉。实验里我用的location /匹配根路径,带不带斜杠效果一样,但你必须知道这个区别,否则换个 location 就翻车。
4.4 日志格式与排查字段
默认的combined日志格式包含客户端 IP、时间、请求行、状态码、响应大小、Referer、User-Agent,够用但不够细。做代理实验时,我建议自定义一个格式,把上游响应时间和上游地址也带上:
log_format proxy_debug '$remote_addr - $remote_user [$time_local] ' '"$request" $status $body_bytes_sent ' '"$http_referer" "$http_user_agent" ' 'upstream=$upstream_addr ' 'upstream_time=$upstream_response_time ' 'request_time=$request_time';$upstream_addr告诉你请求最终打到了哪个后端,$upstream_response_time是后端处理耗时,$request_time是 Nginx 视角的总耗时。这两个时间一对比,就能判断慢是慢在后端还是慢在 Nginx 本身。这个技巧在排查性能问题时特别有用,属于"配一次受益很久"的投入。
5. 完整实操流程与验证
5.1 从零到跑通的完整步骤
把前面的碎片拼起来,完整流程是这样的:
- 确认环境:
nginx -v看版本,nginx -t看默认配置是否正常。 - 建目录放文件:
mkdir -p /data/www/site-a /data/www/site-b,往 site-a 放一个index.html,内容随便写点能区分的东西,比如标题写"我是站点 A"。 - 起后端服务:
cd /data/www/site-b && python3 -m http.server 9001 &,同样放个index.html写"我是站点 B 的后端"。 - 写两个配置文件:按前面的模板,把
site-a.conf和site-b.conf放进conf.d目录。 - 检查语法:
nginx -t,这一步必须过,报错会直接告诉你哪个文件哪一行有问题。 - 重载配置:
nginx -s reload,注意是 reload 不是 restart,reload 是平滑重载,不断连接。 - 验证:浏览器开
localhost:8081和localhost:8082,应该分别看到两个不同的页面。
5.2 用命令行验证,别只信浏览器
浏览器有缓存,有时候你改了配置它还是显示旧的,容易误判。我习惯用curl做验证,干净利落:
curl -I http://localhost:8081 curl -I http://localhost:8082-I只取响应头,看HTTP/1.1 200 OK和Server: nginx就够了。想看得更细,加-v看完整交互。如果 8081 返回 200 而 8082 返回 502,那基本就是后端服务没起来或者proxy_pass地址写错了,502 是 Nginx 连不上后端的典型信号。
再进一步,可以验证两个站点的隔离性:故意把 site-a 的index.html删掉,访问 8081 应该 404,而 8082 完全不受影响。这个测试能直观证明"两个 server 块互不干扰",比看配置文档印象深得多。
5.3 参数计算:连接数与超时怎么定
实验阶段用默认值就行,但既然讲到了,顺手把两个关键参数的算法说清楚,以后上生产心里有数。
worker_connections:单个 worker 进程能同时处理的最大连接数。理论最大并发 =worker_processes × worker_connections。但注意,反向代理场景下,Nginx 既要接客户端连接,又要连后端,一个请求占两条连接,所以实际能扛的并发要除以 2。比如worker_processes 4、worker_connections 1024,理论 4096 条连接,代理场景下大约能扛 2000 个并发请求。
proxy_read_timeout:Nginx 等待后端响应的超时时间,默认 60 秒。如果后端处理慢,比如跑报表要 2 分钟,就得把这个值调大,否则 Nginx 会提前断开报 504。设置原则是"略大于后端 P99 响应时间",别盲目调成很大,否则后端真挂了,Nginx 会一直傻等,白白占着连接。
6. 常见问题与排查技巧实录
6.1 高频问题速查表
| 现象 | 最可能的原因 | 排查方向 |
|---|---|---|
| 8081 访问 404 | root 路径写错或文件不存在 | 检查root指向的目录和文件权限 |
| 8082 返回 502 | 后端没起或地址端口错 | curl直连后端端口验证 |
| 8082 返回 504 | 后端响应超时 | 看proxy_read_timeout和后端日志 |
| 改了配置没生效 | 忘了 reload 或改错文件 | nginx -t确认,nginx -s reload |
| 启动报端口占用 | 端口被别的进程占了 | ss -tlnp找到占用进程 |
| 后端拿不到真实 IP | 漏了 proxy_set_header | 补上 X-Real-IP 等头 |
| 静态资源 403 | 目录权限不足 | 检查 Nginx 运行用户对目录的读权限 |
6.2 三个我踩过的坑
坑一:reload 之后配置没变。有次我改完配置nginx -s reload,刷新页面还是老样子。折腾半天才发现,我改的是sites-available里的文件,但sites-enabled里的软链接指向的是另一个同名文件。教训是:改配置前先确认nginx -T(大写 T)输出的到底是哪份配置,它会打印所有生效的配置和来源文件路径,一目了然。
坑二:代理后静态资源全 404。前端页面能打开,但 CSS、JS 全挂。原因是页面里写的是绝对路径/static/xxx.js,而我的location /api/只代理了 API 前缀,静态资源请求打到了 Nginx 本地,本地又没有这些文件。解决办法要么给静态资源也配代理,要么让前端用相对路径。这个坑的本质是没搞清楚"哪些请求该走代理、哪些该走本地"。
坑三:日志文件权限报错。自定义日志路径后 Nginx 起不来,报Permission denied。原因是 Nginx 的 worker 进程以www-data或nginx用户运行,而我建的日志目录属主是 root。解决方法是把日志目录属主改成 Nginx 运行用户,或者干脆把日志放在 Nginx 有权限的默认目录下。查 Nginx 运行用户看nginx.conf里的user指令。
6.3 一个实用的排查顺序
遇到问题别乱试,按这个顺序走,90% 的情况能定位:
nginx -t看语法,配置都过不了就别谈别的。nginx -T看生效配置,确认你改的和你以为的一致。curl -I看状态码,200/404/502/504 各自指向不同方向。- 看对应站点的
error.log,Nginx 的报错信息其实很直白。 - 代理问题再
curl直连后端,确认后端本身是好的。
这个顺序的核心逻辑是"从外到内、从配置到服务",先排除配置层的问题,再往服务层查,能避免在错误的方向上浪费时间。
7. 配置优化与后续扩展方向
7.1 让配置更健壮的两个小改动
实验跑通之后,可以加两个小优化让配置更专业。一是给静态资源加缓存头,减少重复请求:
location ~* \.(css|js|png|jpg|jpeg|gif|ico)$ { expires 7d; add_header Cache-Control "public"; }二是给代理加一个健康检查的兜底,后端挂了返回一个友好的错误页,而不是干巴巴的 502:
error_page 502 503 504 /50x.html; location = /50x.html { root /data/www/errors; }这两个改动都不大,但体现的是"配置不只是能跑,还要跑得稳"的思路。
7.2 从双端口到多站点管理
双端口实验的真正价值,是帮你建立起"一个 server 块就是一个独立站点"的模型。等你以后站点多了,这套思路可以直接扩展:每个站点一个.conf文件,统一放在conf.d下,用include批量加载。想下线某个站,删文件 reload 即可;想加新站,复制模板改端口和路径即可。这种"配置即站点"的组织方式,比把所有东西塞进一个大文件里可维护性高太多。
再往后,你可以把端口分流升级成域名分流,把listen 8081换成server_name a.example.com,其余结构几乎不用动。也可以引入 upstream 做后端负载均衡,把单个proxy_pass地址换成一个后端组。这些进阶玩法的地基,都是今天这个双端口实验打下的。
我个人在实际操作中的体会是,Nginx 这东西看着配置项多,但核心就那么几个概念:server 块管站点、location 管路径、root 管静态、proxy_pass 管转发、log 管排查。把这五个吃透,剩下的一大堆指令都是在这五个基础上的微调。双端口代理这个实验之所以值得反复做,就是因为它用最小的场景把这五个概念全串了一遍,做完一遍,你对 Nginx 的理解会比看十篇教程都扎实。最后再分享一个小习惯:每次改完配置,别急着关终端,先nginx -t再reload,然后curl验证一遍,这三步养成肌肉记忆,能帮你挡掉绝大多数低级失误。