Minio + Cloudflare Tunnel 踩坑记:解决 Obsidian Remotely Save 插件同步 403 错误
一、 背景与问题
近期,我计划将 S3 存储迁移至家中 NAS 上运行的 Docker 版 Minio,主要目的是为了让 Obsidian 的 Remotely Save
插件能将笔记同步到这个私有的对象存储上。
在配置过程中,我遇到了一个奇怪的 403 Forbidden
错误。它的奇特之处在于:
-
检查通过:
Remotely Save
插件的“检查”功能(Check)每次都能成功。 -
同步失败:但真正执行“同步”(Sync)操作时,就会稳定地报 403 错误。
我的网络环境采用了内外网分离的 DNS 解析策略(Split-horizon DNS),以实现最佳访问路径:
网络架构设计:
内网访问:当手机、电脑等设备连接家庭 Wi-Fi 时,通过内部 DNS 将域名
minio-api.tomyail.com
解析为 NAS 的内网 IP。流量直接在局域网内传输,速度快、延迟低。外网访问:当设备在外部网络时,域名
minio-api.tomyail.com
会被公网 DNS 解析,流量通过 Cloudflare Tunnel 穿透到家中的 Kubernetes 集群,再由集群路由至 NAS。
这种差异化的访问路径,也导致了内外网需要不同的解决方案。
二、 Minio 部署配置 (Docker Compose)
Minio 服务本身部署在 NAS 的 Docker 环境中,配置如下:
YAML
version: '3'
services: minio: image: bitnami/minio:2025.4.22 restart: always ports: - "9011:9001" # MinIO 控制台端口 - "9010:9000" # MinIO API 端口 environment: # 设置 MinIO 的根用户和密码。请务必将它们更改为强密码! - MINIO_ROOT_USER=xxx - MINIO_ROOT_PASSWORD=xxx - MINIO_BROWSER=on volumes: - ./data:/bitnami/minio/data healthcheck: test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] interval: 30s timeout: 20s retries: 3
💡 关于 Minio 版本的重要提示:
较新版本的 Minio 官方镜像 (
minio/minio
) 移除了 Web 控制台的大部分管理功能(如 IAM 策略、存储桶管理等)。为了保留完整的 UI 功能,可以选择一个较早的、功能未被移除的版本。
官方镜像推荐版本:
minio/minio:RELEASE.2025-04-22T22-12-26Z
Bitnami 镜像对应版本:
bitnami/minio is 2025.4.22
三、 方案一:内网访问 (Nginx Proxy Manager)
在内网,我使用 Nginx Proxy Manager (NPM) 来管理所有服务的自定义域名和 SSL 证书。为了让 Minio 在反向代理后正常工作,必须在 NPM 的高级配置中添加以下 Nginx 指令:
# 确保后端 Minio 服务能收到正确的 Host 头,这是签名验证成功的关键proxy_set_header Host $http_host;
# 传递真实的客户端 IP 地址proxy_set_header X-Real-IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 传递客户端原始的协议类型 (http/https)proxy_set_header X-Forwarded-Proto $scheme;
# 为 Minio 控制台可能用到的 WebSocket 提供支持proxy_set_header Upgrade $http_upgrade;proxy_set_header Connection "upgrade";proxy_http_version 1.1;
# 解除上传文件大小的限制client_max_body_size 0;
添加这些配置后,内网通过 minio-api.tomyail.com
访问 Minio 一切正常。
四、 方案二:外网访问 (Kubernetes + Cloudflare Tunnel)
外网访问的链路更长,流量经由 Cloudflare Tunnel 进入我家中的 Talos Kubernetes 集群,再由集群内的 Gateway API 路由到 NAS。
为了让集群能访问到集群外部的 NAS 上的 Minio 服务,我创建了 Endpoints
和 Service
资源,手动将服务指向了 NAS 的 IP 地址。
# 1. 创建 Endpoints,手动指定 Minio 服务的后端 IP 和端口apiVersion: v1kind: Endpointsmetadata: name: cold-minio-apisubsets: - addresses: - ip: "192.168.50.220" # NAS 的内网 IP ports: - port: 9010 # Docker 映射出的 API 端口---# 2. 创建 Service,使其能够被集群内部的其他服务发现apiVersion: v1kind: Servicemetadata: name: cold-minio-apispec: ports: - port: 9010 targetPort: 9010---# 3. 创建 HTTPRoute,通过 Gateway 将域名流量路由到该服务apiVersion: gateway.networking.k8s.io/v1kind: HTTPRoutemetadata: name: cold-minio-api-routespec: parentRefs: - name: external namespace: kube-system sectionName: https hostnames: - "minio-api.tomyail.com" rules: - backendRefs: - name: cold-minio-api port: 9010
五、 最终的“罪魁祸首”与解决方案
即使配置了上述路由,外网访问在“同步”时依然失败,稳定复现 403 错误。
经过一番排查,我发现问题根源并非出在 Kubernetes 的路由层(Cilium Gateway API),而是出在了最前端的 Cloudflare。
核心原因:Cloudflare 的代理服务(橙色云朵)并不仅仅是简单地转发流量。它会主动地对请求进行分析、改写和缓存。这种“智能”行为对于静态网站是极大的优化,但对于像 S3 这样对请求格式有严格规范的 API 来说,却是致命的。任何未经预期的修改都可能导致 Minio 服务端的签名验证失败。
例如,社区中就有用户报告,在开启“全部缓存”(Cache Everything) 时,Cloudflare 会将 HEAD
请求转为 GET
请求,这完美解释了为什么我的“检查”(通常是 HEAD
或 List
请求)能通过,而“同步”(PUT
请求)会失败。
最终解决方案:配置 Cloudflare 缓存规则
解决办法非常直接:告诉 Cloudflare “请不要碰我的 API 流量”。
-
登录 Cloudflare 控制台,选择您的域名。
-
进入左侧菜单的 缓存 (Caching) -> 缓存规则 (Cache Rules)。
-
创建一条新规则,配置如下:
-
字段 (Field):
主机名 (Hostname)
-
运算符 (Operator):
等于 (equals)
-
值 (Value):
minio-api.tomyail.com
-
选择缓存资格 (Cache eligibility): 选择 绕过缓存 (Bypass cache)
-
这条规则强制 Cloudflare 对所有发往
minio-api.tomyail.com
的请求完全不进行任何缓存和可能的内容改写,而是直接、透明地将原始请求转发到后端。
配置完成后,外网同步的 403 问题迎刃而解。