Open MCP Portaldocs

Secrets and key rotation

What ENCRYPTION_KEY and API_KEY_PEPPER protect in Open MCP Portal, and how to rotate each with rotate-keys and the previous-key settings.

Open MCP Portal has two long-lived secrets besides the database URL and the OIDC client secret. Generate each with openssl rand -base64 32; each must decode to exactly 32 bytes, and they must differ.

SecretProtectsIf lost
ENCRYPTION_KEYEncrypts (AES-256-GCM) MCP server header values, identity signing keys' private keys, auth provider and server OAuth client secrets, people's provider tokens, PKCE verifiers, consent links and recorded call contentsThose values can't be decrypted: teams re-enter header values and client secrets, people reconnect accounts
API_KEY_PEPPERKeys the HMAC under which API keys, service account keys and OAuth tokens are stored (only digests are kept)Every API key stops working and every connected app signs in again

Keep both in your secrets store, separate from database backups. Off a loopback APP_URL, the portal refuses the example values from .env.example.

Rotating ENCRYPTION_KEY

Set the previous key

Put the current value in ENCRYPTION_KEY_PREVIOUS and a new one in ENCRYPTION_KEY.

Roll every replica

Restart them all (kubectl rollout restart) and wait until none runs with the old configuration. New values are sealed with the new key; old ones still open with the previous one.

Dry run

open-mcp-portal rotate-keys --dry-run
# Kubernetes: kubectl -n open-mcp-portal exec deploy/open-mcp-portal -c portal -- /open-mcp-portal rotate-keys --dry-run

It checks that every sealed value opens with one of the two keys and counts them per column. If any doesn't, it stops and names the table and row: find the key it was sealed with, or re-enter or delete that value.

Rotate

open-mcp-portal rotate-keys            # --batch-size 100 by default

It re-seals values still on the previous key in batches, with row locks; it's safe while serving and safe to run again. It audits platform.secrets_reencrypt per batch and platform.key_rotation (counts only).

Verify, then remove the previous key

open-mcp-portal doctor reports, under keys encryption, that ENCRYPTION_KEY_PREVIOUS can be removed. Remove it and roll again. Keep the old key with any backups made before the rotation.

Without ENCRYPTION_KEY_PREVIOUS, rotate-keys only checks and reports. A value that can't be decrypted at all (a key changed without setting the previous one) makes the affected feature fail safely: a server's health shows "Settings problem", a provider connection asks the person to connect again.

Rotating API_KEY_PEPPER

Digests can't be recomputed without the keys themselves, so this rotation happens as keys are used:

  1. Put the current value in API_KEY_PEPPER_PREVIOUS and a new one in API_KEY_PEPPER; roll the replicas.
  2. Keys used from then on are re-digested with the new pepper in the same request (audited api_key.pepper_rehash and service_account_key.pepper_rehash). OAuth access tokens expire within an hour, and refresh tokens are replaced when used.
  3. rotate-keys (or rotate-keys --dry-run) lists usable keys still on the previous pepper: kind, ID, name, owner or team and last use. doctor counts them under keys pepper.
  4. After 2 to 4 weeks, remove API_KEY_PEPPER_PREVIOUS. Keys never used meanwhile stop working, and their owners create new ones; connected apps that weren't used sign in again.

Identity signing keys

The ES256 keys that sign identity tokens are generated and rotated by the portal itself, every 30 days, by one replica at a time. A new key is published in the JWKS at least SIGNING_KEY_PREPUBLISH (24 hours by default) before it signs, and a retired key stays published for a day, then its private key is erased. Private keys are encrypted with ENCRYPTION_KEY. Nothing to do as an operator.

The OIDC client secret and the database password

Rotate them at the identity provider or database, update the secret, and roll the replicas. Kubernetes doesn't restart pods when a Secret changes.

On this page