AWS Secrets Manager Rotation: How Automatic Secret Rotation Works
Automatic secret rotation in AWS Secrets Manager replaces the manual work of updating credentials on a schedule. Instead of setting calendar reminders to change database passwords, you configure rotation once and Secrets Manager coordinates the credential change across both the secret store and the target service.
This guide covers how rotation works, when to use managed rotation vs Lambda-based rotation, how to set up rotation for RDS and custom secrets, and how to design applications that handle rotated credentials correctly.
By the end, you will understand which rotation approach fits your use case, how to configure it, and how to avoid the common mistakes that cause rotation failures in production.
Simple explanation
Think of it like changing the locks
Imagine your office has a front-door code that every employee knows. If someone leaves the company, that code still works. Rotation is like changing the code on a schedule so anyone who learned the old one can no longer get in. Secrets Manager does this automatically: it generates a new code, reprograms the lock, tests that the new code works, and then tells everyone the updated combination.
In technical terms, when rotation runs it generates a new password, updates the database (or API, or whatever service uses the credential), verifies the new password works, and then promotes it to the active version. The old password stays available briefly so applications mid-request do not break.
A real example: your production app uses a PostgreSQL password stored in Secrets Manager. Every 30 days, rotation generates a new password, runs an ALTER USER command on the database, confirms the connection works, and promotes the new password to active. Your app fetches the latest password on its next connection attempt.
When to use automatic rotation
- RDS, Redshift, or DocumentDB credentials. Managed rotation handles these with no Lambda code required.
- Database passwords for other engines where you can write a Lambda to run the password-change command.
- Third-party API keys that truly need to be stored as secrets and that the provider lets you rotate programmatically.
- Secrets used in CI/CD pipelines or Kubernetes workloads where credentials are pulled from Secrets Manager at deploy or runtime.
- AWS-to-AWS access. If your EC2 instance, Lambda function, or ECS task needs to call another AWS service, use IAM roles with STS temporary credentials instead. Storing and rotating AWS access keys adds complexity that IAM roles eliminate entirely.
- Secrets that cannot be changed programmatically. If the target service has no API for updating the credential, rotation will fail at the setSecret step.
- Long-lived AWS access keys that could be replaced by IAM roles. Rotating access keys in Secrets Manager is possible but is almost always the wrong solution.
Managed rotation vs Lambda rotation
Secrets Manager offers two rotation approaches. Understanding the difference prevents you from over-engineering simple cases or under-building complex ones.
Managed rotation is available for supported AWS database services (RDS, Redshift, DocumentDB). AWS handles the entire rotation process internally. You do not create, deploy, or manage a Lambda function.
Lambda-based rotation is used for everything else, and also when you need custom rotation logic for database secrets (for example, a multi-step credential change that involves external systems). You write a Lambda function that implements the four rotation steps, and Secrets Manager invokes it on schedule.
| Option | Best for | Lambda needed | Operational effort | Availability notes |
|---|---|---|---|---|
| Managed rotation | RDS, Redshift, DocumentDB secrets | No | Low. AWS manages the rotation infrastructure. | Supports single-user and alternating-users strategies. Alternating users is safer for high availability. |
| Lambda-based rotation | API keys, OAuth tokens, custom credentials, complex database workflows | Yes. You write and maintain the function. | Medium to high. You own the Lambda code, IAM permissions, VPC config, and monitoring. | Availability depends on your Lambda logic. Must handle failures gracefully to avoid leaving credentials in an inconsistent state. |
When using managed rotation for a supported database, do not specify a —rotation-lambda-arn in the CLI. Secrets Manager provisions the rotation infrastructure automatically. The —rotation-lambda-arn parameter is only for Lambda-based rotation where you provide your own function.
Hotel vs Airbnb
Managed rotation is like staying at a hotel: the staff changes the sheets and towels on a schedule, and you just show up. Lambda-based rotation is like renting an Airbnb: you bring your own linens, decide when to wash them, and handle any problems yourself. Both get the job done, but the operational burden is very different.
How Secrets Manager rotation works
Regardless of whether you use managed or Lambda-based rotation, the internal process follows four steps. Understanding these steps helps you debug rotation failures.
Version staging labels
Every secret version in Secrets Manager carries a staging label that tracks where it sits in the rotation lifecycle:
- AWSCURRENT is the active version. This is what applications receive when they call
GetSecretValue. - AWSPENDING is the version being created during rotation. It exists only while rotation is in progress.
- AWSPREVIOUS is the former active version. It remains valid at the target service until the next rotation, giving applications a fallback window.
The four rotation steps
| Step | What happens |
|---|---|
createSecret | Generates a new credential value and stores it as a new secret version labeled AWSPENDING. |
setSecret | Applies the new credential at the target service. For a database, this means running an ALTER USER or equivalent command to change the password. |
testSecret | Verifies the new credential works by connecting to the service with the AWSPENDING version. |
finishSecret | Promotes AWSPENDING to AWSCURRENT and moves the old version to AWSPREVIOUS. The rotation is now complete. |
Relay race baton handoff
Think of rotation like a relay race. The new runner (AWSPENDING) starts running alongside the current runner (AWSCURRENT). Only when the new runner is up to speed and the baton is secure (setSecret + testSecret both pass) does the handoff happen. If something goes wrong, the original runner keeps going. Nobody drops the baton.
If any step fails, the rotation stops and AWSCURRENT remains unchanged. Your application continues using the existing credential. You can monitor failures through CloudTrail events and CloudWatch Logs.
How to set up rotation for RDS, Redshift, and DocumentDB
For supported database services, the setup depends on whether you use managed rotation (recommended) or Lambda-based rotation.
Managed rotation (console)
- Open the secret in the Secrets Manager console.
- Choose Set up automatic rotation.
- Select a rotation schedule (for example, every 30 days).
- Choose a rotation strategy: single user (updates the same database user) or alternating users (switches between two database users, keeping one active at all times).
- Save. Secrets Manager provisions the rotation infrastructure automatically.
Managed rotation (CLI)
# Enable managed rotation for an RDS secret (no Lambda ARN needed)
aws secretsmanager rotate-secret \
--secret-id "prod/myapp/postgres" \
--rotation-rules '{"ScheduleExpression":"rate(30 days)","Duration":"2h"}'Notice there is no —rotation-lambda-arn parameter. For managed rotation, Secrets Manager handles the rotation function internally.
Lambda-based rotation for databases (when you need custom logic)
If you need custom behavior during rotation, such as notifying an external system or performing additional validation, you can create your own Lambda function and pass its ARN:
# Enable rotation with a custom Lambda function
aws secretsmanager rotate-secret \
--secret-id "prod/myapp/postgres" \
--rotation-lambda-arn arn:aws:lambda:us-east-1:123456789012:function:MyCustomRotation \
--rotation-rules '{"ScheduleExpression":"rate(30 days)","Duration":"2h"}'Prerequisites
Before rotation will work, verify these requirements:
- Network access. The rotation process (whether managed or Lambda) must be able to reach the database. For databases in a VPC, this means the rotation infrastructure needs to be in the same VPC or have routing to it.
- Secrets Manager API access. If rotation runs inside a private VPC subnet with no internet access, create a VPC endpoint for Secrets Manager or configure a NAT gateway so the rotation process can call the Secrets Manager API.
- IAM permissions. The rotation process needs
secretsmanager:GetSecretValue,secretsmanager:PutSecretValue,secretsmanager:UpdateSecretVersionStage, andsecretsmanager:DescribeSecreton the secret. - KMS access. If the secret is encrypted with a customer managed key (CMK), the rotation process also needs
kms:Decryptandkms:GenerateDataKeypermissions on that key. - Database permissions. The database user whose credentials are stored in the secret must have permission to change its own password (for single-user rotation) or create and manage the alternating user (for alternating-users rotation).
The alternating-users strategy creates two database users and rotates between them. While one user’s password is being changed, the other remains active. This is the safer option for high-availability applications because there is always one fully working credential. However, it requires the admin secret to have permission to manage both database users.
How to set up rotation for custom secrets
For API keys, OAuth tokens, or any credential not covered by managed rotation, you write a Lambda function that implements the four rotation steps. Secrets Manager invokes your function with these event fields:
SecretIdis the ARN of the secret being rotated.ClientRequestTokenis a unique ID for this rotation attempt. It becomes the new version ID.Stepis one ofcreateSecret,setSecret,testSecret, orfinishSecret.
Here is a minimal Lambda structure showing the control flow:
import boto3
import json
def lambda_handler(event, context):
client = boto3.client('secretsmanager')
arn = event['SecretId']
token = event['ClientRequestToken']
step = event['Step']
if step == "createSecret":
# Generate new credentials and store as AWSPENDING
current = json.loads(
client.get_secret_value(SecretId=arn)['SecretString']
)
current['api_key'] = generate_new_api_key()
client.put_secret_value(
SecretId=arn,
ClientRequestToken=token,
SecretString=json.dumps(current),
VersionStages=['AWSPENDING']
)
elif step == "setSecret":
# Call the external API to register the new key
pending = json.loads(
client.get_secret_value(
SecretId=arn, VersionId=token, VersionStage='AWSPENDING'
)['SecretString']
)
register_key_with_provider(pending['api_key'])
elif step == "testSecret":
# Verify the new key works
pending = json.loads(
client.get_secret_value(
SecretId=arn, VersionId=token, VersionStage='AWSPENDING'
)['SecretString']
)
call_provider_api(pending['api_key'])
elif step == "finishSecret":
# Promote AWSPENDING to AWSCURRENT
metadata = client.describe_secret(SecretId=arn)
current_version = next(
v for v, stages in metadata['VersionIdsToStages'].items()
if 'AWSCURRENT' in stages
)
client.update_secret_version_stage(
SecretId=arn, VersionStage='AWSCURRENT',
MoveToVersionId=token, RemoveFromVersionId=current_version
)Testing custom rotation safely
A broken rotation function can leave credentials in an inconsistent state between Secrets Manager and your target service. Never test a new rotation Lambda directly against production.
- Start with a secret that points to a test or staging environment.
- Trigger rotation manually:
aws secretsmanager rotate-secret —secret-id “test/myapp/api-key” - Check CloudWatch Logs for the Lambda to verify each step completed.
- Confirm the AWSCURRENT version has the new credential:
aws secretsmanager get-secret-value —secret-id “test/myapp/api-key” - Verify your application can still authenticate using the secret.
Rotation schedules and windows
Oil change vs annual inspection
A rate() schedule is like changing your oil every 5,000 miles. It runs at a fixed interval from the last rotation. A cron() schedule is like a yearly inspection on a fixed date. It runs at a specific time you choose, regardless of when the last rotation happened. Both keep things healthy, but you pick the one that matches how you operate.
rate() expressions
The simplest option. Rotation runs at a fixed interval from the last rotation.
# Rotate every 30 days
aws secretsmanager rotate-secret \
--secret-id "prod/myapp/database" \
--rotation-rules '{"ScheduleExpression":"rate(30 days)"}'cron() expressions
For precise timing, use a cron expression. All cron schedules run in UTC.
# Rotate at 2:00 AM UTC on the first Sunday of every month
aws secretsmanager rotate-secret \
--secret-id "prod/myapp/database" \
--rotation-rules '{"ScheduleExpression":"cron(0 2 ? * SUN#1 *)"}'Rotation windows
The Duration parameter defines a rotation window: the time range within which rotation can start after the scheduled time. This is useful when you want rotation to happen during a low-traffic period but do not need it at an exact minute.
# Rotate every 30 days, within a 4-hour window starting at the scheduled time
aws secretsmanager rotate-secret \
--secret-id "prod/myapp/database" \
--rotation-rules '{"ScheduleExpression":"rate(30 days)","Duration":"4h"}'If you do not specify a duration, Secrets Manager uses a default rotation window. Cron-based schedules are especially helpful when you want rotation during a maintenance window. All schedule expressions evaluate in UTC, so convert your desired local time before writing the expression.
How applications should handle rotated secrets
Configuring rotation is only half the work. Your application must be designed to handle credential changes gracefully.
Most rotation failures are not rotation bugs. They are application bugs. An app that reads the secret once at startup and never refreshes will break on the very first rotation. If your app caches passwords forever or never retries on auth failures, rotation will cause outages no matter how well the rotation itself is configured.
Cache with a TTL
Do not fetch the secret on every request (expensive and slow), but do not cache it forever either. A TTL of 5 to 15 minutes is a common starting point. The AWS Secrets Manager caching libraries (available for Java, Python, .NET, Go, and JavaScript) handle this automatically.
Retry on authentication failure
When a database connection fails with an authentication error, fetch a fresh secret and retry before giving up. This handles the brief window where the cached password is the old one and the database now expects the new one.
import boto3
import json
import psycopg2
import time
_cache = {'value': None, 'fetched_at': 0}
CACHE_TTL = 300 # 5 minutes
def get_secret(force_refresh=False):
now = time.time()
if not force_refresh and _cache['value'] and (now - _cache['fetched_at']) < CACHE_TTL:
return _cache['value']
client = boto3.client('secretsmanager')
result = client.get_secret_value(SecretId='prod/myapp/postgres')
_cache['value'] = json.loads(result['SecretString'])
_cache['fetched_at'] = now
return _cache['value']
def get_db_connection():
secret = get_secret()
for attempt in range(3):
try:
return psycopg2.connect(
host=secret['host'], database=secret['dbname'],
user=secret['username'], password=secret['password'],
port=secret['port']
)
except psycopg2.OperationalError as e:
if "password authentication failed" in str(e) and attempt < 2:
time.sleep(1)
secret = get_secret(force_refresh=True)
else:
raiseRefresh connection pools
If your application uses a connection pool, existing connections hold the old password. New connections will use whatever the pool’s credential source provides. Design your pool to:
- Periodically cycle out old connections (most connection pool libraries have a max-lifetime setting).
- Handle authentication errors by evicting the failed connection and fetching a fresh secret for the replacement.
Application design matters as much as rotation configuration. A well-configured rotation schedule paired with an app that caches credentials forever will still cause downtime.
Common mistakes
- Using Lambda-based rotation when managed rotation is available. For RDS, Redshift, and DocumentDB, managed rotation is simpler and has fewer moving parts. Only use a custom Lambda if you need custom rotation logic.
- Application caches secrets forever. An application that reads the secret once at startup and never refreshes will break on the first rotation. Always cache with a TTL and retry on authentication failures.
- Missing network access for the rotation process. The rotation function must reach both the target database and the Secrets Manager API. If the database is in a private VPC subnet, you need a VPC endpoint for Secrets Manager or a NAT gateway.
- Wrong IAM or KMS permissions. The rotation process needs permissions on the secret and, if you use a customer managed key, on the KMS key. Missing KMS permissions are a common cause of silent rotation failures.
- Rotating AWS access keys instead of using IAM roles. If you are rotating IAM user access keys through Secrets Manager, step back and ask whether an IAM role with STS temporary credentials would eliminate the need for stored keys entirely.
- Not testing rotation before going to production. Rotation has multiple dependencies (network, IAM, database permissions, application retry logic). Always trigger a manual rotation in a test environment and verify every step before enabling it on production secrets.
- Setting a rotation frequency too high without testing. Rotating daily means 365 rotation events per year. If your application has any weakness in handling rotation, you will find out quickly. Start with 30 or 90 days, confirm everything works, then shorten the interval if compliance requires it.
Frequently asked questions
Do I need Lambda for AWS Secrets Manager rotation?
Not always. For supported services like RDS, Redshift, and DocumentDB, AWS offers managed rotation that handles everything without you creating a Lambda function. For other secret types (API keys, OAuth tokens, custom credentials), you write a Lambda rotation function that implements the four-step rotation logic.
Does secret rotation cause downtime?
It depends on how your application handles credentials. Rotation itself is designed to keep the old credential valid (as AWSPREVIOUS) during the transition. But if your application caches credentials indefinitely or does not retry on auth failures, connections can break. The alternating-users strategy is the safest option for high availability because one user is always active. Application-side retry and cache refresh are just as important as the rotation config.
How often should I rotate secrets?
There is no single answer. Many teams start with 30 or 90 days for database passwords. Compliance frameworks may require specific intervals. The key is to test your application at whatever frequency you choose before going to production. Rotating more often reduces the exposure window but increases the chance of hitting an application bug that does not handle rotation well.
Can I rotate non-database secrets?
Yes. Any secret stored in Secrets Manager can be rotated automatically if you provide a Lambda function that knows how to change the credential at the source. This works for API keys, OAuth tokens, SSH keys, or any custom credential. The Lambda receives the same four-step event structure regardless of the secret type.
Should I store AWS access keys in Secrets Manager or use IAM roles and STS?
Use IAM roles and STS temporary credentials whenever possible. Storing AWS access keys in Secrets Manager and rotating them is a valid pattern for legacy systems that cannot use roles, but it adds operational complexity. IAM roles with STS provide short-lived credentials automatically with no rotation infrastructure to maintain.