internal指令强制location仅响应Nginx内部重定向(如X-Accel-Redirect、rewrite last),拒绝浏览器或curl等外部直接请求并返回404,是实现安全文件下载的语义级守门机制。

internal 指令是 Nginx 实现安全文件下载的核心机制,它不负责权限判断,但强制切断外部直连路径,只允许由后端程序通过 X-Accel-Redirect 头触发的内部重定向访问。没有它,所有配置都形同虚设。
为什么必须加 internal?
加了 internal 后,Nginx 会拒绝任何来自浏览器或 curl 的直接请求,返回 404。它不是“隐藏路径”,而是语义级限制:该 location 只能被 Nginx 自身内部跳转命中,不能作为普通 HTTP 接口暴露。
- 不加 internal → 用户拼出
/download/secret.pdf就能绕过所有后端逻辑直接下载 - 加了 internal → 即使 URL 正确,浏览器访问也 404;只有后端发来
X-Accel-Redirect: /download/secret.pdf,Nginx 才会查物理路径并流式响应 - internal 不影响 header 设置(如 Content-Type、Content-Disposition),这些仍由后端控制
location 配置的关键细节
用 alias 还是 root,取决于你希望如何拼接路径。多数场景推荐 alias,更直观可控:
- 用
alias /data/files/;+ 请求头X-Accel-Redirect: /dl/report.pdf→ 实际读取/data/files/report.pdf - 用
root /data;+ 同样头 → 实际读取/data/dl/report.pdf(因为 root 会把完整 URI 路径追加到末尾) - alias 末尾斜杠必须与 location 末尾一致:location /dl/ 对应 alias /data/files/;location /dl 对应 alias /data/files
- 路径中避免硬编码盘符或绝对路径风险,Linux 下建议用符号链接统一管理;Windows + Cygwin 环境需用
/cygdrive/c/...格式
后端怎么配合才真正生效?
后端只需设置三个关键响应头,其余交给 Nginx —— 不读文件、不 echo、不输出 body:
-
X-Accel-Redirect:值必须是 Nginx 中定义的 internal location 开头的路径,比如/dl/config.zip,不能是http://或绝对磁盘路径 -
Content-Type:显式指定,否则默认 text/html;PDF 用application/pdf,ZIP 用application/zip,通用二进制用application/octet-stream -
Content-Disposition:设为attachment; filename="xxx.zip"强制下载;中文名务必 UTF-8 编码 + URL encode,例如filename="%E6%96%87%E4%BB%B6.zip" - 必须调用
render nothing: true(Rails)、return response(FastAPI)或exit(PHP),防止后续代码意外输出内容破坏响应流
常见踩坑点和验证方法
部署后立刻验证两件事:能否直连、能否正常下载。
- 在浏览器打开
https://yoursite.com/dl/test.pdf→ 应返回 404,否则 internal 未生效或 location 匹配失败 - 用 curl 测试后端接口:
curl -I https://yoursite.com/api/download?file=test.pdf→ 响应头中必须含X-Accel-Redirect,且不含Transfer-Encoding: chunked或多余 body - 检查 Nginx error.log,若出现
open() "/data/files/test.pdf" failed (2: No such file),说明 alias/root 路径错或文件权限不足(Nginx worker 用户需有读权限) - 下载后文件损坏?大概率是后端多输出了空格、BOM 或调试信息——确保 header 设置后无任何 echo/print 输出











