Nginx反向代理S3/OSS出现403的主因是TLS握手时SNI为空,需启用proxy_ssl_server_name on并用bucket完整域名写proxy_pass;SNI与Host头必须一致且指向有效bucket。

直接结论:Nginx 反向代理到 S3/OSS 类服务(如 bucket.s3.amazonaws.com 或 bucket.oss-cn-hangzhou.aliyuncs.com)时出现 403,且直连正常,大概率是 proxy_pass 写了 IP 或未启用 proxy_ssl_server_name on,导致 TLS 握手阶段 SNI 字段为空,上游云存储拒绝建立连接——它根本没认出你要访问的是哪个 bucket。
为什么 S3/OSS 对 SNI 敏感
S3 和主流对象存储都依赖 SNI 区分请求归属的 bucket。例如:
-
https://my-bucket.s3.us-east-1.amazonaws.com和https://other-bucket.s3.us-east-1.amazonaws.com共享同一 IP,靠 Client Hello 中的 SNI 字段区分 - 如果 Nginx 发起 TLS 握手时不带 SNI(比如
proxy_pass https://52.216.128.100/),上游会默认返回一个“兜底”证书或直接拒连,常见表现为 403(不是 404,也不是 SSL 错误) - 这种 403 不带明确提示,
curl -v看不到证书错误,但 Wireshark 抓包可确认Server Name Indication字段为空
必须启用 proxy_ssl_server_name on 并用域名写 proxy_pass
这是最常被忽略的硬性要求。Nginx 默认不会把原始请求的 Host 头复用为上游 SNI;它只认 proxy_pass 后面的域名。
- ✅ 正确写法:
proxy_pass https://my-bucket.s3.us-east-1.amazonaws.com/;(结尾斜杠不能少) - ✅ 必须加:
proxy_ssl_server_name on;(Nginx 1.7.0+,低于此版本必须升级) - ❌ 错误写法:
proxy_pass https://52.216.128.100/;(SNI 丢失) - ❌ 错误写法:
proxy_pass https://s3.us-east-1.amazonaws.com/;(缺少 bucket 前缀,SNI 是s3.us-east-1.amazonaws.com,无法路由到具体 bucket)
多级代理或动态 bucket 场景下要显式控制 SNI
如果你的业务需要通过一个统一入口反向代理多个 bucket(比如按请求路径 /bucket-a/xxx → bucket-a.s3.region.amazonaws.com),就不能只靠静态 proxy_pass。
- 用
resolver避免 DNS 缓存干扰:resolver 8.8.8.8 valid=30s; - 用变量拼接 bucket 域名,并强制指定 SNI:
proxy_ssl_name "$bucket_domain"; - 示例配置片段:
location ~ ^/([^/]+)/(.*)$ {
set $bucket_name $1;
set $bucket_domain "$bucket_name.s3.us-east-1.amazonaws.com";
resolver 8.8.8.8 valid=30s;
proxy_pass https://$bucket_domain/$2;
proxy_ssl_server_name on;
proxy_ssl_name "$bucket_domain";
proxy_set_header Host $bucket_domain;
}
注意:proxy_ssl_name 是 Nginx 1.11.12+ 才支持,旧版本只能走静态域名 + proxy_ssl_server_name on。
别忘了 Host 头和证书验证的配合
SNI 解决了“连谁”,Host 头解决“找谁的资源”。两者不一致也会触发 403(尤其 OSS):
-
proxy_set_header Host $host;是错的——它会把客户端请求的Host(比如cdn.example.com)透传过去,上游不认识 - 应设为 bucket 域名:
proxy_set_header Host $bucket_domain;(静态场景)或proxy_set_header Host "$bucket_name.s3.us-east-1.amazonaws.com"; -
proxy_ssl_verify off;可临时关闭证书校验(测试用),但生产环境必须配proxy_ssl_trusted_certificate,否则可能因证书链不全被拒
真正容易被忽略的是:SNI 和 Host 头必须指向同一个逻辑 bucket,且这个 bucket 在云厂商控制台已正确配置跨域(CORS)与权限策略——否则即使 TLS 握手成功,HTTP 层仍会返回 403。











