Secure Storage for Claude API Keys in Production
Storing a Claude API key securely in production means keeping it out of source control, out of client-side code, and out of plaintext config files — and instead loading it at runtime from a dedicated secrets manager or environment variable injection system with tightly scoped access. The short version: never hardcode it, never ship it to a browser, rotate it on a schedule, and give each service or team its own key so you can revoke one without breaking everything else.
That answers the "where do I put it" question. The harder part is doing this consistently across environments, teams, and CI pipelines without slowing anyone down. Below is a practical breakdown of what actually works.
Where NOT to put your key
Before the "right" way, a quick list of the mistakes that cause leaked keys in the wild:
- Committed to git — even in a private repo, keys end up in history forever, and private repos get forked, cloned, or exposed.
- Hardcoded in client-side JavaScript — anything shipped to a browser is public, full stop. Anyone can open devtools and read it.
- In Slack messages or shared docs — convenient for a quick test, terrible for audit trails.
- In Dockerfiles with
ENVbaked at build time — the key ends up in the image layers, which get pushed to a registry. - In CI logs — if your build script echoes environment variables for debugging, mask them or you'll find your key in a log retention bucket.
If any of these sound familiar, treat the key as compromised and rotate it.
The baseline: environment variables
For most teams, environment variables injected at runtime (not build time) are the minimum acceptable standard:
export CLAUDE_API_KEY=sk-ant-xxxxxxxxxxxxxxxx
const apiKey = process.env.CLAUDE_API_KEY;
if (!apiKey) {
throw new Error("CLAUDE_API_KEY is not set");
}
This works fine for a single server or container, but it has limits: env vars are visible to any process running in the same container, they're easy to accidentally log, and they don't give you rotation, scoping, or an audit trail on their own.
The production standard: a secrets manager
For anything running in production with multiple services or team members, use a dedicated secrets manager — AWS Secrets Manager, GCP Secret Manager, HashiCorp Vault, or your platform's built-in equivalent (Vercel, Fly.io, Render, and similar all have first-class secret storage). The pattern is the same regardless of provider:
- Store the key encrypted at rest in the secrets manager.
- Grant your application's runtime role read access via IAM/policy, not a static credential.
- Fetch the secret at process startup and hold it in memory — never write it to disk.
- Set up automatic rotation if your provider supports it, or a manual rotation runbook if it doesn't.
// Example: fetching from AWS Secrets Manager at startup
import { SecretsManagerClient, GetSecretValueCommand } from "@aws-sdk/client-secrets-manager";
const client = new SecretsManagerClient({ region: "eu-west-1" });
const response = await client.send(
new GetSecretValueCommand({ SecretId: "prod/claude-api-key" })
);
const apiKey = response.SecretString;
This removes the key from your deployment config entirely — your CI/CD pipeline never sees the plaintext value, and access is governed by the same IAM policies as the rest of your infrastructure.
Scope keys per service, not one key for everything
A single shared API key used by every microservice, cron job, and internal tool is a single point of failure. If one service gets compromised, you have to rotate the key everywhere simultaneously, which usually means downtime.
Instead:
- Issue a separate key per service or team where the provider supports it.
- Name keys by purpose (
checkout-service,support-bot,internal-cli) so you know what breaks if you revoke one. - Set spend or rate limits per key if your provider offers them, so a bug in one service can't exhaust your entire budget.
This is one of the reasons teams put Claude behind an internal gateway rather than distributing the raw Anthropic key directly. SubToAPI, for example, issues separate sub_live_... application keys per app or environment from a single underlying Claude subscription, so you can revoke a staging key without touching production, and see usage broken down per key in the dashboard. You generate keys after signup and use them exactly like a normal bearer token:
curl https://api.subtoapi.app/v1/messages \
-H "Authorization: Bearer $SUBTOAPI_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-5",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "Summarize this ticket."}]
}'
Full request/response details are in the API docs, and the quickstart covers getting a key from a fresh signup to a working call in a few minutes.
Rotation without downtime
Rotating a key shouldn't require a deploy. The standard pattern:
- Generate a new key alongside the old one (most providers support multiple active keys).
- Update your secrets manager with the new value.
- Restart or let your services pick up the new value on their next secret refresh cycle.
- Confirm traffic is flowing on the new key (check usage metadata if your provider exposes it).
- Revoke the old key.
Do this on a fixed schedule — quarterly is a reasonable default for most teams — and immediately whenever someone with key access leaves, or a key is suspected to have leaked (e.g., found in a log, a public repo, or a shared screen).
Don't forget local development
Local .env files are fine for development as long as:
.envis in.gitignorefrom day one, not added after the first commit.- Developers use their own personal or low-limit keys, not the production key copy-pasted into everyone's laptop.
- You use a tool like
direnvor your editor's secret integration instead of pasting keys into shell history.
Quick checklist
- [ ] Key is not in git history, current or past
- [ ] Key is not in any client-side bundle
- [ ] Key is loaded from a secrets manager or injected env var at runtime
- [ ] Each service/environment has its own scoped key
- [ ] Rotation is scheduled, not ad hoc
- [ ] CI logs mask secret values
- [ ] You have a revoke runbook for a suspected leak
Questions
Is it safe to put a Claude API key in a .env file? Yes for local development, as long as .env is gitignored from the first commit. For production, prefer a secrets manager over a .env file on a server, since secrets managers give you access control, rotation, and an audit trail.
Can I use the same API key across all my environments? You can, but you shouldn't. Separate keys per environment (dev/staging/prod) and per service let you revoke or rate-limit one without affecting the others, and make it much easier to trace where a spike in usage or a leak came from.
How often should I rotate a production API key? A fixed schedule (quarterly is common) plus immediate rotation whenever a key may have been exposed — found in a log, committed to git, or accessible to someone who's left the team. Automate it where your provider supports programmatic key creation and revocation.