记一次 File Browser 上传失败排障:从 403 Forbidden 到权限修复

问题现象

在 1Panel 中部署了 File Browser 文件管理器,并设置挂载目录为 ./data/mnt。无论是通过网页拖拽上传文件,还是使用 API 调用(带 Token),均返回 403 Forbidden401 Unauthorized,但在登录和浏览目录时一切正常。

点击查看代码
t: tus: unexpected response while creating upload, originated from request (method: POST, url:  http://47.117.186.104:40071/api/tus/%E7%A8%8B%E5%BA%8F%E9%80%BB%E8%BE%91%E5 …%E6%93%8E%E8%99%9A%E6%8B%9F%E6%9C%BA%E5%AE%9E%E7%8E%B0.docx?override=false, response code: 403, response text: 403 Forbidden
, request id: n/a)
    at t.value (index-B0tCXerS.js:4:8418)
    at index-B0tCXerS.js:4:10053
onError	@	index-B0tCXerS.js:4
value	@	index-B0tCXerS.js:4
value	@	index-B0tCXerS.js:4
(匿名)	@	index-B0tCXerS.js:4
Promise.then		
value	@	index-B0tCXerS.js:4
value	@	index-B0tCXerS.js:4
(匿名)	@	index-B0tCXerS.js:4
Promise.then		
value	@	index-B0tCXerS.js:4
(匿名)	@	index-B0tCXerS.js:4
setTimeout		
value	@	index-B0tCXerS.js:4
value	@	index-B0tCXerS.js:4
(匿名)	@	index-B0tCXerS.js:4
Promise.then		
value	@	index-B0tCXerS.js:4
value	@	index-B0tCXerS.js:4
(匿名)	@	index-B0tCXerS.js:4
Promise.then		
value	@	index-B0tCXerS.js:4
(匿名)	@	index-B0tCXerS.js:4
setTimeout		
value	@	index-B0tCXerS.js:4
value	@	index-B0tCXerS.js:4
(匿名)	@	index-B0tCXerS.js:4
Promise.then		
value	@	index-B0tCXerS.js:4
value	@	index-B0tCXerS.js:4
(匿名)	@	index-B0tCXerS.js:4
Promise.then		
value	@	index-B0tCXerS.js:4
(匿名)	@	index-B0tCXerS.js:4
setTimeout		
value	@	index-B0tCXerS.js:4
value	@	index-B0tCXerS.js:4
(匿名)	@	index-B0tCXerS.js:4
Promise.then		
value	@	index-B0tCXerS.js:4
value	@	index-B0tCXerS.js:4
(匿名)	@	index-B0tCXerS.js:4
Promise.then		
value	@	index-B0tCXerS.js:4
(匿名)	@	index-B0tCXerS.js:4
setTimeout		
value	@	index-B0tCXerS.js:4
value	@	index-B0tCXerS.js:4
(匿名)	@	index-B0tCXerS.js:4
Promise.then		
value	@	index-B0tCXerS.js:4
value	@	index-B0tCXerS.js:4
(匿名)	@	index-B0tCXerS.js:4
Promise.then		
value	@	index-B0tCXerS.js:4
(匿名)	@	index-B0tCXerS.js:4
setTimeout		
value	@	index-B0tCXerS.js:4
value	@	index-B0tCXerS.js:4
(匿名)	@	index-B0tCXerS.js:4
Promise.then		
value	@	index-B0tCXerS.js:4
value	@	index-B0tCXerS.js:4
(匿名)	@	index-B0tCXerS.js:4
Promise.then		
value	@	index-B0tCXerS.js:4
(匿名)	@	index-B0tCXerS.js:4
sh	@	index-B0tCXerS.js:4
Ch	@	index-B0tCXerS.js:4
await in Ch		
p	@	index-B0tCXerS.js:4
s	@	index-B0tCXerS.js:4
i	@	index-B0tCXerS.js:2
Av	@	index-B0tCXerS.js:4
i	@	index-B0tCXerS.js:4
index-B0tCXerS.js:4 
 POST http://47.117.186.104:40071/api/resources/test/?override=false 403 (Forbidden)
(匿名)	@	index-B0tCXerS.js:4
wh	@	index-B0tCXerS.js:4
Ch	@	index-B0tCXerS.js:4
d	@	index-B0tCXerS.js:4
(匿名)	@	i18n-DAsyoe9Q.js:4
zn	@	i18n-DAsyoe9Q.js:2
Bn	@	i18n-DAsyoe9Q.js:2
n	@	i18n-DAsyoe9Q.js:4
index-B0tCXerS.js:4 Starting conflict check, 1 possible conflict found.
index-B0tCXerS.js:4 0 conflicts found.
content_main.js:4529 Uncaught (in promise) fetchError: Failed to fetch
    at JS (content_main.js:4529:11258)
    at e.sendFrom (content_main.js:4529:2189)
    at async ry.sendWithHealth (content_main.js:4529:16104)
    at async kK (content_main.js:4529:21795)
    at async xe (content_main.js:4529:19832)
JS	@	content_main.js:4529
sendFrom	@	content_main.js:4529
await in sendFrom		
sendMessage	@	content_main.js:4529
(匿名)	@	content_main.js:4529
sendWithHealth	@	content_main.js:4529
kK	@	content_main.js:4529
xe	@	content_main.js:4529
await in xe		
Ne	@	content_main.js:4529
Vp	@	content_main.js:4529
(匿名)	@	content_main.js:4529
Promise.then		
oE	@	content_main.js:4529
hl	@	content_main.js:4529
(匿名)	@	content_main.js:4529
setTimeout		
(匿名)	@	content_main.js:4529
Promise.then		
oE	@	content_main.js:4529
hl	@	content_main.js:4529
WK	@	content_main.js:4529
await in WK		
wy	@	content_main.js:4529
await in wy		
de	@	content_main.js:4529
(匿名)	@	content_main.js:8260
x2	@	content_main.js:4515
Xe.__r	@	content_main.js:4515
y2	@	content_main.js:4515
(匿名)	@	content_main.js:4515
$m	@	content_main.js:4515
setTimeout		
h2	@	content_main.js:4515
w0.setState	@	content_main.js:4515
ZG.r.__c.r.__	@	content_main.js:4515
(匿名)	@	content_main.js:8260
setTimeout		
(匿名)	@	content_main.js:8260
F	@	content_main.js:8260

排查过程

1. 初看是认证问题

一开始测试 API 时 /api/login 能正常返回 Token,但携带 Token 请求任何需要认证的资源(如列出目录、上传文件)均返回 401。使用 curl 测试同样 401,怀疑是反向代理或配置丢弃了 Authorization 头。
尝试改用 Cookie 认证也无果。

2. 观察浏览器错误

发现网页上传时浏览器控制台报错:

POST /api/tus/... 403 (Forbidden)

同时,尝试创建文件夹也返回 403。说明问题并非认证头丢失,而是服务器直接拒绝了所有写入操作

3. 检查日志,找到根因

查看 File Browser 容器日志,出现如下关键信息:

open /srv/程序逻辑可配功能方案.docx: permission denied
mkdir /srv/test: permission denied

问题确认:容器内运行用户对挂载目录 /srv 没有写入权限。

查看 docker-compose 挂载配置:

volumes:
  - ${MOUNT_PATH}:/srv

环境中 MOUNT_PATH 设定为 ./data/mnt。该宿主机目录的所有者与容器内运行用户(UID=1000)不一致,导致权限拒绝。

解决方案

通过 1Panel 的文件管理器修改宿主机目录权限:
image

  1. 定位宿主机绝对路径
    通常 1Panel 将应用数据放在 /opt/1panel/apps/filebrowser/ 下,进入该目录找到 data/mnt

  2. 修改权限

    • 右键 mnt 目录 → 修改权限
    • 设置所有者为 UID 1000,用户组 1000
    • 权限设为 755 (rwxr-xr-x)
    • 勾选“同时修改子目录权限”
    • 确认
      image
  3. 重启容器使配置生效
    在 1Panel 应用列表点击 File Browser 的「重启」按钮。

再次测试,文件上传、文件夹创建全部恢复正常。

技术总结

  • File Browser 新版默认开启 TUS 断点续传,上传走 /api/tus,但仍需底层文件写入权限。
  • 容器化部署时,务必保证挂载卷的权限与容器内运行用户匹配。1Panel 安装的 File Browser 内部运行用户为 nobody 或普通用户(UID 1000),若宿主机目录属于 root 且不可写,则必现 403。
  • 日志是最直接的突破口docker logs 可以迅速定位 permission denied 而不是在 API 认证上绕圈子。

延伸思考

若后续遇到类似问题,可按以下顺序排查:

  1. 查看容器日志,确认具体错误类型。
  2. 检查挂载目录的权限和所有者。
  3. 检查 File Browser 内部规则(Rules)是否禁止写入。
  4. 若前端 TUS 报错但普通上传正常,可尝试添加 ?tus=false 参数或禁用服务端 TUS。

标签:File Browser / Docker / 1Panel / 权限排障 / 403 Forbidden
日期:2026-05-07


这篇博客完整记录了从现象到解决的思路,希望对你有帮助。可以直接复制到你的技术博客平台,如有需要调整的细节可自行修改。

posted @ 2026-05-07 09:36  口嗨养生博  阅读(19)  评论(0)    收藏  举报