Shipping LLM features on Cloudflare Workers means you need a clean way to handle cloudflare workers secrets api keys without leaking them to the client or committing them to git. This guide walks through the exact steps to store provider credentials as encrypted secrets, read them in your Worker, and call an LLM provider securely from the edge.
Step 1: Scaffold a Worker for LLM calls
Start with a minimal Worker project. If you don’t have one, use wrangler to init:
npm create cloudflare@latest llm-edge-worker
cd llm-edge-worker
npm install
Pick the “Hello World” Worker (TypeScript) template. The generated src/index.ts exports a fetch handler. That handler is where we’ll read secrets and proxy requests to an LLM provider. Keep the logic thin: parse the incoming request, call the provider using the secret, return the streamed response.
A common mistake is bundling the provider SDK. Most LLM providers expose an OpenAI-compatible REST endpoint, so a plain fetch avoids bloat and keeps cold starts low. If you need streaming, return the upstream ReadableStream directly instead of buffering the whole response.
Project layout
You should end up with:
src/index.ts
wrangler.toml
.dev.vars (gitignored)
The wrangler.toml holds routes and compatibility flags, not keys. Keep it that way.
Step 2: Store provider keys as encrypted secrets
Never paste keys into wrangler.toml or source files. Cloudflare encrypts secrets at rest and injects them as environment variables at runtime. Use the CLI:
wrangler secret put OPENAI_API_KEY
wrangler secret put ANTHROPIC_API_KEY
The command prompts for the value. For production, scope it:
wrangler secret put OPENAI_API_KEY --env production
You can manage cloudflare workers secrets api keys for each provider this way. List what’s stored with:
wrangler secret list
Secrets are write-only; you cannot retrieve the value after setting it. If you lose it, delete and put a new one. Values are capped at 1 KB, which is plenty for bearer tokens. For local development, create a .dev.vars file (gitignored) with the same names—wrangler dev reads it automatically.
CI injection
In pipelines, pipe the value in:
echo "$OPENAI_API_KEY" | wrangler secret put OPENAI_API_KEY --env production
This avoids interactive prompts and keeps the key out of shell history if you use a masked CI variable.
Step 3: Read secrets inside the Worker handler
Define an Env interface and reference env.KEY. Here’s a minimal handler that calls OpenAI’s chat completions:
interface Env {
OPENAI_API_KEY: string;
}
export default {
async fetch(req: Request, env: Env): Promise<Response> {
const payload = {
model: "gpt-4o-mini",
messages: [{ role: "user", content: "Say hi." }],
stream: true,
};
const upstream = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
"content-type": "application/json",
authorization: `Bearer ${env.OPENAI_API_KEY}`,
},
body: JSON.stringify(payload),
});
return new Response(upstream.body, {
headers: { "content-type": "text/event-stream" },
status: upstream.status,
});
},
};
The secret never touches the response. If you need multiple providers, add fields to Env and branch on the request path or header. Secrets are per-environment, so env in staging differs from production.
Step 4: Keep keys out of logs and error responses
It’s tempting to log env during debugging. Don’t. Cloudflare streams Worker logs to the dashboard; a single console.log(env) exposes your cloudflare workers secrets api keys to anyone with log access. Wrap upstream calls in try/catch and return generic errors:
try {
const res = await fetch(/* ... */);
if (!res.ok) return new Response("Provider error", { status: 502 });
return res;
} catch (e) {
return new Response("Upstream failed", { status: 503 });
}
Also strip authorization headers from any incoming request before forwarding—clients must not send their own provider keys through your Worker. If you need to pass a user token to a gateway, use a separate signed header and validate it before mapping to the secret.
Step 5: Handle multiple providers and fallback
When you run several cloudflare workers secrets api keys, you’ll want fallback logic if one provider rate-limits. A naive approach:
const providers = [
{ url: "https://api.openai.com/v1/chat/completions", key: env.OPENAI_API_KEY },
{ url: "https://api.anthropic.com/v1/messages", key: env.ANTHROPIC_API_KEY },
];
for (const p of providers) {
const r = await fetch(p.url, {
method: "POST",
headers: { authorization: `Bearer ${p.key}`, "content-type": "application/json" },
body: req.body,
});
if (r.status !== 429 && r.status !== 503) return r;
}
return new Response("All providers down", { status: 503 });
If you’d rather not juggle a dozen cloudflare workers secrets api keys for each LLM vendor, a gateway such as n4n.ai consolidates them behind one OpenAI-compatible endpoint that addresses 240+ models and applies automatic fallback when a provider is degraded. You’d then store a single N4N_API_KEY secret and forward requests with provider routing hints. That reduces secret sprawl and per-provider quota handling.
Cache-control hints
Providers support prompt caching via headers. Forward cache-control from the client only if you trust it; otherwise set your own x-cache-ttl based on the route. The gateway or provider honors those hints, but your Worker should sanitize them.
Step 6: Test locally with .dev.vars
Create .dev.vars in the project root:
OPENAI_API_KEY=sk-test-123
ANTHROPIC_API_KEY=sk-ant-test-456
Run wrangler dev. The Worker reads these as env.OPENAI_API_KEY. Confirm with a curl to http://localhost:8787. Never commit .dev.vars—add it to .gitignore. For CI, inject secrets via the platform’s secret store, not the file.
Local verification
curl -X POST http://localhost:8787 -H "content-type: application/json" \
-d '{"prompt":"hello"}' --verbose 2>&1 | grep -i "authorization"
You should see the header sent upstream in the verbose output, but the key value must match your .dev.vars, not anything from the client.
Step 7: Deploy and verify success
Deploy:
wrangler deploy --env production
Verification should prove two things: the Worker can authenticate, and secrets aren’t leaked. Hit the endpoint:
curl -s https://your-worker.workers.dev/ -H "content-type: application/json" \
-d '{"prompt":"ping"}' | head -c 200
Expect a valid model response. Then check wrangler tail and confirm no authorization header or key string appears in logs. Finally, run a negative test: temporarily delete a secret and confirm the Worker returns your generic 502, not a stack trace with the key.
Automated check
Add a smoke test in CI that deploys to a preview environment and asserts the response status is 200 and the body does not contain sk-. This catches accidental logging regressions.
Step 8: Rotate and audit secrets
Providers leak. Rotate quarterly or on suspicion. Delete and re-put:
wrangler secret delete OPENAI_API_KEY
wrangler secret put OPENAI_API_KEY --env production
Audit with wrangler secret list per environment. If you used the gateway approach, rotation is a single key. For direct providers, keep an inventory of which cloudflare workers secrets api keys map to which Worker and environment.
Secrets in Cloudflare Workers are immutable per deploy; a put triggers a new deployment. That’s the right trade-off—no hot-reload of credentials, full version traceability. Treat each secret as a revocable capability, and your edge LLM integration stays clean.