Docs
Key rotation
Replacing ENCRYPTION_KEY without downtime, and without losing a secret.
ENCRYPTION_KEY is what makes stored env vars, database passwords, registry and git credentials, S3 keys and notification configs readable. Losing it makes them unrecoverable, so it is worth rotating on a schedule and after anyone who had it leaves.
ENCRYPTION_KEYS is the comma-separated form: the first entry encrypts, every entry can decrypt. That overlap is what makes a rotation possible with the panel running.
The runbook
1# 1. generate the new key
2openssl rand -hex 32
3
4# 2. /etc/nixploy/.env — NEW key first, current key second
5ENCRYPTION_KEYS=<new>,<current>
6
7# 3. restart the panel so it reads both
8docker service update --force nixploy
9
10# 4. rewrite every secret column with the new key
11docker exec nixploy pnpm -F @nixploy/server nixploy:rotate-key
12
13# 5. /etc/nixploy/.env — drop the old key, restart again
14ENCRYPTION_KEYS=<new>Flags worth knowing
- --dry-run — report what would change and write nothing
- --batch-size=500 — rows per transaction
- --table=notification — one table only
- --skip-undecryptable — leave rows no configured key can read, for the case where a key really was lost
The script discovers the columns from the schema, so a new secret column needs no change to it. Each batch is one transaction, and a row that no configured key can authenticate aborts the run and names the table, column and row — a rotation never half-writes.
Passphrases and formats
A key may also be a passphrase of 32 characters or more. Those are stretched with scrypt and written with a version prefix; 64-character hex keys are used directly. Both forms stay readable forever, so moving a passphrase install onto a hex key is just another rotation.
Before you start
- Take an instance backup first — the rotation rewrites every secret column
- Keep the old key until step 5 has completed successfully; a restore from a dump taken before the rotation still needs it
- /etc/nixploy/.env is deliberately excluded from instance backups, so store both keys somewhere the backup reader cannot reach
- Treat a lost key as a migration, not a config change: without it every credential has to be re-entered by hand
Also see the repository guides under docs/ · REST API reference
