晚上修 webhook 最怕这种情况:wrangler dev 本地一切正常,preview URL 也能调通,合并到 main 后生产环境开始返回 401、500,日志里只有一句 missing API key。代码看起来没动,密钥也明明在本机文件里,真正出错的位置通常在环境选择和 secret 名称。
Cloudflare Workers 的 secret 可以像环境变量一样从 env 里读,但它不是一份全局配置。Local preview、Wrangler environment、已部署 Worker 和 GitHub Actions 是四个入口。排查时不要先改业务逻辑,先把这四处对齐。
preview、production、environment 到底差在哪?
先把词分开,很多误判都来自把 preview 当成 production 的影子。
| 场景 | 常见命令或入口 | secret 来源 | 最容易错在哪里 |
|---|---|---|---|
| 本地开发 | npx wrangler dev | .dev.vars 或 .env | 本地有值,线上没有值 |
| 本地指定环境 | npx wrangler dev --env preview | .dev.vars.preview、.env.preview 等 | 文件名存在后,加载顺序和默认文件不同 |
| 预览部署 | npx wrangler deploy --env preview | preview environment 对应 Worker 的 secret | 写到了顶层 Worker,preview Worker 读不到 |
| 生产部署 | npx wrangler deploy --env production 或不带 --env 的顶层部署 | production environment 或顶层 Worker 的 secret | 团队对「production」到底是 env 还是顶层 Worker 没约定 |
Cloudflare 的 Wrangler environment 会让同一个项目拥有不同配置;例如顶层 name 是 my-api,env.preview 可能部署成 my-api-preview。这意味着你不是给「一个 Worker 的两个模式」塞 secret,而是在给不同 Worker 分别配置 secret。
快速判断:代码问题还是配置问题?
先找一个只读的健康检查,不要用真实支付、发信或扣费接口验证。比如给 Hono、itty-router 或原生 Worker 临时加一个只返回状态的路径,确认 secret 名称存在,不返回 secret 值。
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
if (url.pathname === "/__health/secrets") {
return Response.json({
hasStripeKey: Boolean(env.STRIPE_SECRET_KEY),
hasWebhookSecret: Boolean(env.STRIPE_WEBHOOK_SECRET),
runtime: env.APP_ENV ?? "unknown"
});
}
return new Response("ok");
}
};
如果 preview 返回 true、production 返回 false,代码路径大概率没坏,问题在 secret 来源。如果两边都是 false,再看 TypeScript 类型、binding 名称和 wrangler.jsonc 里的 secrets.required。不要把 secret 值打印到日志里,日志系统、Sentry、Axiom 或 PostHog 都可能把它保存下来。
secret 名称要怎么逐个对齐?
先固定一组名字,别同时出现 OPENAI_KEY、OPENAI_API_KEY、OPENAI_SECRET。独立开发者项目里,我会把第三方平台、用途和环境无关信息写进名称,环境差异只体现在值上。
STRIPE_SECRET_KEY
STRIPE_WEBHOOK_SECRET
RESEND_API_KEY
OPENAI_API_KEY
DATABASE_URL
然后按同一个名字检查四处:
# 本地 preview 文件
grep -n "STRIPE_SECRET_KEY" .dev.vars .dev.vars.preview .env .env.preview 2>/dev/null
# 本地用 preview 环境跑
npx wrangler dev --env preview
# 给 preview environment 写 secret
npx wrangler secret put STRIPE_SECRET_KEY --env preview
# 给 production environment 写 secret
npx wrangler secret put STRIPE_SECRET_KEY --env production
如果你的生产就是顶层 Worker,没有在 wrangler.jsonc 里定义 env.production,生产 secret 命令应是不带环境的:
npx wrangler secret put STRIPE_SECRET_KEY
npx wrangler deploy
如果团队已经定义了 env.production,那就统一带上环境:
npx wrangler secret put STRIPE_SECRET_KEY --env production
npx wrangler deploy --env production
两种方式不要混着用。混用后最常见的现场是:Dashboard 里顶层 Worker 有 secret,my-api-production 没有;或者 CI 一直部署 --env production,你却在本机给顶层 Worker put 了 secret。
wrangler.jsonc 里哪些字段不能指望继承?
Cloudflare 文档把 bindings、环境变量和 secrets 归到 environment 级别;这些配置不能靠「顶层有一份」就默认带到每个 environment。vars 也是同理,env.production.vars 需要自己写。
一个更稳的 wrangler.jsonc 可以这样收口:
{
"$schema": "./node_modules/wrangler/config-schema.json",
"name": "my-saas-api",
"main": "src/index.ts",
"compatibility_date": "2026-03-20",
"vars": {
"APP_ENV": "preview",
"API_HOST": "https://api-preview.example.com"
},
"secrets": {
"required": [
"STRIPE_SECRET_KEY",
"STRIPE_WEBHOOK_SECRET",
"RESEND_API_KEY"
]
},
"env": {
"production": {
"name": "my-saas-api-production",
"vars": {
"APP_ENV": "production",
"API_HOST": "https://api.example.com"
},
"routes": [
{
"pattern": "api.example.com/*",
"zone_name": "example.com"
}
],
"secrets": {
"required": [
"STRIPE_SECRET_KEY",
"STRIPE_WEBHOOK_SECRET",
"RESEND_API_KEY"
]
}
},
"preview": {
"name": "my-saas-api-preview",
"vars": {
"APP_ENV": "preview",
"API_HOST": "https://api-preview.example.com"
},
"secrets": {
"required": [
"STRIPE_SECRET_KEY",
"STRIPE_WEBHOOK_SECRET",
"RESEND_API_KEY"
]
}
}
}
}
这里有三个取舍。第一,vars 只放不敏感配置,API key、数据库密码、webhook signing secret 都走 secret。第二,APP_ENV 可以是普通变量,因为它只是帮助日志和健康检查判断当前跑在哪个环境,不是凭据。第三,secrets.required 对 named environment 不能只依赖顶层配置,env.production 和 env.preview 里也要写清必需 secret 名称。
GitHub Actions 部署前怎么验生产 secret?
CI 里最怕「deploy 成功,但生产第一次真实请求才发现没 secret」。可以在 workflow 里把部署和 smoke test 分开。smoke test 只测是否能读到必需名字,不调用真实扣费链路。
name: Deploy Worker
on:
push:
branches:
- main
permissions:
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: 22
cache: npm
- run: npm ci
- run: npx wrangler deploy --env production
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }}
- run: |
curl --fail --silent --show-error \
https://api.example.com/__health/secrets
如果你要在 CI 同步上传 secret 文件,Cloudflare 支持 --secrets-file,但这会把密钥管理压力转到 GitHub secret、环境审批和日志脱敏上。小团队更常见的做法是:密钥轮换时手动 wrangler secret put 到目标 environment,日常 CI 只负责部署代码。
跨地区维护 Cloudflare Dashboard、GitHub Actions 日志和 Wrangler 登录时,最烦的是偶发加载失败混进真实配置问题。需要远程处理生产事故时,可以准备一条 海外服务跑 GitHub Actions / Cloudflare 的稳定线路,但它只能减少排查入口抖动,不能替代 secret 权限、环境命名和部署验证。
改完后用哪些命令验收?
先在本地确认 preview 环境读到的名称:
npx wrangler dev --env preview
curl http://localhost:8787/__health/secrets
再部署 preview,不碰生产流量:
npx wrangler secret put STRIPE_SECRET_KEY --env preview
npx wrangler secret put STRIPE_WEBHOOK_SECRET --env preview
npx wrangler deploy --env preview
curl https://my-saas-api-preview.<your-subdomain>.workers.dev/__health/secrets
最后处理 production:
npx wrangler secret put STRIPE_SECRET_KEY --env production
npx wrangler secret put STRIPE_WEBHOOK_SECRET --env production
npx wrangler deploy --env production
curl --fail https://api.example.com/__health/secrets
如果你用的是顶层 Worker 做生产,把上面 production 命令里的 --env production 去掉。验收记录里只写四件事:Worker 名称、Wrangler 命令、secret 名称、健康检查返回时间。不要记录 secret 值,也不要截图 Dashboard 里带有敏感字段的页面。
哪些场景要停下来另查?
这篇只处理 Workers secret 在 preview、production、Wrangler environment 之间不一致的问题。遇到下列情况时,排查重点要从 secret 名称切到部署、权限或代码路径:
| 现象 | 更可能的问题 | 下一步 |
|---|---|---|
| secret 存在,但 Stripe 返回 401 | key 值过期或 test/live key 用反 | 去 Stripe Dashboard 看 key 类型和最近轮换时间 |
| production 偶发 500,健康检查一直正常 | 上游 API、数据库、队列或限流 | 看 request id、上游状态码和重试日志 |
本地 .env 读到了,.dev.vars 不生效 | 本地加载规则冲突 | 二选一使用 .dev.vars 或 .env,别两套一起维护 |
| preview 读错数据库 | D1、KV、R2、Hyperdrive binding 配错 | 检查 env.preview 下每个 binding 的 id |
| Dashboard 看到 secret,Wrangler 仍报缺失 | 目标 Worker 或 account 错了 | 核对 account_id、Worker 名称和 --env |
还有一个未测范围:Workers for Platforms、Secrets Store beta、Terraform 管理的 Cloudflare 资源,密钥来源会多一层。如果你的团队已经用 IaC 管理 Cloudflare,不要手动在 Dashboard 里临时加 secret,先看仓库里的资源定义,否则下一次 apply 可能把手改内容覆盖掉。