Changing a database password without dropping traffic.Why it is not just "change the password"#
A pool holds authenticated connections. Changing the password server-side does
not invalidate them — existing connections keep working until they are
closed, and the next reconnect fails. So the failure arrives minutes or hours
after the change, when a connection is evicted and replaced, and it looks
unrelated to what you did.That delay is the whole problem. Plan for two valid credentials at once.The safe order below, and the leak path that joins it at step 2: a leak skips
the overlap, because a credential in someone else's hands is worse than the
outage.The safe order#
Create a second user#
Give it the same grants, rather than changing the existing one's password.Copy the grants app_v1 actually has, not GRANT ALL: a migration or
seeder that grants everything is what prorm audit code
reports as GRANT-001, and prorm audit db reports a role that can write
when it only probes (DB-READONLY).Deploy the application pointing at it#
Both users are valid, so the rollout can be gradual and a rollback is just
the previous deploy.Watch for the old user going quiet#
Do not skip this — a forgotten job, a cron, or a replica may still be using
it.Drop the old user#
Once nothing has used it for a full cycle of whatever your longest-lived
process is.If you must reuse the same username, the same principle applies in a cruder
form: change the password, then immediately restart every instance so no pool
holds a connection authenticated with the old one.Getting the credential into the process#
Read it at construction, not at import:const db = new Prorm({
dialect: 'postgres',
host: process.env.PGHOST,
username: process.env.PGUSER,
password: process.env.PGPASSWORD,
database: process.env.PGDATABASE,
});
prorm reads password once, when it builds the dialect. There is no hook that
re-reads it, so rotating a secret in a secrets manager does not reach a
running process — it needs a restart, or a new Prorm instance.For short-lived credentials (IAM auth, Vault leases), construct a new instance
on the lease boundary and close the old one after its in-flight work drains:const next = new Prorm({ /* … fresh credentials … */ });
await next.connect();
swapGlobalInstance(next);
await previous.close(); // waits for checked-out connections to return
If a credential leaked#
Order matters: revoke first, restore service second.1.
Revoke immediately — ALTER USER … PASSWORD, or drop the user. Accept the
outage; a leaked credential in use is worse than downtime.
2.
Rotate anything sharing it. If the leak was a repository or an image layer,
assume everything in that scope is exposed.
3.
Deploy the new credential.
4.
Check for damage while access was open: unexpected rows, changed grants, new
users. Audit logging is what makes this
answerable rather than a guess; prorm logs tail --level critical+ --since 7d
and prorm audit db (grants and superuser membership as they are now) are
the two commands to start with. 5.
Find how it leaked. prorm audit code . reports a password in a connection
URL (SEC-001) and a committed .env or .klock file (SEC-003) without
repeating the secret; see The security toolkit. A passwordless .klock has no database password to change, and its secret
cannot be rotated in place: revoke the session (prorm passwordless revoke)
and issue a new one. See klock.$ prorm passwordless list --db billing
$ prorm passwordless revoke 3f2c9a4e-1b7d-4c55-9e0a-6d8b2f71c0aa --db billing
✓ Revoked 3f2c9a4e-1b7d-4c55-9e0a-6d8b2f71c0aa.
$ prorm passwordless issue --db billing --role billing_worker --read --write --label billing-worker --expires 90
issue writes the new .klock to ./.klock unless --klock names
another path. Ship it the way you ship any secret, never through the
repository the old one leaked from.[!CAUTION]
Revoke before you re-issue. The old .klock keeps working until its
session is revoked, and a copy you cannot see is still a copy.
klock — passwordless sessions: revoke and re-issue instead of rotating Modified at 2026-10-04 00:40:16