Skip to content

Secret Rotation

SecretRotation FrequencyImpact of Rotation
JWT_SECRETEvery 90 daysAll active sessions invalidated (or none with dual-secret)
APP_ENCRYPTION_KEYAnnuallyRequires data re-encryption migration
MFA_ENCRYPTION_KEYAnnuallyRequires re-encryption migration; users locked out if skipped
ENROLLMENT_KEY_PEPPERAnnuallyAll unexpired enrollment keys invalidated
MFA_RECOVERY_CODE_PEPPERAnnuallyAll existing MFA recovery codes invalidated
AGENT_ENROLLMENT_SECRETEvery 90 daysOnly affects new enrollments
SESSION_SECRETEvery 90 daysAll active sessions invalidated
POSTGRES_PASSWORDEvery 90 daysRequires coordinated DB + app restart
REDIS_URL credentialsEvery 90 daysBrief reconnection
S3_ACCESS_KEY / S3_SECRET_KEYEvery 90 daysNone
CLOUDFLARE_API_TOKENEvery 90 daysNone (existing certs unaffected)
TURN_SECRETEvery 90 daysActive remote sessions may drop
METRICS_SCRAPE_TOKENEvery 180 daysBrief gap in metrics collection
RESEND_API_KEY / SMTP passwordPer provider policyBrief notification gap
Twilio credentialsPer provider policyBrief SMS notification gap
ANTHROPIC_API_KEYPer provider policyAI assistant unavailable
User API keysUser responsibilityImmediate
  1. Generate a new secret:

    Terminal window
    openssl rand -base64 64
  2. Update .env.prod with the new JWT_SECRET

  3. Restart the API:

    Terminal window
    docker compose -f docker/docker-compose.prod.yml restart api
  4. All existing JWTs become invalid — users will need to log in again

  1. Generate a new key:

    Terminal window
    openssl rand -hex 32
  2. Record the old key securely — you need it for the re-encryption migration

  3. Run the re-encryption migration:

    Terminal window
    OLD_ENCRYPTION_KEY=<old-key> \
    APP_ENCRYPTION_KEY=<new-key> \
    npx tsx scripts/re-encrypt-secrets.ts
  4. Verify decryption of several records with the new key

  5. Update APP_ENCRYPTION_KEY in .env.prod to the new key

  6. Restart the API:

    Terminal window
    docker compose -f docker/docker-compose.prod.yml restart api
  1. Generate a new key:

    Terminal window
    openssl rand -hex 32
  2. Run a re-encryption migration targeting MFA secret columns (same approach as APP_ENCRYPTION_KEY)

  3. Update MFA_ENCRYPTION_KEY in .env.prod

  4. Restart the API:

    Terminal window
    docker compose -f docker/docker-compose.prod.yml restart api
  1. Generate a new pepper:

    Terminal window
    openssl rand -hex 32
  2. Update ENROLLMENT_KEY_PEPPER in .env.prod

  3. Restart the API:

    Terminal window
    docker compose -f docker/docker-compose.prod.yml restart api
  4. Regenerate enrollment keys and redistribute to anyone deploying new agents

  1. Generate a new pepper:

    Terminal window
    openssl rand -hex 32
  2. Update MFA_RECOVERY_CODE_PEPPER in .env.prod

  3. Restart the API:

    Terminal window
    docker compose -f docker/docker-compose.prod.yml restart api
  4. Notify users to regenerate their MFA recovery codes via Settings, or trigger a bulk regeneration as an admin

  1. Generate a new secret:

    Terminal window
    openssl rand -hex 32
  2. Update .env.prod with the new AGENT_ENROLLMENT_SECRET

  3. Restart the API

  4. Update any deployment scripts or MDM policies with the new secret

  1. Generate a new secret:

    Terminal window
    openssl rand -base64 64
  2. Update SESSION_SECRET in .env.prod

  3. Restart the API:

    Terminal window
    docker compose -f docker/docker-compose.prod.yml restart api
  4. All active sessions are invalidated — users must log in again

  1. Generate a new password:

    Terminal window
    openssl rand -base64 24 | tr -d '/+='
  2. Update the password in PostgreSQL:

    Terminal window
    docker compose -f docker/docker-compose.prod.yml exec postgres \
    psql -U breeze -c "ALTER USER breeze PASSWORD 'new-password';"
  3. Update POSTGRES_PASSWORD and DATABASE_URL in .env.prod

  4. Restart the API:

    Terminal window
    docker compose -f docker/docker-compose.prod.yml restart api
  1. Set or update the Redis password:

    Terminal window
    redis-cli CONFIG SET requirepass "new-redis-password"
    redis-cli -a "new-redis-password" CONFIG REWRITE
  2. Update REDIS_URL in .env.prod:

    REDIS_URL=redis://:new-redis-password@localhost:6379
  3. Restart the API and worker processes:

    Terminal window
    docker compose -f docker/docker-compose.prod.yml restart api worker
  1. Create a new access key pair in your S3/R2/MinIO console

  2. Update S3_ACCESS_KEY and S3_SECRET_KEY in .env.prod

  3. Restart the API:

    Terminal window
    docker compose -f docker/docker-compose.prod.yml restart api
  4. Verify by uploading and downloading a test file

  5. Delete the old access key in your storage provider’s console

  1. In the Cloudflare dashboard, go to My Profile > API Tokens

  2. Create a new token with the same permissions (Client Certificates: Edit for the relevant zone)

  3. Update CLOUDFLARE_API_TOKEN in .env.prod

  4. Restart the API:

    Terminal window
    docker compose -f docker/docker-compose.prod.yml restart api
  5. Verify by triggering a certificate enrollment or renewal

  6. Delete the old token in the Cloudflare dashboard

  1. Generate a new secret:

    Terminal window
    openssl rand -hex 32
  2. Update TURN_SECRET in both the TURN server configuration and .env.prod

  3. Restart the TURN server and the API:

    Terminal window
    docker compose -f docker/docker-compose.prod.yml restart api turn
  1. Generate a new token:

    Terminal window
    openssl rand -hex 32
  2. Update METRICS_SCRAPE_TOKEN in .env.prod

  3. Update the token file:

    Terminal window
    echo "new-token" > monitoring/secrets/metrics_scrape_token
    chmod 600 monitoring/secrets/metrics_scrape_token
  4. Restart API and Prometheus:

    Terminal window
    docker compose -f docker/docker-compose.prod.yml restart api prometheus

Covers RESEND_API_KEY, SMTP_USER / SMTP_PASS, and MAILGUN_API_KEY.

  1. Rotate the credential in the provider’s dashboard (Resend, Mailgun, or your SMTP provider)

  2. Update the corresponding env var(s) in .env.prod

  3. Restart the API:

    Terminal window
    docker compose -f docker/docker-compose.prod.yml restart api
  4. Verify by triggering a test notification (e.g., password reset email)

Rotating SMS Provider Credentials (Twilio)

Section titled “Rotating SMS Provider Credentials (Twilio)”

Covers TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN.

  1. In the Twilio console, generate a new Auth Token (or create a new API key pair)

  2. Update TWILIO_ACCOUNT_SID and/or TWILIO_AUTH_TOKEN in .env.prod

  3. Restart the API:

    Terminal window
    docker compose -f docker/docker-compose.prod.yml restart api
  4. Verify by triggering a test SMS alert

  5. Revoke the old credential in the Twilio console

  1. Generate a new API key in the Anthropic console

  2. Update ANTHROPIC_API_KEY in .env.prod

  3. Restart the API:

    Terminal window
    docker compose -f docker/docker-compose.prod.yml restart api
  4. Revoke the old key in the Anthropic console

User-facing API keys are managed through the application, not through environment variables.

  • Individual rotation: Users regenerate their API key via Settings > API Keys > Regenerate, or via POST /api/v1/api-keys
  • Admin revocation: Admins can revoke any API key via the admin panel or DELETE /api/v1/api-keys/:id
  • Bulk revocation: In a security incident, an admin can revoke all API keys; users must generate new ones

No environment variable changes or restarts are required. The old key is immediately invalidated upon regeneration.

If you suspect any secret has been compromised:

  1. Rotate the compromised secret immediately using the procedures above
  2. Check audit logs for unauthorized access during the exposure window
  3. Rotate related secrets — if JWT_SECRET was compromised, also rotate SESSION_SECRET; if database credentials leaked, also rotate APP_ENCRYPTION_KEY in case encrypted data was exfiltrated
  4. Notify affected users if their data may have been accessed
  5. File a post-incident report documenting the timeline, impact, and remediation