关键是在响应头中设置Content-Disposition为attachment并显式指定Content-Type,推荐用location精确匹配文件后缀,配置default_type application/octet-stream和add_header Content-Disposition "attachment; filename=$1"。

要让浏览器对特定静态资源(如 PDF、ZIP、TXT 等)强制触发下载而非内嵌预览或直接打开,关键是在响应头中正确设置 Content-Disposition 为 attachment,并配合合适的 Content-Type。仅靠 Nginx 的 add_header 指令无法可靠实现——因为 add_header 是“追加”行为,且不支持条件判断,而 Content-Disposition 需要**精确控制、避免重复或冲突**(例如浏览器收到两个 Content-Disposition 头会出错)。因此,必须使用 headers_more 模块的 more_set_headers(可覆盖),或更推荐、无需第三方模块的原生方案:add_header + location 匹配 + 正确的 Content-Type 覆盖。
✅ 推荐做法:用 location 精确匹配文件类型,设置 Content-Disposition + Content-Type
这是最稳定、无需编译额外模块的方式。核心逻辑是:对目标后缀的请求,单独定义 location 块,禁用默认类型推断,显式指定安全的 Content-Type(如 application/octet-stream),再添加 Content-Disposition。
- 在 server 或 http 块中添加如下配置(以强制下载 .pdf、.zip、.xlsx 为例):
location ~* \.(pdf|zip|rar|7z|exe|dmg|xlsx|xls|docx|doc|txt)$ {
# 关键:关闭类型自动识别,防止 Nginx 根据文件名猜出 text/plain 或 application/pdf
default_type application/octet-stream;
<pre class="brush:php;toolbar:false;"># 强制下载,filename 使用原始文件名(注意:需确保文件名安全,无换行/双引号)
add_header Content-Disposition "attachment; filename=$1";
# 可选:禁用缓存(尤其开发调试时)
add_header Cache-Control "no-store";
}
⚠️ 注意:$1 是正则捕获的扩展名(如 .pdf),若想保留完整原始文件名(如 report_v2.pdf),应改用 $request_filename 或 $uri,但需额外处理 URL 编码和安全性(见下条)。
⚠️ 安全提醒:filename 中不要直接拼接用户可控的原始文件名
如果 URL 是 /files/download?name=evil%22%3E%3Cscript%3Ealert(1)%3C/script%3E.pdf,直接用 filename=$arg_name 可能导致 HTTP 头注入。生产环境应:
- 避免从 query 参数或 URI 中直接提取 filename
- 若必须动态生成,先做白名单校验(只允许字母、数字、下划线、短横线、点)
- 对 filename 值进行 RFC 5987 编码(如中文):Nginx 原生不支持,可用 Lua 模块或后端代理处理
- 更稳妥的做法:固定 filename(如
download.bin)或由后端(PHP/Node.js)生成响应头
? 替代方案:用 map + add_header(适合简单场景)
如果只需按扩展名统一处理,且不需复杂逻辑,可用 map 预定义 header 值:
map $sent_http_content_type $disposition {
"~*^application/pdf$" "attachment; filename=\"document.pdf\"";
"~*^application/zip$" "attachment; filename=\"archive.zip\"";
default "";
}
<p>server {
...
add_header Content-Disposition $disposition always;
}</p>⚠️ 限制:依赖 $sent_http_content_type,而该变量在 add_header 执行时可能尚未确定(尤其启用 gzip 或 proxy_pass 时),容易失效。不推荐用于关键下载链路。
? 验证是否生效
部署后,用 curl 检查响应头:
curl -I https://yoursite.com/report.pdf
应看到类似输出:
HTTP/2 200 content-type: application/octet-stream content-disposition: attachment; filename=".pdf" content-length: 123456
若出现 content-disposition: inline 或缺失,说明配置未命中,检查 location 正则、文件路径、MIME 类型是否被其他指令覆盖(如 types 块)。











