Rotate database secrets with Secrets Manager

Turn on rotation for RDS and Aurora credentials — RDS-managed master passwords (ManageMasterUserPassword, 7-day default) or a Lambda rotation function (single or alternating users) — set the schedule and window, understand the AWSCURRENT/AWSPENDING/AWSPREVIOUS labels and the four rotation steps, make clients survive a rotation, and fix the common failures by their log text.

markdownguidev4NGHW-0XEAstage: operatetrust: officialservice: secretsmanagerservice: rdsinterface: cliinterface: sdkSecrets Manager rotationrotate RDS passwordManageMasterUserPasswordmanage-master-user-passwordMasterUserSecretrotate-master-user-passwordrotation LambdacreateSecret setSecret testSecret finishSecretAWSCURRENTAWSPENDINGAWSPREVIOUSsingle user rotationalternating usersRDS Proxy alternating usersrotation schedule ScheduleExpressionrotation window Durationrotation failedAccess to KMS is not allowedsetSecret Unable to log into databasePending secret version was not created by LambdaRotationTokenFound credentials in environment variablesSecrets Manager VPC endpoint

Created 18 Oct 2026 · Modified 23 Oct 2026 · 4 versions

For an RDS or Aurora master password, let RDS manage it (--manage-master-user-password): no Lambda function, and rotation every 7 days by default. For application users, or anything RDS doesn't manage, use a Lambda rotation function. Either way, clients must re-read the secret when a login fails, rather than caching it forever.

Option A: RDS-managed master password

aws rds modify-db-instance --db-instance-identifier mydb \
  --manage-master-user-password \
  --master-user-secret-kms-key-id alias/my-key \
  --apply-immediately
  • Omit --master-user-secret-kms-key-id to use Secrets Manager's default key. A key in another account must be named by ARN. The key can't be changed later.
  • describe-db-instances shows MasterUserSecret with SecretArn, SecretStatus (creating, active, rotating or impaired) and KmsKeyId. The example secret ARNs contain rds!.
  • Rotation is every 7 days by default. The schedule can be modified, and --rotate-master-user-password together with --apply-immediately rotates now. Generated passwords are 28 characters.
  • Not supported with:
  • creating read replicas from such a source (except RDS for SQL Server and Db2);
  • Blue/Green Deployments;
  • RDS Custom;
  • Oracle Data Guard switchover.

Plan around these before you switch.

Option B: a Lambda rotation function

StrategyHow it worksUse when
Single userChanges the one user's password in placeSimple apps; how existing connections behave during the switch isn't stated on the fetched pages (UNVERIFIED)
Alternating usersKeeps two users and clones one with a superuser secretTwo users take turns, so one stays untouched while the other changes (the hub's reading; uninterrupted logins aren't stated on the fetched pages). RDS Proxy doesn't support this strategy

Schedule (SDK RotationRules):

  • ScheduleExpression (rate(…) or cron(…), UTC; as often as every four hours), or AutomaticallyAfterDays, not both. Hour-based schedules need ScheduleExpression.
  • Duration sets the window, for example 3h, and must not reach into the next window. Without it, hourly schedules close after one hour and daily ones at the end of the day. The window starts at midnight unless you set a start time.
  • Failed rotations are retried several times.
  • Turning rotation on in the console needs iam:CreateRole and iam:AttachRolePolicy, because it creates the function's role.
import { SecretsManagerClient, RotateSecretCommand } from "@aws-sdk/client-secrets-manager";

const sm = new SecretsManagerClient({});
await sm.send(new RotateSecretCommand({
  SecretId: "prod/app/db",
  RotationLambdaARN: "arn:aws:lambda:<region>:<account-id>:function:<rotation-fn>",
  RotationRules: { ScheduleExpression: "rate(30 days)", Duration: "3h" },
}));

What a rotation does

  1. createSecret: stores a new version labelled AWSPENDING. The version ID is the request token, so retries reuse it.
  2. setSecret: changes the password in the database to the pending value. It first checks that AWSCURRENT works and refers to the same user and host.
  3. testSecret: logs in with the pending value.
  4. finishSecret: moves AWSCURRENT to the new version. The old one becomes AWSPREVIOUS.

If AWSPENDING sits on a version other than AWSCURRENT, later rotations assume a rotation is still running and fail. A failed run can leave AWSPENDING on an empty version. The fix is to clear AWSPENDING and retry.

Network

The function runs in the database's VPC and security group, and it must also reach the Secrets Manager endpoint: through an interface VPC endpoint (preferred) or NAT. For alternating users with an RDS-managed superuser secret, it also needs the RDS API endpoint. The endpoint's security group must admit the VPC's CIDR on 443, and its endpoint policy must allow the function (patterns/iam-to-vpc-endpoints.md).

Make clients survive rotation

  • Read the secret by name. The default GetSecretValue returns AWSCURRENT.
  • Cache it, but on an authentication failure re-read it once and reconnect before failing (guides/rds/connect-securely.md).

When rotation fails (CloudWatch logs of the rotation function)

Log or error textCause → fix
Nothing after Found credentials in environment variablesThe function can't reach Secrets Manager (long duration, then timeout). Add a Secrets Manager interface endpoint or NAT
Nothing after createSecretHTTPS blocked by a NACL, the endpoint security group or the endpoint policy; Lambda timeout too short; or no RDS API endpoint (alternating users with an RDS-managed superuser secret)
An error occurred (AccessDeniedException) when calling the GetSecretValue operation: Access to KMS is not allowedThe function's role can't use the secret's customer managed key. The key policy may restrict by encryption context (reference/kms/key-policies-and-errors.md)
Key is missing from secret JSONThe secret's JSON lacks fields the function expects (username, password, host and so on). Match the documented structure
setSecret: Unable to log into databaseSlow (over about 5000 ms): network, so put the function in the DB's VPC and open its SG to the DB SG. Fast: bad credentials, so test AWSCURRENT and AWSPREVIOUS. Aurora PostgreSQL 13+ with scram-sha-256, or TLS-required databases, with a function created before late December 2021: recreate the function
Unable to import module 'lambda_function'An old Python 3.7 function was moved to a newer runtime. Recreate it on Python 3.12 (CDK users: aws-cdk-lib v2.258.0 or later)
Pending secret version <id> for Secret <arn> was not created by Lambda <arn>. Remove the AWSPENDING staging label and restart rotationCross-account or assumed-role rotation didn't pass RotationToken. Read event['RotationToken'] and pass it to put_secret_value
Error when executing lambda <arn> during <step> step (looping)Concurrency too low. Set reserved concurrency to at least 10

Read this document as markdown — the same bytes an agent receives.