secure_link校验失败主因是时间差与算法差,需统一秒级时间戳、严格拼接顺序、正确获取ip、匹配url参数名,并通过日志定位各环节问题。

secure_link 校验失败,多数不是配置写错了,而是签名生成与 Nginx 内部校验之间存在“时间差”和“算法差”。核心问题集中在时区、时间戳单位、拼接顺序、IP 获取方式这四点上,只要对齐,基本就能通过。
时间戳必须用 Unix 秒级,且服务端与签发端时钟要同步
Nginx 的 secure_link_md5 中的 $secure_link_expires 变量只接受秒级 Unix 时间戳(如 1717027200),不是 ISO 8601 字符串(如 2024-06-15T12:00:00+00:00),也不是毫秒级。一旦传入字符串或毫秒值,Nginx 会解析失败,导致 $secure_link_expires 为 0,签名比对直接跳过。
- 后端生成链接时,务必调用
time()(PHP)、int(time.time())(Python)等秒级函数 - 检查服务器系统时间是否准确:运行
date -u看 UTC 时间,再用ntpstat或timedatectl status确认是否已同步 - 若使用容器或云主机,注意宿主机与容器时钟可能漂移,建议挂载
/etc/timezone并启用systemd-timesyncd
签名拼接顺序和内容必须一字不差
Nginx 按你写的 secure_link_md5 字符串模板原样拼接并计算 MD5。常见错误包括:
- 多空格或少空格:
"$secure_link_expires$uri$remote_addr secret_key"中的空格是分隔符,不能写成"$secure_link_expires$uri$remote_addrsecret_key" - URI 包含查询参数?
$uri是不含?及之后部分的路径,例如请求/files/report.pdf?st=abc&e=123,$uri值为/files/report.pdf,不是完整 URL - 是否启用 IP 绑定?若配置中用了
$remote_addr,但前端有 CDN 或反向代理,$remote_addr就是代理 IP,不是真实用户 IP。此时要么去掉该变量,要么配合real_ip_module和set_real_ip_from修正
URL 参数名必须与 secure_link 指令严格对应
secure_link $arg_md5,$arg_expires; 表示从 URL 查询参数中取 md5=xxx 和 expires=xxx。但很多教程误写成 st 和 e(这是旧版或兼容写法),而 Nginx 默认并不识别。
- 若想用
?st=xxx&e=123这种格式,需改用正则提取:secure_link /(.*)\?st=(.*)\&e=(.*);,再配合secure_link_md5 "$3$1$remote_addr secret_key";—— 但更推荐统一用语义化参数名,避免歧义 - 确保链接里没有 URL 编码错误:例如
md5值是 Base64 URL 安全编码(无+、/、=),但 Nginx 不自动解码,所以要么传原始 MD5 十六进制字符串(32 位小写),要么在后端做标准 Base64 解码后再比对(不推荐) - 测试时可用
curl -v "http://localhost/files/test.zip?md5=xxx&expires=1717027200"手动构造,排除前端 JS 编码干扰
调试技巧:用日志看清每一步到底发生了什么
光看 403 没用,得知道哪一环断了。在 location 块中加入临时日志输出:
- 加
log_format debug '$remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" "$arg_md5" "$arg_expires" "$secure_link_expires" "$secure_link"'; - 在对应 location 中设
access_log /var/log/nginx/debug.log debug; - 请求后查日志,重点关注:
$arg_expires是否能被读出、$secure_link_expires是否为有效数字、$secure_link是否为空 - 如果
$secure_link_expires是 0,说明时间解析失败;如果是正常数字但$secure_link仍为空,大概率是 MD5 拼接不一致











