共计 2908 个字符,预计需要花费 8 分钟才能阅读完成。
文章目录 [隐藏]
有朋友知道我做了个待办清单的应用,前两周更新了用户中心的功能,但有粉丝反应说自定义头像上传失败,要我赶紧修复,当时想着不影响功能就一直没去修,趁着今天有点空就排查了一下,不查不要紧,一查吓一跳!大家可以点击下面文章去查看我的另一个公众号,可以关注一下!
上传成功,却看不到头像?文件明明存在,访问却 404?这篇文章带你深入 Nginx 的 location 匹配陷阱。
问题现象
周末在生产环境测试头像上传功能,操作一切正常:
- 点击上传头像 ✅
- 选择图片 ✅
- 提示 " 上传成功 " ✅
- 刷新页面 … 头像不见了 ❌
打开浏览器控制台,赫然一个红色的 404:
GET https://www.guozige.com/media/avatars/user18_d07403b000e049228e100fa6f5ff4b42.png 404
初步排查:文件到底在不在?
作为一个 " 经验丰富 " 的开发者,我的第一反应是: 文件可能没保存成功 。
# 进入 Docker 容器检查
$ docker exec todolist_app ls -la /app/media/avatars/
-rwxrwxr-x 1 www www 777090 user18_d07403b000e049228e100fa6f5ff4b42.png
文件存在!大小也正常(777KB)。
那问题出在哪?🤔
深入排查:Docker Volume 的坑?
等等,Docker 容器里的文件,宿主机能访问到吗?
# 检查 Docker 挂载配置
$ docker inspect todolist_app --format '{{json .Mounts}}' | python3 -m json.tool
{
"Source": "/www/data/todolist/media",
"Destination": "/app/media"
}
原来容器的 /app/media 挂载到了宿主机的 /www/data/todolist/media。
# 检查宿主机目录
$ ls -la /www/data/todolist/media/avatars/
-rwxrwxr-x 1 www www 777090 user18_d07403b000e049228e100fa6f5ff4b42.png
文件也在!Volume 挂载没问题。
检查 Nginx 配置
既然文件存在,那一定是 Nginx 配置的问题。看看 media 的 location 配置:
# /www/server/panel/vhost/nginx/html_www.guozige.com.conf
location /media/ {
alias /www/data/todolist/media/;
expires 30d;
add_header Cache-Control "public, immutable";
}
路径完全正确!指向 /www/data/todolist/media/,和 Docker 挂载的目录一致。
我开始怀疑人生了 … 🤯
转机:检查 Nginx 日志
为 media 配置了专门的日志:
$ cat /www/wwwlogs/media_access.log
# 空的!$ cat /www/wwwlogs/media_error.log
# 也是空的!
日志是空的! 这说明请求根本没有进入 location /media/ 块!
那请求去哪了?在这里真的有点怀疑人生了!各种清浏览器缓存,重启服务,反正能想到的都试过了……
真相大白:正则 location 的优先级陷阱
后来在 Claude Code 的定位下,发现了根本原因:仔细检查完整的 Nginx 配置,我发现了这段 " 隐藏的杀手 ":
# 在配置文件的后面...
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
{
expires 30d;
error_log /dev/null;
access_log /dev/null;
}
这是一个 正则表达式 location(~ 开头),匹配所有图片文件!
Nginx Location 匹配规则
Nginx 的 location 匹配优先级如下:
| 优先级 | 修饰符 | 说明 | 示例 |
|---|---|---|---|
| 1 | = |
精确匹配 | location = /api |
| 2 | ^~ |
前缀匹配(阻止正则) | location ^~ /static/ |
| 3 | ~/ ~* |
正则匹配 | location ~ \.png$ |
| 4 | 无 | 普通前缀匹配 | location /media/ |
关键点 :正则匹配(~)优先于普通前缀匹配!
所以当请求 /media/avatars/xxx.png 时:
- Nginx 先找精确匹配 → 没有
- 再找
^~前缀匹配 → 没有 - 然后找正则匹配 → 命中
~ .*\.png$ ✅ - 普通前缀
/media/被跳过了!
而这个正则 location 没有指定 root 或 alias,所以使用了 server 块的默认 root:
root /www/wwwroot/todolist/frontend/dist;
最终 Nginx 去找的是:
/www/wwwroot/todolist/frontend/dist/media/avatars/xxx.png
这个路径当然不存在,所以返回 404!
解决方案
只需要两个字符:^~
# 修改前
location /media/ {alias /www/data/todolist/media/;}
# 修改后
location ^~ /media/ {alias /www/data/todolist/media/;}
^~ 修饰符告诉 Nginx:如果这个前缀匹配成功, 不要再检查正则表达式 ,直接使用这个 location。
# 测试配置
$ nginx -t
nginx: configuration file /www/server/nginx/conf/nginx.conf test is successful
# 重载配置
$ nginx -s reload
刷新页面,头像终于显示了!🎉
经验总结
1. Nginx Location 优先级口诀
精确第一(=),前缀阻断(^~),正则优先(~),普通垫底
2. 常见场景的正确配置
# 静态资源目录:使用 ^~ 阻止正则匹配
location ^~ /static/ {alias /var/www/static/;}
location ^~ /media/ {alias /var/www/media/;}
# 通用图片缓存规则(放在最后)location ~* \.(gif|jpg|jpeg|png|webp)$ {expires 30d;}
3. 排查思路
问题现象 → 确认文件存在 → 检查 Nginx 配置 → 查看日志 → 分析 location 匹配
4. 调试技巧
为可疑的 location 添加独立日志:
location /media/ {
alias /var/www/media/;
access_log /var/log/nginx/media_access.log;
error_log /var/log/nginx/media_error.log;
}
如果日志为空,说明请求没有命中这个 location。
写在最后
这个问题让我折腾了整整 2 小时,最后的解决方案却只有 2 个字符:^~。
Nginx 的 location 匹配规则看似简单,实则暗藏玄机。希望这篇文章能帮你在遇到类似问题时,少走一些弯路。
记住 :当静态资源 404 但文件确实存在时,先检查是否有正则 location 在 " 截胡 "!