blurred image
Next.js + Nginx 动态文件上传与 403/404 问题排查 thumbnail image

Next.js + Nginx 动态文件上传与 403/404 问题排查

Next.js + Nginx 动态文件上传与 403/404 问题排查

问题背景

项目使用:

  • Next.js
  • Nginx
  • Linux(Ubuntu)

需要支持用户上传 .mid 文件。

最初将文件放在:

/public/midisongs

开发环境(Windows)正常,但部署到 Linux 后出现:

  • 某些文件 404
  • 删除 .next 后恢复
  • revalidatePath() 无效
  • 中文文件名异常
  • nginx alias 后出现 403

核心结论

不要把运行时上传文件放进 public

public/ 是:

构建时静态资源目录

不是:

运行时动态文件存储

否则会遇到:

  • Next.js static manifest cache
  • build cache
  • .next 缓存
  • Linux 文件系统问题

正确方案

推荐目录结构

/home/ubuntu/mahirooyama-blog/
├ uploads/
│  ├ midisongs/
│  │   ├ 4b378e32280932b9.mid

├ app/
├ public/
├ next.config.js

Nginx 配置

location /midisongs/ {
    alias /home/ubuntu/mahirooyama-blog/uploads/midisongs/;
}

注意:

alias 后面必须带 /

错误:

alias /home/ubuntu/mahirooyama-blog/uploads/midisongs;

正确:

alias /home/ubuntu/mahirooyama-blog/uploads/midisongs/;

为什么之前会 404

原因:

public/ 下动态新增文件
不会被 Next.js production 正确感知

所以:

  • API 能读取
  • 页面列表能更新
  • 但静态资源 404

删除 .next 后恢复,是因为:

重新生成了 static manifest

为什么 revalidatePath() 无效

revalidatePath('/', 'layout');

只能刷新:

  • RSC cache
  • fetch cache
  • 页面缓存

不能刷新:

静态文件系统映射

因为 .mid 文件不属于:

  • page
  • route
  • fetch

而属于:

Next.js static asset server

为什么 nginx 会 403

403 表示:

nginx 找到了文件
但没有权限读取

不是文件不存在。


Linux 权限关键点

Linux 不仅要求:

文件可读

还要求:

路径上的每一级目录都必须有 x 权限

例如:

/home/ubuntu/mahirooyama-blog/uploads/midisongs/test.mid

nginx 必须能穿过:

/
home
ubuntu
mahirooyama-blog
uploads
midisongs

正确权限配置

1. 允许目录 traversal

chmod o+x /home/ubuntu
chmod o+x /home/ubuntu/mahirooyama-blog

注意:

不是:

chmod 755 /home/ubuntu

只需要:

o+x

即可。


2. uploads 目录权限

chmod -R 755 /home/ubuntu/mahirooyama-blog/uploads

3. 文件权限

find /home/ubuntu/mahirooyama-blog/uploads \
-type f \
-exec chmod 644 {} \;

4. reload nginx

sudo nginx -t
sudo systemctl reload nginx

验证 nginx 是否有权限

Ubuntu 默认 nginx 用户通常是:

www-data

验证:

sudo -u www-data ls \
/home/ubuntu/mahirooyama-blog/uploads/midisongs

如果能列出文件:

权限已经正确

查看 nginx 错误日志

最关键命令:

tail -f /var/log/nginx/error.log

然后浏览器访问一次。

可以直接看到:

  • permission denied
  • forbidden
  • no such file

等真实原因。


最终推荐架构

用户上传文件

放:

/uploads

不要放:

/public

Next.js

负责:

  • 页面
  • API
  • 数据

Nginx

负责:

  • 静态文件
  • 大文件
  • 上传资源

通过:

alias

直接读取磁盘。


推荐文件命名

不要使用原始中文文件名作为磁盘文件名。

推荐:

4b378e32280932b9.mid

原始标题保存在数据库:

{
  "title": "Minecraft",
  "storageKey": "4b378e32280932b9.mid"
}

这样可以避免:

  • Unicode
  • Linux 文件系统
  • URL 编码
  • 特殊字符

相关问题。


总结

正确做法

用户上传

uploads/

nginx alias

/midisongs/*

而不是:

用户上传

public/

Next.js static server