Skip to content

PicGo + Cloudflare R2 配置与排障手册 ​

本文记录一套已经实际使用的 PicGo + Cloudflare R2 配置,并提供从快速检查到深入定位的排障流程。

配置概览 ​

上传链路:

text
PicGo → picgo-plugin-s3 → 本地 HTTP 代理(可选)→ Cloudflare R2

访问链路:

text
Markdown 图片 URL → cdn.yunshux.com → Cloudflare R2

两条链路相互独立:CDN 访问很快,不代表 R2 上传一定快;R2 Endpoint 可连接,也不代表 CDN 域名配置正确。

当前示例使用以下配置:

text
Bucket:yunshu
Region:auto
Endpoint:https://<ACCOUNT_ID>.r2.cloudflarestorage.com
上传路径:images/{year}/{month}/{fileName}-{year}{month}{day}-{md5:0,8}.{extName}
HTTP 代理:http://127.0.0.1:7897
CDN:https://cdn.yunshux.com
WebP:quality 80,method 6

其中 <ACCOUNT_ID>、Access Key ID 和 Secret Access Key 必须替换为自己的值;代理端口应以 macOS 当前系统代理为准。

前置准备 ​

创建 R2 Bucket 和访问凭据 ​

  1. 在 Cloudflare 控制台进入 R2 Object Storage,创建名为 yunshu 的 bucket。
  2. 在 R2 的 API Token 管理页面创建凭据。
  3. 将权限范围限制到 yunshu,仅授予上传所需的最小权限。PicGo S3 上传通常使用该 bucket 的 Object Read & Write。
  4. 保存生成的 Access Key ID 和 Secret Access Key。Secret Access Key 通常只展示一次。

密钥安全

不要把 Access Key ID、Secret Access Key 或完整 PicGo 配置写进本文、截图、聊天记录和 Git。排障命令只需要 Account ID,不需要真实密钥。

绑定自定义域名 ​

在 Cloudflare 控制台进入:

text
R2 → yunshu → Settings → Custom Domains

绑定 cdn.yunshux.com,等待状态变为可用,再访问一个已上传对象验证域名。自定义域名负责公开读取;R2 S3 Endpoint 负责 PicGo 上传。

安装 PicGo 插件 ​

在 PicGo 的插件设置中安装:

text
picgo-plugin-s3

如果需要上传前转为 WebP,再安装:

text
picgo-plugin-compress-next

安装完成后重启 PicGo,确认图床设置中出现 Amazon S3,并将它设为默认图床。

PicGo 配置 ​

Amazon S3 基础配置 ​

在 PicGo → 图床设置 → Amazon S3 中填写:

text
应用密钥 ID:
自己的 R2 Access Key ID

应用密钥:
自己的 R2 Secret Access Key

桶名:
yunshu

上传文件路径:
images/{year}/{month}/{fileName}-{year}{month}{day}-{md5:0,8}.{extName}

地区:
auto

自定义节点:
https://<ACCOUNT_ID>.r2.cloudflarestorage.com

代理:
http://127.0.0.1:7897

代理不是固定必填项。如果当前网络直连 R2 上传稳定,可以留空;如果直连很慢而 HTTP 代理链路正常,再填写系统当前代理。

ACL 和寻址方式 ​

Cloudflare R2 不支持通过对象级公开 ACL 实现公开访问。插件存在 ACL 字段时:

  • 优先留空或关闭该选项,使插件不发送 ACL。
  • 如果 UI 强制要求选择,使用 private。
  • 不要使用 public-read。
  • 公开访问统一通过 R2 自定义域名或其前置 CDN。

pathStyleAccess 不要机械开启

不同 picgo-plugin-s3 版本和 AWS SDK 的字段语义可能不同。先保持插件默认值;只有出现 bucket host、签名或 Endpoint 相关错误,并确认当前插件把 pathStyleAccess: true 定义为路径式访问时,再开启并复测。不要把它当作所有 R2 配置的固定要求。

输出 URL 和 WebP ​

根据当前插件界面选择一种配置方式:

  • UI 字段是“图片访问域名”时,填写 https://cdn.yunshux.com。
  • 使用插件原始配置 outputURLPattern 时,填写 https://cdn.yunshux.com/{path}。

不要在基础域名后重复拼接上传路径,否则可能生成两段相同目录。

启用 picgo-plugin-compress-next 后配置:

text
格式:WebP
quality:80
method:6

首次配置后上传一张不含敏感信息的测试图片,并确认:

  1. PicGo 日志包含 Uploading... Current uploader is [aws-s3]。
  2. 最终出现 PicGo SUCCESS。
  3. 返回 URL 以 https://cdn.yunshux.com/ 开头。
  4. 浏览器能够打开该 URL。

30 秒快速排障 ​

按以下顺序检查,不需要先运行 Wrangler。

  1. 查看 macOS 当前系统代理:

    bash
    scutil --proxy

    优先查看已启用的 HTTPEnable、HTTPProxy 和 HTTPPort。没有启用 HTTP 代理时,再查看已启用的 HTTPSEnable、HTTPSProxy 和 HTTPSPort。

  2. 检查代理端口。以下为当前示例,端口变化后必须替换:

    bash
    nc -vz -w 3 127.0.0.1 7897

    出现 succeeded 表示端口可连接;出现 Connection refused 或超时,通常是代理软件未启动、系统代理未开启或端口已经变化。

  3. 将 PicGo 代理更新为当前 HTTP 代理,例如:

    text
    http://127.0.0.1:7897

    再上传一张测试图片,对比日志中 Uploading... 到 PicGo SUCCESS 的耗时。

代理协议

picgo-plugin-s3 的代理字段需要 HTTP 代理 URL 时,不能直接填写 socks5://...。如果系统只有 SOCKS 代理,请在代理软件中开启 HTTP/mixed 端口,再填写 http://主机:端口。

如果仍失败,先确认 Amazon S3 是默认图床。日志若显示 [picgo-cloud],说明当前上传器选错,与 R2 网络无关。

一键排障脚本 ​

脚本仅适用于 macOS,长期保存到 ~/.local/bin/picgo-r2-check。它不会读取或输出任何 R2 密钥,也不会修改 PicGo 配置。

安装脚本 ​

先把脚本中的 YOUR_ACCOUNT_ID 替换为自己的 Cloudflare Account ID,再执行整段命令:

bash
mkdir -p ~/.local/bin

cat > ~/.local/bin/picgo-r2-check <<'EOF'
#!/usr/bin/env bash

set -u

for cmd in scutil nc curl; do
  if ! command -v "$cmd" >/dev/null 2>&1; then
    echo "❌ 缺少依赖:$cmd"
    exit 1
  fi
done

ACCOUNT_ID="YOUR_ACCOUNT_ID"
BUCKET="yunshu"
CDN="https://cdn.yunshux.com"
UPLOAD_PATH='images/{year}/{month}/{fileName}-{year}{month}{day}-{md5:0,8}.{extName}'

if [[ -z "$ACCOUNT_ID" || "$ACCOUNT_ID" == "YOUR_ACCOUNT_ID" ]]; then
  echo "❌ 请先把脚本中的 YOUR_ACCOUNT_ID 替换为自己的 Cloudflare Account ID"
  exit 1
fi

R2="https://${ACCOUNT_ID}.r2.cloudflarestorage.com"
PROXY_DATA="$(scutil --proxy)"

proxy_value() {
  printf '%s\n' "$PROXY_DATA" | awk -v key="$1" '$1 == key { print $3; exit }'
}

HTTP_ENABLE="$(proxy_value HTTPEnable)"
HTTP_HOST="$(proxy_value HTTPProxy)"
HTTP_PORT="$(proxy_value HTTPPort)"
HTTPS_ENABLE="$(proxy_value HTTPSEnable)"
HTTPS_HOST="$(proxy_value HTTPSProxy)"
HTTPS_PORT="$(proxy_value HTTPSPort)"
SOCKS_ENABLE="$(proxy_value SOCKSEnable)"
SOCKS_HOST="$(proxy_value SOCKSProxy)"
SOCKS_PORT="$(proxy_value SOCKSPort)"

PROXY=""
PROXY_HOST=""
PROXY_PORT=""
PROXY_SOURCE=""

if [[ "$HTTP_ENABLE" == "1" && -n "$HTTP_HOST" && -n "$HTTP_PORT" ]]; then
  PROXY_HOST="$HTTP_HOST"
  PROXY_PORT="$HTTP_PORT"
  PROXY_SOURCE="HTTP"
elif [[ "$HTTPS_ENABLE" == "1" && -n "$HTTPS_HOST" && -n "$HTTPS_PORT" ]]; then
  PROXY_HOST="$HTTPS_HOST"
  PROXY_PORT="$HTTPS_PORT"
  PROXY_SOURCE="HTTPS(按 HTTP CONNECT 使用)"
fi

if [[ -n "$PROXY_HOST" && -n "$PROXY_PORT" ]]; then
  PROXY="http://${PROXY_HOST}:${PROXY_PORT}"
fi

check_url() {
  local label="$1"
  shift

  echo
  echo "$label"
  curl --connect-timeout 5 --max-time 15 \
    -o /dev/null -sS \
    -w 'HTTP: %{http_code}\nDNS: %{time_namelookup}s\nTCP: %{time_connect}s\nTLS: %{time_appconnect}s\nTTFB: %{time_starttransfer}s\nTOTAL: %{time_total}s\n' \
    "$@"
  local status=$?
  if [[ $status -ne 0 ]]; then
    echo "curl exit: $status"
  fi
}

echo
echo "======================================"
echo " PicGo + Cloudflare R2 一键排障"
echo "======================================"

echo
echo "[1/5] 检查 macOS 系统代理"
if [[ -n "$PROXY" ]]; then
  echo "代理来源:$PROXY_SOURCE"
  echo "HTTP 代理:$PROXY"
else
  echo "⚠️ 未检测到已启用的 HTTP/HTTPS 代理"
  if [[ "$SOCKS_ENABLE" == "1" && -n "$SOCKS_HOST" && -n "$SOCKS_PORT" ]]; then
    echo "检测到 SOCKS:socks5://${SOCKS_HOST}:${SOCKS_PORT}"
    echo "PicGo S3 代理字段不能直接照填 SOCKS,请启用 HTTP/mixed 端口。"
  fi
fi

echo
echo "[2/5] 检查代理端口"
if [[ -n "$PROXY" ]]; then
  if nc -z -w 3 "$PROXY_HOST" "$PROXY_PORT" 2>/dev/null; then
    echo "✅ 代理端口可连接:${PROXY_HOST}:${PROXY_PORT}"
  else
    echo "❌ 代理端口无法连接:${PROXY_HOST}:${PROXY_PORT}"
  fi
else
  echo "跳过:没有可用的 HTTP/HTTPS 代理"
fi

check_url "[3/5] 测试 R2 直连" "$R2"

if [[ -n "$PROXY" ]]; then
  check_url "[4/5] 测试 R2 代理链路" -x "$PROXY" "$R2"
else
  echo
  echo "[4/5] 测试 R2 代理链路"
  echo "跳过:没有可用的 HTTP/HTTPS 代理"
fi

check_url "[5/5] 测试 CDN" "$CDN"

echo
echo "======================================"
echo " PicGo 推荐配置"
echo "======================================"
echo "Bucket   : $BUCKET"
echo "Region   : auto"
echo "Endpoint : $R2"
echo "Path     : $UPLOAD_PATH"
echo "Proxy    : ${PROXY:-未检测到,请按当前网络决定是否留空}"
echo "CDN      : $CDN"
echo "ACL      : 不发送;若必须选择则使用 private"
echo
echo "说明:未签名请求的 HTTP 状态不验证 R2 凭据或实际上传权限。"
EOF

chmod +x ~/.local/bin/picgo-r2-check
~/.local/bin/picgo-r2-check

日后运行 ​

直接执行:

bash
~/.local/bin/picgo-r2-check

如果希望在任意目录输入 picgo-r2-check,把目录加入 PATH:

bash
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc

脚本每次从 scutil --proxy 读取当前代理:优先使用已启用的 HTTP 代理,必要时回退到已启用的 HTTPS 代理,因此代理软件切换端口后无需修改脚本。

深入排障 ​

正确理解脚本结果 ​

  • R2 直连慢、代理快:PicGo 中填写脚本输出的 HTTP 代理。
  • R2 直连和代理都失败:检查 DNS、系统时间、代理规则、TLS 拦截和 Cloudflare 服务状态。
  • R2 链路正常但 PicGo 上传失败:检查凭据、bucket、ACL、Endpoint、上传路径和默认上传器。
  • 上传成功但 CDN 无法访问:检查自定义域名绑定、对象路径和 DNS;不要继续调整上传代理。

未签名请求的边界

curl 未携带 AWS Signature。访问 R2 Endpoint 返回 400 只能说明请求已经收到 HTTP 响应、链路基本可达,不能验证 Access Key、Secret Access Key、bucket 权限或 PicGo 实际上传。其他 HTTP 状态也应结合响应时间和 PicGo 签名请求判断。

常见配置错误 ​

代理软件未启动或端口变化

重新运行 scutil --proxy 和一键脚本,以脚本输出为准,不要猜测 7890、7897 或 1080。系统只启用 SOCKS 时,在代理软件中开启 HTTP/mixed 端口。

凭据或权限错误

重新创建仅限 yunshu 的 R2 API Token,更新 PicGo 中的 Access Key ID 和 Secret Access Key。不要用普通 Cloudflare API Token 文本代替 S3 凭据。

ACL 错误

删除 public-read。优先让插件不发送 ACL;插件必须选择时使用 private,公开访问交给 cdn.yunshux.com。

上传器错误

日志必须显示:

text
Uploading... Current uploader is [aws-s3]

如果显示 [picgo-cloud],回到 Amazon S3 设置并设为默认图床。

可选:Local Uploads ​

R2 Local Uploads 在 2026 年仍属于 open beta。它可能改善部分跨区域上传链路,但不是 PicGo 正确上传的前提,也不应作为 30 秒主流程或脚本的依赖。

启用:

bash
npx wrangler r2 bucket local-uploads enable yunshu

停用:

bash
npx wrangler r2 bucket local-uploads disable yunshu

状态检查请使用 Cloudflare 控制台:

text
R2 → yunshu → Settings → Local Uploads

不要使用官方文档未列出的 local-uploads get 子命令。

可选:Wrangler 登录与网络细查 ​

只有管理 Local Uploads 等 R2 功能时才需要 Wrangler 登录:

bash
npx wrangler login
npx wrangler whoami

授权异常时可以重新登录:

bash
npx wrangler logout
npx wrangler login
npx wrangler whoami

如果一键脚本仍无法定位,再按需检查:

bash
dig "<ACCOUNT_ID>.r2.cloudflarestorage.com"
traceroute -n "<ACCOUNT_ID>.r2.cloudflarestorage.com"
env | grep -i proxy

终端中的 HTTP_PROXY、HTTPS_PROXY 和 ALL_PROXY 不一定会被 PicGo GUI 继承,所以 PicGo 需要代理时仍应在插件配置中显式填写。

重装恢复 ​

按以下清单恢复,密钥从安全的密码管理器中读取:

  1. 安装 PicGo。
  2. 安装 picgo-plugin-s3。
  3. 如需 WebP,安装 picgo-plugin-compress-next。
  4. 配置 Bucket yunshu、Region auto 和 R2 Endpoint。
  5. 填入 R2 Access Key ID 与 Secret Access Key。
  6. 恢复上传路径模板。
  7. ACL 设置为不发送;无法留空时使用 private。
  8. 配置图片访问域名或 outputURLPattern。
  9. 运行 scutil --proxy,按当前网络决定是否填写 HTTP 代理。
  10. 配置 WebP quality 80、method 6。
  11. 将 Amazon S3 设为默认图床。
  12. 上传测试图片,验证 PicGo 日志、R2 对象和 CDN URL。
  13. 如有需要,重新安装 ~/.local/bin/picgo-r2-check。

已验证案例与日常速查 ​

一次实际优化记录 ​

同一台设备的一次排障记录中,未配置可用代理时:

text
10:47:19 Uploading...
10:48:05 SUCCESS

本次约耗时 46 秒。将 PicGo 代理更新为当时系统正在使用的:

text
http://127.0.0.1:7897

再次测试:

text
10:54:17 Uploading...
10:54:18 SUCCESS

本次约耗时 1 秒。该结果说明当时的瓶颈在直连网络路径,不能视为其他网络、地区或时间都能达到 1 秒的保证。

日常速查 ​

上传突然变慢或失败时:

  1. 运行 picgo-r2-check。
  2. 代理端口失败:启动代理软件,重新读取系统代理。
  3. R2 直连慢而代理快:更新 PicGo 的 HTTP 代理。
  4. 两条 R2 链路都正常:检查默认上传器、凭据、bucket、ACL 和上传路径。
  5. 上传成功但 URL 无法访问:检查 cdn.yunshux.com 绑定和对象路径。

正常配置的最终目标:

text
上传器:aws-s3
Bucket:yunshu
Endpoint:https://<ACCOUNT_ID>.r2.cloudflarestorage.com
输出 URL:https://cdn.yunshux.com/<上传路径>

基于 VitePress 构建