A Practical Guide to Secret Rotation
You get a PagerDuty alert at 2 AM. Your Stripe webhook secret was exposed in a public repo. You need to rotate it immediately. Do you know, right now, every platform and environment where that secret is deployed?
Most teams don't. Secret rotation is one of those things everyone agrees they should do regularly but almost nobody does until forced. And when you're forced, it's always urgent, always stressful, and always reveals how little visibility you have into where your secrets actually live.
Why rotate secrets at all
The case for rotation is simple: every secret has a nonzero probability of being compromised. The longer a secret lives, the more opportunities there are for it to leak through git history, log files, developer machines, third-party breaches, or plain human error.
Regular rotation limits the blast radius. A database password that's rotated monthly means a leak from two months ago is already invalidated. A key that hasn't been rotated in eighteen months means an eighteen-month window of potential exposure.
Beyond risk reduction, some compliance frameworks (SOC 2, PCI DSS, HIPAA) require documented rotation policies. Even if you're not pursuing certification today, building the habit early saves a painful retrofit later.
The rotation checklist
Every secret rotation follows the same basic steps. The complexity comes from doing them consistently across multiple platforms.
1. Generate the new credential
Go to the provider (Stripe, your database, AWS IAM, etc.) and create a new key. Don't revoke the old one yet.
2. Deploy the new value everywhere
This is where most rotations fail. You update production but forget staging. You update Vercel but forget Convex. You update the platform but forget to update the CI/CD pipeline.
You need a complete inventory of every location where the secret is used:
- Platform environments -- Vercel (development, preview, production), Railway, Convex, etc.
- CI/CD pipelines -- GitHub Actions secrets, GitLab CI variables
- Local developer machines -- everyone's
.envfiles - Infrastructure -- Kubernetes secrets, AWS Parameter Store, Terraform state
3. Verify the new credential works
After deploying, verify that each service is functioning correctly with the new value. Check API responses, webhook deliveries, database connections. Don't skip this -- silent auth failures are worse than loud ones.
4. Revoke the old credential
Only after you've confirmed the new value works everywhere should you revoke the old one. This two-phase approach (deploy new, then revoke old) avoids downtime from premature revocation.
5. Document what you did
Record which secret was rotated, when, by whom, and which platforms were updated. This is both a compliance requirement and a debugging aid.
Where teams get stuck
No single inventory of secrets
The first question in any rotation is "where is this secret used?" If the answer requires checking three dashboards, a CI/CD config, and asking your teammates, you've already lost time.
A dotenvy.yaml config gives you that inventory:
secrets:
- STRIPE_SECRET_KEY
- STRIPE_WEBHOOK_SECRET
- DATABASE_URL
- RESEND_API_KEY
- CLERK_SECRET_KEY
targets:
vercel:
type: vercel
project: my-app
mapping:
development: test
preview: test
production: live
convex:
type: convex
deployment: my-app
mapping:
default: test
railway:
type: railway
project: my-workers
mapping:
production: liveWhen you need to rotate STRIPE_SECRET_KEY, the config tells you immediately: it's deployed to Vercel (three environments), Convex (one deployment), and Railway (one service). No guessing.
The multi-platform update problem
Even with a clear inventory, updating a secret across multiple platforms means logging into each dashboard separately. For a single secret across three platforms and two environments, that's up to six manual updates.
dotenvy collapses this to two commands:
# Rotate the test-mode key
dotenvy set STRIPE_SECRET_KEY=sk_test_new_xxx
# Rotate the live-mode key
dotenvy set STRIPE_SECRET_KEY=sk_live_new_xxx --env liveEach set command updates your local file and pushes to every target mapped to that environment. All platforms are updated in one operation.
Forgetting to rotate test credentials
Production rotations get attention because outages are visible. But test credentials matter too. If your test-mode Stripe key leaks, an attacker can read your test data, understand your integration patterns, and potentially use that knowledge to target your production setup.
Rotate both environments:
dotenvy set STRIPE_SECRET_KEY=sk_test_rotated --env test
dotenvy set STRIPE_SECRET_KEY=sk_live_rotated --env liveNo verification step
After rotating, you need to know the new value actually took effect on every platform. A dry-run comparison shows you what's currently deployed:
dotenvy sync test --dry-run
dotenvy sync live --dry-runIf everything shows "synced," the rotation is complete. If anything shows "drifted," you have a platform that didn't get the update.
Building a rotation schedule
Not every secret needs the same rotation frequency. A reasonable starting point:
Rotate immediately on exposure:
- Any secret found in git history, logs, or public channels. No exceptions.
Rotate quarterly:
- API keys for third-party services (Stripe, Resend, Twilio)
- Webhook signing secrets
- OAuth client secrets
Rotate when team membership changes:
- Any secret that a departing team member had access to
- Shared service account credentials
Rotate annually (at minimum):
- Database passwords
- Encryption keys (with re-encryption of stored data if needed)
- Infrastructure credentials (AWS, GCP, Azure)
The specific intervals matter less than having intervals at all. A quarterly rotation that actually happens is better than a monthly policy that nobody follows.
Automating the workflow
A manual rotation process that takes thirty minutes and touches six platforms will get skipped. The goal is to reduce the friction until rotation is a two-minute task.
Here's a practical rotation workflow:
# 1. Generate new credential at the provider (manual step)
# 2. Update test environments
dotenvy set STRIPE_SECRET_KEY=sk_test_new_value
# 3. Verify test environments
dotenvy sync test --dry-run
# Confirm all targets show "synced"
# 4. Update production environments
dotenvy set STRIPE_SECRET_KEY=sk_live_new_value --env live
# 5. Verify production environments
dotenvy sync live --dry-run
# Confirm all targets show "synced"
# 6. Revoke old credential at the provider (manual step)Steps 1 and 6 require visiting the provider's dashboard -- there's no way around that. But steps 2 through 5 are fully handled by dotenvy. The entire rotation touches your terminal and the provider dashboard. No other dashboards involved.
The cost of not rotating
Every unrotated secret is a bet that it hasn't been compromised. The longer the interval, the worse the odds. And when a breach happens, the investigation always reveals the same thing: the compromised credential was set fourteen months ago and never rotated.
Regular rotation is cheap insurance. Especially when the sync step is automated, the only real cost is generating a new credential and revoking the old one. That's a five-minute task per secret, per quarter. Compare that to the cost of a breach.
Start with an inventory. Know where your secrets live. Rotate the most critical ones first. Automate the deployment step so there's no excuse to skip it.
curl -fsSL https://dotenvy.dev/install.sh | sh