解决 MinIO 反代 403 SignatureDoesNotMatch

最近开始折腾自建存储,原计划使用 WebDAV。但发现套了 Cloudflare CDN 只能上传 100MB 的文件,直连服务器上传比较慢,因此考虑使用 MinIO 对象存储 + 分片上传绕过这个限制。不过发现 Cloudflare + MinIO 不是开箱即用的,存在很多坑,比如这个 SignatureDoesNotMatch 问题,没搜到一篇有用的文章,故在此记录一下思路和解决方案。

问题

flowchart LR
    n1["Rclone"] --> n2["Cloudflare"]
    n2 --> n3["MinIO"]

如图部署的MinIO对象存储服务器,无法使用客户端链接,报错 SignatureDoesNotMatch

1
2
3
❯ rclone --config rclone.conf lsd minio:
2025/**/** **:**:** ERROR : : error listing: operation error S3: ListObjectsV2, https response error StatusCode: 403, RequestID: *****, HostID: *****, api error SignatureDoesNotMatch: The request signature we calculated does not match the signature you provided. Check your key and signing method.
2025/**/** **:**:** NOTICE: Failed to lsd with 2 errors: last error was: operation error S3: ListObjectsV2, https response error StatusCode: 403, RequestID: *****, HostID: *****, api error SignatureDoesNotMatch: The request signature we calculated does not match the signature you provided. Check your key and signing method.

解决过程

重装 + 升级最新版本:无效。

使用密码直连:无效。

控制台设置 Access Policy 为 Public:无效。

直连服务器没有问题:

flowchart LR
    n1["Client"] --> n2["MinIO"]

所以问题可能出现在 Cloudflare CDN 上。

查到这两篇文章让关缓存:

但屁用没有,抓到响应头:cf-cache-status: DYNAMIC

确认 Cloudflare Dashboard 中已经添加好 Cache Rule。

确认未使用 SAAS 域名。

确认未开启标头转换,这个应该只能改响应头,改不了请求头。

最后尝试使用 NGINX 反代 + 改头,更改架构为:

flowchart LR
    n1["Client"] --> n2["Cloudflare"]
    n2 --> n3["Nginx"]
    n3 --> n4["MinIO"]

MinIO 官方给的配置:https://min.io/docs/minio/linux/integrations/setup-nginx-proxy-with-minio.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
upstream minio_s3 {
least_conn;
server minio-01.internal-domain.com:9000;
server minio-02.internal-domain.com:9000;
server minio-03.internal-domain.com:9000;
server minio-04.internal-domain.com:9000;
}

upstream minio_console {
least_conn;
server minio-01.internal-domain.com:9001;
server minio-02.internal-domain.com:9001;
server minio-03.internal-domain.com:9001;
server minio-04.internal-domain.com:9001;
}

server {
listen 80;
listen [::]:80;
server_name minio.example.net;

# Allow special characters in headers
ignore_invalid_headers off;
# Allow any size file to be uploaded.
# Set to a value such as 1000m; to restrict file size to a specific value
client_max_body_size 0;
# Disable buffering
proxy_buffering off;
proxy_request_buffering off;

location / {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

proxy_connect_timeout 300;
# Default is HTTP/1, keepalive is only enabled in HTTP/1.1
proxy_http_version 1.1;
proxy_set_header Connection "";
chunked_transfer_encoding off;

proxy_pass https://minio_s3; # This uses the upstream directive definition to load balance
}

location /minio/ui/ {
rewrite ^/minio/ui/(.*) /$1 break;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-NginX-Proxy true;

# This is necessary to pass the correct IP to be hashed
real_ip_header X-Real-IP;

proxy_connect_timeout 300;

# To support websockets in MinIO versions released after January 2023
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Some environments may encounter CORS errors (Kubernetes + Nginx Ingress)
# Uncomment the following line to set the Origin request to an empty string
# proxy_set_header Origin '';

chunked_transfer_encoding off;

proxy_pass https://minio_console; # This uses the upstream directive definition to load balance
}
}

结果还是有问题。

部分文章说需要更改 Host 为 IP:Port:

https://blog.csdn.net/a_123_4/article/details/136448688

结果还是有问题。

最后找到这个 Issue:

https://github.com/minio/minio/issues/15506

说 Cloudflare 存在改头问题导致签名不匹配。

提到还需添加 Accept-Encoding: indentity,这里添加后就能连上,问题解决。

经过测试,最简配置,少一个都不行:

1
2
3
4
5
6
location / {
proxy_set_header Host $http_host;
proxy_set_header Accept-Encoding "identity";
proxy_http_version 1.1;
proxy_pass http://127.0.0.1:9000;
}

其他的配置不需要都能连上,当然最好都按照官方的添加一下。

解决方案

NGINX 反代 + 改头:

flowchart LR
    n1["Client"] --> n2["Cloudflare"]
    n2 --> n3["Nginx"]
    n3 --> n4["MinIO"]
1
2
proxy_set_header Host $http_host;
proxy_set_header Accept-Encoding "identity";

复盘

查看客户端的请求,发现 accept-encoding: identity,估计是传回服务器的时候被改成 gzip 之类的了。响应的 MIME 类型是 application/xml

https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Accept-Encoding

identity:表示恒等函数(即不作任何修改或压缩)。即使省略,此值始终被视为是可接受的。

由于机器性能不佳,我这里的 NGINX 已经关闭 GZIP 压缩。

1
gzip off; 

所以 NGINX 也有可能会出现这个问题,具体情况具体分析。

尝试添加压缩规则,发现无法提交规则:exceeded the maximum number of rules in the phase http_response_compression: 1 out of 0,应该是只对付费用户开放。

在这个页面发现这篇文档:https://developers.cloudflare.com/speed/optimization/content/compression/

从客户端发送请求到 Cloudflare CDN,没有对请求头的 Accept-Encoding 进行修改:

从 Cloudflare CDN 将客户端的请求转发回服务器,发现对请求头的 Accept-Encoding 进行了修改:

反正通篇文章没有提到这个 identity

具体验证被改成啥,服务器 NGINX 上加个头试试:

1
add_header IF-CF-Modify-Accept-Encoding $http_accept_encoding;

客户端响应到:if-cf-modify-accept-encoding: gzip, br,看来真的会被改头。另外可以发现头的 Key 被改成了小写。

关于响应内容,客户端没有出现乱码,返回的响应内容应该是能够正常解压的。