# Rotate secrets on a schedule

This guide is the how-to; see [Secret rotation](/docs/concepts/rotation) for how it works and what
trust boundary it involves. Rotation is admin-managed and configured per secret. The step that makes
it possible — wrapping the environment's data key to your organization's rotator key — happens in
your browser or CLI, never on a server.

## 1. Pick what to rotate

| You want to rotate | Kind | You also need |
| --- | --- | --- |
| A value only your own code checks (API key, webhook secret) | `generated` | nothing |
| An existing Postgres role's password | `postgres` | a registered Postgres target |
| An existing MySQL/MariaDB account's password | `mysql` | a registered MySQL target |
| An existing Redis ACL user's password | `redis` | a registered Redis target |

The secret must already exist — rotation replaces a value, it doesn't create one. For the database
kinds you need a target registered for [temporary access](/docs/concepts/temporary-access); rotation
reuses the same connection details and admin credential:

```bash
seekrit pg target add --name prod-db --host db.example.com --database app \
  --admin-url "postgres://admin:…@db.example.com:5432/app"
```

## 2. Enable rotation

**With the CLI** — a generated value, rotated every 30 days:

```bash
seekrit rotation enable API_SIGNING_KEY --app web --env production \
  --kind generated --every 30d --now
```

An existing Postgres role, re-keyed weekly:

```bash
seekrit rotation enable DATABASE_PASSWORD --app web --env production \
  --kind postgres --target prod-db --username app_user --every 7d --now
```

`--now` rotates immediately as well as on the schedule, which is the fastest way to confirm the
target and account name are right — a misconfigured policy fails loudly here instead of quietly in
an email tomorrow.

**In the web dashboard:** open the environment → **Rotation** → **configure rotation**, pick the
secret, the kind, and a cadence. For a database kind, pick the target and type the account name.

The first time you enable rotation in an environment, your client unwraps that environment's data
key and re-wraps it to the rotator key. That is the grant the broker needs to write a new value —
you will be asked to unlock if your key is locked. A second rotating secret in the same environment
needs no key access at all.

> **Note:** `--username` names an account that **already exists**. Rotation changes its password in place; it never creates or drops the account, so grants and ownership survive. Make sure your registered target's admin credential can `ALTER` that account.

## 3. Live with it

```bash
seekrit rotation list                     # every policy: cadence, status, next run
seekrit rotation show DATABASE_PASSWORD   # one policy in full, including the last failure
seekrit rotation rotate DATABASE_PASSWORD # rotate now (e.g. after a suspected exposure)
```

Consumers need no changes — the next resolve returns the new value:

```bash
seekrit run --app web --env production -- ./server
```

Every rotation appends a secret version, so `seekrit secrets get DATABASE_PASSWORD` always decrypts
the current one, and the version history shows which versions rotation produced.

## 4. Pause, fix, resume

If a rotation fails — the database is unreachable, the account was renamed, the admin credential
lost its privileges — seekrit records the error, emails your admins, and retries with a backoff.
After five consecutive failures the policy stops retrying and shows as `failed`.

```bash
seekrit rotation show DATABASE_PASSWORD   # `lastError` says what went wrong
seekrit rotation pause DATABASE_PASSWORD  # stop rotating while you work on it
seekrit rotation resume DATABASE_PASSWORD # resume, clearing the failure streak
seekrit rotation set-interval DATABASE_PASSWORD --every 30d
```

The dashboard's rotation card shows the same state inline, with the last error and per-policy
rotate / pause / disable controls.

> **Warning:** A rotation re-keys your database before it stores the new value. If the write-back fails in between, consumers hold a password that no longer works until the retry succeeds — seekrit emails you and retries with a fresh value automatically. Pick a cadence your deployment can absorb, and make sure your apps can re-resolve (or be restarted) after a rotation rather than caching a credential for their whole lifetime.

## Turning it off

```bash
seekrit rotation disable DATABASE_PASSWORD
```

The secret and every version it has are untouched — only the schedule goes away. When the last
policy in an environment is disabled, the rotator's key grant is dropped too, so seekrit's ability to
decrypt that environment ends with the feature. Every action here is written to the
[audit log](/docs/guides/audit-export) (`secret.rotation_configured`, `secret.rotated`,
`secret.rotation_failed`, `secret.rotation_disabled`).
