Database password rotation causes outages when the password changes in one place while applications, pools and replicas still hold the old one. The fix is the same idea on every engine: make two credentials valid at the same time, move every consumer to the new one, prove the old one is idle, then retire it. How you get two valid credentials differs by engine: MySQL supports a secondary password on one account, while PostgreSQL and SQL Server need two login roles that you alternate between. This guide gives the exact statements from the vendor manuals, the queries that show who is still connected with which account, and a rollback for each step. It is for DBAs and platform engineers who own application service accounts on PostgreSQL, MySQL, SQL Server and managed cloud databases.
This is an independent guide from NHI Mgmt Group, based on the MySQL, PostgreSQL, Microsoft SQL Server, AWS and HashiCorp documentation as of November 2026. Confirm syntax against the manual for your engine version before running it in production.
Key takeaways
- Two valid credentials, then one. Every no-downtime method overlaps an old and a new credential; the only question is whether the engine gives you that overlap on one account or you build it with two.
- MySQL has it built in.
ALTER USER ... RETAIN CURRENT PASSWORDkeeps the old password as a secondary, andDISCARD OLD PASSWORDends it once every client has moved. - PostgreSQL and SQL Server need alternating logins. Two login roles with identical grants, usually through a shared group role, let you change one while the other carries traffic.
- Connections outlive passwords. A password change does not end sessions that are already authenticated, so prove the move with session views, not with the absence of errors.
- Automate the second time. Secrets Manager alternating users, Vault static roles and RDS managed master passwords run the same overlap on a schedule, but only if applications read the secret at connect time.
When you need this
- Scheduled rotation: your policy sets a maximum age for service account passwords, and the account belongs to an always-on application.
- People or supplier change: a DBA, developer or contractor who knew the password has left, or a vendor that held a connection string no longer needs it.
- Exposure without proven misuse: a connection string turned up in a ticket, a report or a log. Connection strings shared in customer documents were part of the Red Hat Consulting GitLab Breach 2025.
- Not for a confirmed compromise: if someone is using the password now, speed beats overlap. Change the password, terminate sessions for that account and follow the Database Breach Response Playbook.
Before you start
| Check | Why it matters | How |
|---|---|---|
| Every consumer is known | A forgotten batch job is the usual cause of the outage | Session views by user and client address (step 1), plus connection strings in the secret store and config repositories |
| Consumers read the password from one place | One store update beats editing many hosts | AWS Secrets Manager, Azure Key Vault, Vault, Kubernetes Secrets via an operator, CI variables |
| Pools recycle connections | Pooled connections authenticated with the old password keep running until recycled | Check the pool's maximum connection lifetime and whether the pool re-reads credentials on reconnect |
| Replication topology is understood | Account changes must reach replicas before clients use them | For MySQL, the manual's procedure waits for replication before clients switch |
| You hold the right privilege | Rotation fails half-way without it | MySQL: APPLICATION_PASSWORD_ADMIN (own account) or CREATE USER; PostgreSQL: superuser or CREATEROLE with ADMIN OPTION; SQL Server: ALTER ANY LOGIN |
| The password is not typed in clear | Statements can land in client history and server logs | PostgreSQL warns that ALTER ROLE ... PASSWORD sends cleartext that may be logged and suggests psql \password; generate secrets in the secret manager, not by hand |
Choosing the overlap method
| Method | Engines | How the overlap works | Trade-off |
|---|---|---|---|
| Dual password on one account | MySQL 8.0.14 and later | New primary password, old one kept as secondary until discarded | Simplest; one account, so grants and auditing do not change |
| Alternating logins | PostgreSQL, SQL Server, any engine | Two logins (for example app_a and app_b) with the same grants; rotate the idle one and switch | Grants must stay identical; object ownership must sit with a separate role |
| Single-user change with retry | Any engine | Password changes once; clients retry and fetch the new value | AWS calls it the simplest strategy but notes a short window where new connections can be denied |
| Dynamic credentials | Engines supported by a secrets engine | Each client gets its own short-lived user, so there is nothing to rotate | Needs a broker and clients that renew; best long-term answer |
Use dual password wherever MySQL is the engine. Use alternating logins for PostgreSQL and SQL Server accounts that serve always-on applications. Accept single-user rotation only where clients retry cleanly and a few failed connection attempts are harmless. For greenfield services, skip rotation altogether with dynamic credentials.
Step-by-step
Step 1 is read-only. Steps 2 to 5 add or change credentials and are reversible. Step 6 is the only point at which the old credential stops working.
- List who connects with the account today. Run the session view for your engine several times over a normal business cycle and record each distinct client address and application name.
-- PostgreSQL SELECT usename, application_name, client_addr, backend_start, state FROM pg_stat_activity WHERE usename = 'app_user'; -- MySQL (INFORMATION_SCHEMA.PROCESSLIST is deprecated; the manual recommends the Performance Schema processlist table) SELECT ID, USER, HOST, DB, COMMAND, TIME FROM INFORMATION_SCHEMA.PROCESSLIST WHERE USER = 'appuser1'; -- SQL Server SELECT session_id, login_name, host_name, program_name, login_time FROM sys.dm_exec_sessions WHERE login_name = 'app_user';backend_startin PostgreSQL is the time the client connected, which tells you how long pooled connections live. Long-lived sessions matter in step 5. - Create the second credential.
MySQL: add a new primary, keep the old as secondary
Run it on each server that is not a replica, then wait for the change to replicate. The manual describes this procedure as working "without downtime". Note the edge cases it lists:ALTER USER 'appuser1'@'host1.example.com' IDENTIFIED BY '<new-password>' RETAIN CURRENT PASSWORD;RETAIN CURRENT PASSWORDfails if the current primary password is empty, replaces any existing secondary, and cannot be combined with a change of authentication plugin, which discards the secondary password.PostgreSQL: build an alternating pair once
Put the privileges on a group role that cannot log in, and make two login roles members of it, usingGRANTfor membership as theALTER ROLEpage directs. Create the second login role with a generated password, for exampleCREATE ROLE app_b LOGIN PASSWORD '<generated>' IN ROLE app_rw;whereapp_ais already a member ofapp_rw. On later rotations, simply change the idle login:ALTER ROLE app_b WITH PASSWORD '<new-password>';SQL Server: second login, or change the idle login
SQL Server holds one password per login, so build the same pair: a second login and database user added to the same database roles. On later rotations change only the idle login:ALTER LOGIN app_b WITH PASSWORD = '<new-password>';which requiresALTER ANY LOGIN. - Write the new credential to the secret store. Store the user name and password together as one secret version, so a consumer never reads a new password with an old user name. In AWS Secrets Manager the alternating strategy stores both in one secret and labels the live version
AWSCURRENT; with Vault static roles the client reads the current password from the role. - Move consumers. Reload or redeploy each consumer so it reads the new secret. Applications that read credentials only at start-up keep using the old value until restarted. Update scheduled jobs, reporting tools, ETL connections and any linked server or database link that stores the password; see Database Links and Linked Servers.
- Prove the old credential is idle. Re-run the step 1 queries. For alternating logins, the old login should show no sessions. For MySQL dual password, the session view cannot tell you which password a client used, so rely on the consumer list and restart evidence, then use the authentication log if your audit plugin records failures after the next step. Remember that a password change does not end existing sessions: SQL Server documents that a connection stays authenticated as long as it is maintained, and on Azure SQL Database a password reset does not reauthenticate an existing connection even when pooling resets it. Wait for pools to recycle, or restart them, before you treat a quiet view as proof.
- Retire the old credential.
Disabling is reversible; dropping is not. SQL Server notes that disabling a login does not affect logins already connected, so check sessions again and end stragglers deliberately. On the next rotation, set a new password on the disabled login, re-enable it and switch back.-- MySQL: run on each non-replica, then let it replicate ALTER USER 'appuser1'@'host1.example.com' DISCARD OLD PASSWORD; -- PostgreSQL: block the idle login without dropping it ALTER ROLE app_a NOLOGIN; -- SQL Server: disable the idle login ALTER LOGIN app_a DISABLE;
Automating the overlap
| Option | What it does | Documented behaviour to plan around |
|---|---|---|
| AWS Secrets Manager, alternating users | Lambda rotation clones the user on first rotation, then alternates which user it changes | Needs a superuser secret to clone; "both user and user_clone credentials are valid" after rotation; permission changes must be applied to both users |
| AWS Secrets Manager, single user | Changes one user's password | Open connections are not dropped; a short window may deny new connections, mitigated by retry |
| RDS managed master password | --manage-master-user-password lets RDS generate and rotate the master password in Secrets Manager | Rotates every seven days by default; --rotate-master-user-password --apply-immediately forces it; reserve the master user for administration, not applications |
| Vault static roles | Vault owns one existing user's password and rotates it | rotation_period or rotation_schedule (mutually exclusive) with optional rotation_window; a failed rotation leaves the last good password valid |
| Vault dynamic roles | A new user per lease from database/creds/<role> | Leased with a TTL; Vault revokes users after the lease expires |
| Key Vault and an Azure Function | Event-driven rotation of a two-credential resource | Microsoft's dual-credential tutorial uses the SecretNearExpiry event, raised 30 days before expiry, and a CredentialId tag to track which credential is current |
Two warnings from the vendor documentation. Vault says not to manage the same root credentials you gave Vault in config/ with a static role, because rotation would then break every user Vault manages; rotate the root with vault write -force database/rotate-root/<name> instead. And AWS recommends single-user rotation, not alternating users, when cloned users would not carry the same permissions as the original.
Verify it worked
- Sessions: the session view shows the expected applications connected with the new login (alternating) or freshly started connections after the change (dual password).
- No authentication failures: the database error log and audit records show no failed logins for the account during the retirement window.
- Secret state: the secret store's current version matches the credential in use; in Secrets Manager
AWSCURRENTis on the version you wrote. - Replicas: on MySQL, the discard has replicated; check that replicas accept the new password and reject the old one.
- Record: rotation date, old and new login names and consumer list are written to the account's inventory entry.
Rollback and common errors
| Symptom | Likely cause | Fix |
|---|---|---|
| Login failures right after retirement | A consumer still uses the old credential | Alternating: re-enable the old login (ALTER ROLE app_a LOGIN or ALTER LOGIN app_a ENABLE), fix the consumer, retire again. MySQL: no secondary exists any more, so give the consumer the current password |
RETAIN CURRENT PASSWORD fails | Empty primary password or a plugin change in the same statement | Set a real password first; change the plugin in a separate maintenance step |
| New login works but queries fail | Grants differ between the pair | Grant through the shared group role only; compare role memberships before switching |
| Objects created by the app are owned by one login | The login role owns schema objects | Move ownership to the non-login group role so either login can use and alter them |
| Old login still has sessions days later | Pooled connections never recycle | Set a maximum connection lifetime in the pool, or restart the pool in a window |
| Rotation job fails half-way | Database changed but secret not written, or the reverse | Write the secret as pending first, change the database, then promote the secret; Vault retries until the window closes and keeps the last good password |
Detection and measurement
- Failed logins by account: alert on authentication failures for service accounts in the hour after any rotation, the fastest signal of a missed consumer. See the Database Audit Log Detection Guide.
- Use of a retired login: any session from a login marked idle is either a missed consumer or misuse; both need a ticket.
- Unexpected client addresses: a known account connecting from a new
client_addror host deserves review whether or not a rotation is in progress.
Metrics that show the programme works:
- Percentage of application database accounts with a password younger than policy age.
- Percentage of accounts rotated by automation rather than by hand.
- Rotations that caused an incident, per quarter (target zero).
- Median hours between creating the new credential and retiring the old one.
- Number of accounts shared by more than one application (each one blocks independent rotation).
Common failure patterns
- Changing the password and calling it rotated: sessions authenticated with the old password keep running, so a leaked password used for a long-lived session is not cut off. Terminate sessions when the trigger is exposure.
- One account shared by many applications: every rotation becomes a coordinated change across teams, so it gets postponed. Split accounts per application.
- Grants drift between alternating logins: someone grants a new table to
app_adirectly, and the first switch toapp_bbreaks a feature. - Rotating the admin account that the rotation tool uses: the tool locks itself out. Keep the rotation identity separate and rotate it through its own path.
- Rotation without removal: old logins left enabled "just in case" double the attack surface. The Incomplete rotation breaches guide shows how often a half-finished rotation is the gap attackers use.
Hardening afterwards
- Remove the password where you can: cloud databases support identity-based authentication, for example Microsoft Entra authentication for Azure SQL and IAM database authentication on Amazon RDS. The Database Credential and Access Security Guide compares the options.
- Least privilege per account: application logins should not own schemas or hold administrative roles.
- Owner and purpose: every service account has a named owner, consumer list and rotation method recorded in the inventory.
- Scale out: for many accounts at once, sequence and stage the work as described in Credential Rotation at Scale.
Framework mapping
| Framework | Reference | Relevance |
|---|---|---|
| NIST SP 800-53 Rev 5 | IA-5 Authenticator Management | Changing authenticators on a schedule and on compromise |
| NIST SP 800-53 Rev 5 | AC-2 Account Management | Owners, purpose and disabling of database service accounts |
| NIST SP 800-53 Rev 5 | AU-6 Audit Record Review, Analysis, and Reporting | Reviewing failed logins and use of retired logins |
| NIST CSF 2.0 | PR.AA-01 | Credentials for services are managed by the organisation |
| MITRE ATT&CK | T1552.001 Credentials In Files; T1078 Valid Accounts | Shortens the useful life of passwords taken from configuration files |
Practitioner checklist
- Confirm the trigger is routine or precautionary; for active misuse, use the breach playbook.
- Build the consumer list from session views over a full business cycle.
- Pick the overlap method for the engine: dual password on MySQL, alternating logins elsewhere.
- Generate the password in the secret manager and store user name and password as one version.
- Reload every consumer and recycle connection pools.
- Prove the old credential is idle with session views and the error log.
- Retire by disabling or discarding, never by dropping first.
- Automate the next rotation and record owner, method and date.
Standards and references
- MySQL: Password Management (Dual Password Support)
- PostgreSQL: ALTER ROLE
- Microsoft Learn: ALTER LOGIN (Transact-SQL)
- AWS: Lambda function rotation strategies (Secrets Manager)
- AWS: Password management with Amazon RDS and Secrets Manager
- HashiCorp: Vault database secrets engine
- PostgreSQL: The Cumulative Statistics System (pg_stat_activity)
- Microsoft Learn: Rotation for resources with two sets of credentials
Related NHI Mgmt Group resources: Database Credential and Access Security Guide · How to rotate an AWS IAM access key without downtime · Dynamic Secrets and Ephemeral Credentials Guide · Credential Rotation at Scale