Guide · AWS Secrets Manager

RDS Credential Rotation for MCP Servers

AWS Secrets Manager provides managed rotation Lambda functions for every major RDS engine — you do not need to write the rotation logic for PostgreSQL, MySQL, Oracle, or SQL Server. The managed rotation Lambdas handle all four lifecycle stages (createSecret, setSecret, testSecret, finishSecret) and are deployed automatically from the Secrets Manager console or via CDK. However, three configuration errors cause the majority of rotation failures in practice: the rotation Lambda has no network path to the RDS instance — the Lambda must be deployed in the same VPC as the RDS instance and must either route to Secrets Manager via a VPC interface endpoint or via NAT; without this, the rotation Lambda times out during setSecret when it attempts to connect to the database; MultiUser rotation's master secret ARN is not set — if you configure MultiUser rotation without pointing to a master secret (a separate secret holding admin credentials), the rotation Lambda has no credentials to create or modify the alternating database users; SingleUser rotation during peak traffic causes brief auth failures — SingleUser rotation invalidates the old password on the RDS instance before updating the Secrets Manager AWSCURRENT label; any Lambda with a cached copy of the old password will get authentication errors for the TTL window.

TL;DR

Use MultiUser rotation for production MCP servers — it keeps both old and new credentials valid simultaneously, so there is no auth failure window. Ensure the rotation Lambda is in the RDS VPC with a Secrets Manager VPC endpoint. For MultiUser, create a master secret with admin credentials and reference its ARN when configuring rotation. Monitor rotation via EventBridge RotationSucceeded / RotationFailed events.

AWS-provided rotation Lambda ARNs by engine

Secrets Manager's managed rotation functions are AWS-owned Lambdas deployed into your account when you enable rotation in the console. They are also available as CDK constructs. The specific SAR (Serverless Application Repository) function depends on engine and strategy:

Engine SingleUser SAR function MultiUser SAR function
PostgreSQL (RDS / Aurora) SecretsManagerRDSPostgreSQLRotationSingleUser SecretsManagerRDSPostgreSQLRotationMultiUser
MySQL (RDS / Aurora) SecretsManagerRDSMySQLRotationSingleUser SecretsManagerRDSMySQLRotationMultiUser
Oracle RDS SecretsManagerRDSOracleRotationSingleUser SecretsManagerRDSOracleRotationMultiUser
SQL Server RDS SecretsManagerRDSSQLServerRotationSingleUser SecretsManagerRDSSQLServerRotationMultiUser

Each function is deployed into your VPC via AWS SAR when you enable rotation. The deployment creates a Lambda function named SecretsManager-{engine}-{strategy}-{suffix} in your account. You do not manage this Lambda's code — AWS handles updates.

Configuring rotation via CDK (recommended)

CDK's Secrets Manager constructs handle the SAR function deployment and VPC configuration in one call:

from aws_cdk import (
    aws_secretsmanager as sm,
    aws_rds as rds,
    aws_ec2 as ec2,
    Duration,
)

# Aurora PostgreSQL cluster for MCP server
db_cluster = rds.DatabaseCluster(
    self, "MCPDatabase",
    engine=rds.DatabaseClusterEngine.aurora_postgres(
        version=rds.AuroraPostgresEngineVersion.VER_15_4
    ),
    vpc=vpc,
    vpc_subnets=ec2.SubnetSelection(subnet_type=ec2.SubnetType.PRIVATE_WITH_EGRESS),
)

# The cluster auto-creates a secret for the master password
# Add a separate application user secret with rotation
app_secret = sm.Secret(
    self, "MCPAppSecret",
    description="MCP server application user credentials",
    generate_secret_string=sm.SecretStringGenerator(
        secret_string_template='{"username": "mcp_app_a"}',
        generate_string_key="password",
        exclude_characters="/@\"\\",
    ),
)

# For MultiUser rotation, create a master secret first
master_secret = sm.Secret(
    self, "MCPMasterSecret",
    description="RDS admin credentials for rotation",
    secret_string_value=SecretValue.unsafe_plain_text(
        '{"username": "mcp_admin", "password": "change-me"}'
    ),
)

# Enable MultiUser rotation — CDK deploys the SAR rotation Lambda automatically
app_secret.add_rotation_schedule(
    "AppSecretRotation",
    hosted_rotation=sm.HostedRotation.postgre_sql_multi_user(
        master_secret=master_secret,
        vpc=vpc,
        vpc_subnets=ec2.SubnetSelection(subnet_type=ec2.SubnetType.PRIVATE_WITH_EGRESS),
        security_groups=[rotation_lambda_sg],
    ),
    automatically_after=Duration.days(30),
)

The rotation_lambda_sg security group must have an outbound rule to the RDS security group on port 5432 (PostgreSQL). The RDS security group must have an inbound rule from the rotation Lambda security group on port 5432. Without this explicit security group wiring, the rotation Lambda's setSecret call times out.

VPC network requirements for the rotation Lambda

The rotation Lambda must be able to reach two endpoints:

  1. The RDS instance/cluster — to connect and change the password. Requires network access on the database port (typically 5432 for PostgreSQL, 3306 for MySQL) from the rotation Lambda's subnet/SG to the RDS SG.
  2. The Secrets Manager API — to call GetSecretValue and PutSecretValue. Requires either a VPC interface endpoint for Secrets Manager (com.amazonaws.{region}.secretsmanager) or a NAT gateway/instance with internet access.
Network path option Cost Security Latency
VPC interface endpoint for Secrets Manager ~$7.30/month per AZ per endpoint Traffic stays on AWS backbone — no internet exposure <1 ms (intra-VPC)
NAT gateway ~$32/month per NAT + $0.045/GB data processing Traffic exits to internet briefly 1–5 ms
NAT instance (EC2) $0 for t3.micro (free tier) or ~$7/month for t3.small Same as NAT gateway but less managed 1–10 ms

For MCP servers that already use a VPC endpoint for Secrets Manager (to cache secrets without NAT), the rotation Lambda can share the same endpoint. No additional endpoint is needed — just ensure the rotation Lambda's subnet can reach the existing endpoint's network interface.

MultiUser rotation — how the alternating users work

MultiUser rotation maintains two database users (conventionally mcp_app_a and mcp_app_b) and alternates between them on each rotation cycle:

# Initial state
AWSCURRENT → { "username": "mcp_app_a", "password": "pw-abc123" }
AWSPREVIOUS → { "username": "mcp_app_b", "password": "pw-xyz789" }
(both users exist in the database with their respective passwords)

# Rotation cycle triggered
# createSecret: generate new password for mcp_app_b (the AWSPREVIOUS user)
AWSPENDING → { "username": "mcp_app_b", "password": "pw-new456" }

# setSecret: update mcp_app_b's password in RDS using the master secret's admin credentials
# testSecret: connect to RDS using AWSPENDING credentials (mcp_app_b / pw-new456) → verify
# finishSecret: promote AWSPENDING to AWSCURRENT

# Final state
AWSCURRENT → { "username": "mcp_app_b", "password": "pw-new456" }
AWSPREVIOUS → { "username": "mcp_app_a", "password": "pw-abc123" }
# mcp_app_a still exists in the database with its old password (still valid)

During the entire rotation window, mcp_app_a remains a valid database user with an unchanged password. Lambda functions with cached mcp_app_a credentials continue to authenticate successfully. After finishSecret, new cache fetches return mcp_app_b credentials. Old cached credentials (mcp_app_a) remain valid until the next rotation, when mcp_app_a's password is updated.

The master secret — containing admin credentials for the database — must be stored as a separate Secrets Manager secret. Its ARN must be set in the rotating secret's metadata. If the master secret's password has also rotated, verify the master secret's AWSCURRENT value can actually connect to the database before triggering application secret rotation.

Testing rotation without affecting production traffic

To verify rotation works before relying on it in production:

# 1. Trigger an immediate rotation (without waiting for the schedule)
aws secretsmanager rotate-secret \
  --secret-id "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/mcp-server/database-AbCdEf"

# 2. Monitor the rotation Lambda's CloudWatch logs in real time
aws logs tail \
  /aws/lambda/SecretsManager-RDSPostgreSQL-MultiUser-AbCdEf \
  --follow \
  --filter-pattern "ERROR"

# 3. Describe the secret to verify version stage transition
aws secretsmanager describe-secret \
  --secret-id "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/mcp-server/database-AbCdEf" \
  --query "VersionIdsToStages"
# Expected after successful rotation: {"new-version-id": ["AWSCURRENT"], "old-version-id": ["AWSPREVIOUS"]}

# 4. Verify the new credential actually works
python3 -c "
import boto3, json, psycopg2
client = boto3.client('secretsmanager', region_name='us-east-1')
resp = client.get_secret_value(SecretId='arn:...', VersionStage='AWSCURRENT')
creds = json.loads(resp['SecretString'])
conn = psycopg2.connect(host=creds['host'], user=creds['username'], password=creds['password'])
print('Connection successful')
conn.close()
"

Run these steps in a staging environment first. The most common failure mode is a rotation that Secrets Manager marks as successful but that leaves the database in an inconsistent state (e.g., the new user was created but not granted the necessary schema permissions). Always verify that the application can actually query data with the new credential, not just that the connection authenticated.

Common failures

Symptom Root cause Fix
Rotation Lambda times out with could not connect to server: Connection timed out Rotation Lambda subnet has no route to the RDS instance; missing security group rule or missing VPC endpoint for Secrets Manager Deploy rotation Lambda in the same subnet group as the RDS instance; add SG inbound rule from rotation Lambda SG to RDS SG on DB port
MultiUser rotation fails with AccessDeniedException on GetSecretValue for master secret Rotation Lambda execution role lacks secretsmanager:GetSecretValue on the master secret ARN Add secretsmanager:GetSecretValue on the master secret ARN to the rotation Lambda's IAM role
MCP server auth failures for 30 seconds after SingleUser rotation completes SingleUser rotation updates the DB password before updating Secrets Manager; warm Lambda containers use cached old password Switch to MultiUser rotation for zero-downtime; or reduce cache TTL to 60s and accept the brief failure window
New database user created by MultiUser rotation can't query application tables Rotation Lambda's setSecret creates the user but doesn't GRANT schema permissions Use a custom rotation Lambda (not the managed one) that adds GRANT statements after user creation; or pre-grant via ALTER DEFAULT PRIVILEGES on the master user
Rotation succeeds but the master secret itself becomes invalid after a failed rotation of the master secret Rotation of the master secret failed midway; AWSCURRENT points to an invalid credential Manually update the master secret value with known-good credentials using put-secret-value; verify with a direct DB connection before re-enabling rotation

Catch RDS rotation failures before they cause MCP server downtime

A rotation failure can silently break your MCP server's database access hours later when the old credential expires. AliveMCP probes every 60 seconds and alerts within one minute — giving you the lead time to diagnose a rotation issue before it becomes a production incident.

Join the waitlist →