上一篇把 Nginx 整体结构和第一个站点跑起来了。这一篇专门钻新人栽得最频繁、面试问得最高频的几个概念:server 怎么匹配域名、location 优先级、root 和 alias 的区别。
我自己面试候选人时,问 location 五种匹配优先级能答全的不到三成;线上 nginx 配置改错的事故,一半以上是 root / alias 用混了。这一篇按真实排查流程讲透。
一、server 是怎么被匹配上的
一台 Nginx 上常常跑十几个 server(虚拟主机),来一个请求选哪个 server 处理?走这个顺序:
1. 先按 listen 端口过滤(只看监听同一端口的 server)2. 再按 server_name 匹配(精确 → 通配符 → 正则 → default_server)
第一步:listen 端口
server {listen80;server_name a.example.com;}server {listen8080;server_name a.example.com;}
同一个 a.example.com,访问 80 端口走第一个,访问 8080 走第二个。端口是硬过滤。
第二步:server_name 四级优先级
| | | |
|---|
| | server_name a.example.com; | a.example.com |
| | server_name *.example.com; | x.example.com |
| | server_name example.*; | example.com |
| | server_name ~^api\d+\.example\.com$; | api1.example.com |
| | listen 80 default_server; | |
注意一个反直觉的点:正则优先级比通配符低。*.example.com 会先吃掉,正则只在通配符没命中时才匹配。
server_name 实用写法
# 精确单个server_name a.example.com;# 多个精确(一个 server 服务多域名)server_name a.example.com b.example.com;# 通配符server_name*.example.com;# 主域 + 所有子域server_name example.com *.example.com;# 兜底:catch-allserver_name _;# 正则(必须 ~ 开头)server_name ~^api(?<num>\d+)\.example\.com$;
listen 写法的几个变种
listen80; # IPv4 任意地址listen [::]:80; # IPv6 任意地址listen80 default_server; # 标记为默认 server(**同一端口只能有一个 default**)listen443 ssl http2; # HTTPS + HTTP/2(注意 1.25.1 起推荐写 `http2 on;`)listen unix:/run/nginx-internal.sock; # Unix socket(内部转发用)listen80 reuseport; # 启用 SO_REUSEPORT,多 worker 各自 accept(高并发场景)
default_server 的重要性:
如果一个请求带的 Host 头谁都不匹配(比如别人拿你的 IP 直接打),会走 default_server。没显式指定时,会走「同端口下第一个 server」,常常导致莫名其妙的请求被某个不相关的 server 接走。生产配置永远建议:
# 第一个 server 就放个明确的 default,拒绝未识别 Hostserver {listen80 default_server;server_name _;return444; # nginx 内部状态码,直接关闭连接不返回}
二、location 五种匹配方式与优先级(面试高频)
server 选好之后,按 URL 路径匹配 location。location 有五种写法,优先级不是从上到下,而是按修饰符决定。
| | | |
|---|
| = | 精确匹配 | location = /favicon.ico |
| ^~ | | location ^~ /static/ |
| ~ | 区分大小写正则 | location ~ \.php$ |
| ~* | 不区分大小写正则 | location ~* \.(jpg|png)$ |
| | | location /api/ |
匹配流程总结一句话
Nginx 先把所有前缀 location(无修饰符 + ^~ + =)扫一遍,挑出最长前缀;如果最长前缀是 = 或 ^~,直接定;否则继续按配置顺序匹配正则 location,命中正则就走正则,没命中走那个最长前缀。
用一个完整例子演示
server {listen80;server_name demo.local;location = / {return200"A: 精确根路径\n"; }location = /favicon.ico {return200"B: 精确 favicon\n"; }location ^~ /static/ {return200"C: ^~ /static/\n"; }location~ \.php$ {return200"D: 正则 .php\n"; }location~* \.(jpg|png|gif)$ {return200"E: 正则不区分大小写图片\n"; }location /api/ {return200"F: 普通前缀 /api/\n"; }location / {return200"G: 普通前缀 / (兜底)\n"; }}
访问不同 URL 命中谁:
| | |
|---|
/ | | = |
/favicon.ico | | = |
/static/css/app.css | | ^~ |
/static/image.PNG | | |
/index.php | | |
/photo.jpg | | |
/photo.JPG | | |
/api/users | | |
/about | | |
面试题最爱问的两点:
^~ 不是「正则」,是「前缀但抑制正则」。新人爱说成「^~ 是开头匹配」,是错的——它是前缀匹配里的一个修饰,命中后直接跳过正则阶段- 正则之间按出现顺序匹配,普通前缀按最长匹配。所以正则的相对顺序很重要,普通前缀的写法顺序不重要
三、root vs alias 的关键差异(踩坑王)
这两个指令都用来「告诉 nginx 文件在磁盘的哪儿」,但逻辑完全不同:
用同一个请求演示
请求 URL:/static/x.png
# 写法 1:用 rootlocation /static/ {root /var/www;}# Nginx 拼接:root + 完整 URI# 实际磁盘路径:/var/www/static/x.png# 写法 2:用 aliaslocation /static/ {alias /var/www/assets/;}# Nginx 替换:alias 替换掉 location 部分# 实际磁盘路径:/var/www/assets/x.png# 写法 3:alias 路径与 location 不同名(最常见用法)location /imgs/ {alias /data/pictures/;}# 请求 /imgs/cat.jpg → 磁盘 /data/pictures/cat.jpg
一张对照表
| | |
|---|
| | |
| | alias 是目录时必须以 / 结尾 |
| | 只能用在 location 内 |
| | 正则 location 用 alias 要写 $1 类捕获 |
| | |
root 的最佳实践
默认把 root 写在 server 级别一份,各 location 不重复写:
server {listen80;server_name demo.local;root /var/www/my-site; # ← 默认根目录location / {try_files$uri$uri/ /index.html; }location /docs/ {# 没写 root,自动用 server 级别的 /var/www/my-site# 请求 /docs/a.html → /var/www/my-site/docs/a.html }location /assets/ {# 想给 /assets 单独换路径,这里覆盖alias /opt/cdn-cache/;# 请求 /assets/main.css → /opt/cdn-cache/main.css }}
alias 三个最高频踩坑
# ❌ 坑 1:alias 末尾没斜杠location /static/ {alias /var/www/assets; # 缺 /}# 请求 /static/x.png 实际去找 /var/www/assetsx.png(路径被拼坏)# ✅ 正确:location 和 alias 都要么都有 /,要么都没 /location /static/ {alias /var/www/assets/;}# ❌ 坑 2:在 server 上下文用了 aliasserver {alias /var/www/x; # ❌ alias 不能用在 server 级}# ❌ 坑 3:正则 location 配 alias 用错变量location~ ^/imgs/(.+)\.jpg$ {alias /data/$1.jpg; # 这种写法在新版 nginx 已不推荐,建议 try_files}
四、try_files 的妙用:SPA 兜底必备
try_files 让 nginx 按顺序尝试多个文件,第一个存在的就用,全都不存在走最后一个(可以是一个 URI 或状态码)。
静态文件兜底 404
location / {try_files$uri$uri/ =404;# 1. 试 $uri 本身是不是文件# 2. 试 $uri/ 是不是目录(自动找 index)# 3. 都没有,返回 404}
SPA 单页应用(Vue / React)
location / {try_files$uri$uri/ /index.html;# 文件在就给文件,否则统统返回 index.html# 这样前端路由刷新 /users/123 也不会 404}
没有这一行,前端路由刷新就死——Nginx 看到 /users/123 不存在直接 404,因为这个 URL 是前端 router 在浏览器里识别的,nginx 不知道。
PHP 应用经典写法
location / {try_files$uri$uri/ /index.php?$query_string;}location~ \.php$ {fastcgi_pass unix:/run/php/php8.2-fpm.sock;fastcgi_index index.php;include fastcgi_params;fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;}
五、「我的图片为什么 404」标准排查流程
新人最常问的问题之一:「配了 nginx 但图片访问 404,配置看着没问题」。按这个流程排:
Step 1:看 error.log 里的实际路径
sudotail -f /var/log/nginx/error.log# 再访问一次出问题的 URL,等错误打出来
会看到类似:
*123 open() "/var/www/static/static/img/x.png" failed (2: No such file or directory),client: 127.0.0.1, server: demo.local, request: "GET /static/img/x.png HTTP/1.1"
注意 /var/www/static/static/img/x.png——多了一个 static。说明 root 拼接出了重复路径,应该改用 alias 或调整 root。
Step 2:核对 root / alias 配置
# 假设原配置location /static/ {root /var/www/static; # ← root 会拼上 location,变成 /var/www/static/static/...}# 修正方案 A:去掉 root 里的 staticlocation /static/ {root /var/www; # → /var/www/static/img/x.png}# 修正方案 B:用 aliaslocation /static/ {alias /var/www/static/; # → /var/www/static/img/x.png}
Step 3:文件权限和属主
sudo -u www-data ls /var/www/static/img/x.png# 如果 Permission denied 就是权限问题# Nginx 用户需要:目录有 x(能进入)+ 文件有 r(能读)sudochown -R www-data:www-data /var/www/staticsudochmod -R 755 /var/www/static
Step 4:用 nginx debug 看真实匹配过程(杀手锏)
# error_log 改成 debug 级别(**仅排查时开**)error_log /var/log/nginx/error.log debug;
reload 后再请求一次,error.log 里会有:
test location: "/static/"using configuration "/static/"http filename: "/var/www/static/img/x.png"
直接告诉你它命中了哪个 location、拼出了什么路径,排错一目了然。
六、internal 与 X-Accel-Redirect:高级用法
location 加 internal 表示只能被内部 rewrite / redirect 进入,不能从外部 URL 直接访问。
location /protected/ { internal; # 外部 GET /protected/x 直接 404alias /var/www/protected-files/;}location /api/download/ {# 应用做鉴权后,回 X-Accel-Redirect 头给 nginxproxy_pass http://backend;}
后端 PHP / Node 校验通过后回 header X-Accel-Redirect: /protected/big-file.pdf,nginx 接到这个头会内部转发到 /protected/ 由它来发文件,应用本身不传文件、不耗带宽——这是大文件下载鉴权的经典模式。
七、配置最佳实践小抄
server {# ━━ listen ━━listen80;listen [::]:80; # 同时听 IPv6# ━━ server_name ━━server_name www.example.com example.com;# ━━ 根路径写在 server 级,全 server 共享 ━━root /var/www/example;index index.html;# ━━ 日志分站点 ━━access_log /var/log/nginx/example.access.log;error_log /var/log/nginx/example.error.log warn;# ━━ 通用 location ━━location / {try_files$uri$uri/ =404; }# ━━ 静态资源单独缓存 ━━location~* \.(jpg|png|css|js)$ {expires7d;add_header Cache-Control "public, immutable"; }# ━━ 隐藏文件拒绝访问 ━━location~ /\. {deny all;access_logoff;log_not_foundoff; }# ━━ 转发 API 到后端 ━━location /api/ {proxy_pass http://127.0.0.1:3000/; # 注意结尾的 /,下一篇详讲proxy_set_header Host $host;proxy_set_header X-Real-IP $remote_addr; }}
八、常见错误总结
| | |
|---|
| sites-available 改了但没软链到 sites-enabled | ln -s sites-available/x sites-enabled/ |
| | |
| | try_files $uri $uri/ /index.html; |
| | |
| | 加一个 return 444; 的 default |
| | location 和 alias 末尾要么都带 / 要么都不带 |
写在最后
- server 选谁,先按 listen 端口,再按 server_name(精确 > 通配 > 正则 > default)
- location 五优先级背熟:
= > ^~ > ~/~*(按顺序) > 普通前缀(按最长) ^~ 不是「正则」,是「前缀且阻断正则」- root 拼接、alias 替换
- alias 必须末尾带 /(如果指向目录)
- SPA 项目 location / 里
try_files $uri $uri/ /index.html;是必备 - 排查路径问题先开
error_log debug,nginx 会告诉你它命中了哪个 location、拼出了什么真实磁盘路径