In 2024 alone, security researchers at GitGuardian found 23,770,171 new hardcoded secrets dumped into public GitHub repositories, according to the company’s State of Secrets Sprawl 2025 report. Verizon’s 2025 Data Breach Investigations Report puts a finer point on why that matters: 22% of breaches now involve stolen or compromised credentials, per the 2025 DBIR. Database passwords in a .env file, API keys pasted into a Slack message, a Terraform state file checked into a private repo that quietly turned public — this is how most breaches actually start.
AWS Secrets Manager is Amazon’s answer to that problem: a managed vault that stores, encrypts, rotates, and audits access to credentials so they never have to live in code or config files again. This tutorial walks through setting it up from a blank AWS account to a production-ready configuration with automatic rotation, Lambda and ECS integration, Kubernetes sync, and infrastructure-as-code — in 13 steps, roughly 80 minutes. By the end you’ll also know exactly what it costs, how it compares to Parameter Store, Azure Key Vault, and HashiCorp Vault, and how to avoid the mistakes that generate the angriest AWS bills.
Don't miss new tech stories on Google
Add Tech Insider once in the Google app and our stories appear in your news suggestions.
What Is AWS Secrets Manager, and Why It Matters in 2026
AWS Secrets Manager is a managed service for storing and retrieving credentials, API keys, tokens, and other sensitive values without hardcoding them into application source, container images, or configuration files. Every secret is encrypted at rest with AWS Key Management Service (KMS), versioned automatically, and accessible only to IAM principals explicitly granted permission. That last part matters more than it sounds — the biggest practical difference between “a secret stored somewhere” and “a secret managed properly” is that every read is authenticated, authorized, and logged.
Under the hood, Secrets Manager stores each secret as an encrypted blob (typically JSON, but it accepts any string or binary payload up to 65,536 bytes) with a set of versions attached to it. Each version carries staging labels — AWSCURRENT, AWSPENDING, and AWSPREVIOUS — that let a rotation function stage a new credential before cutting traffic over to it, so an application never sees a half-rotated secret. An AWS account can hold up to 500,000 secrets per Region, and the GetSecretValue API is rated for 10,000 requests per second per Region, which is generous enough that throttling is almost always a caching problem rather than a real ceiling (source: AWS Secrets Manager quotas).
What makes AWS Secrets Manager worth the setup time in 2026 specifically is the rotation piece. Static, never-rotated database passwords are one of the most common findings in cloud security audits, and Secrets Manager ships pre-built Lambda rotation templates for Amazon RDS (MySQL, PostgreSQL, Oracle, and SQL Server engines), Redshift, DocumentDB, Neptune, and MongoDB Atlas, plus a generic HTTP endpoint template for anything else (source: AWS rotation documentation). That turns “rotate credentials every 90 days” from a recurring engineering chore into a scheduled Lambda invocation.
There’s also a compliance angle that pushes teams toward Secrets Manager specifically rather than a homegrown solution. SOC 2, PCI-DSS, and most internal security reviews now expect evidence that credentials rotate on a schedule and that access to them is logged per-principal, not just per-application. A managed service that generates that audit trail automatically — every GetSecretValue call, every rotation event, tied to a specific IAM identity in CloudTrail — is a much easier story to tell an auditor than “we trust the deploy script.”
This guide assumes you’re setting up AWS Secrets Manager for a real application — not just clicking through the console once. We’ll build a least-privilege IAM policy first, then work through the CLI, SDK, rotation, cross-account access, replication, and integration with Lambda, ECS/Fargate, and EKS, before finishing with Terraform, monitoring, and cleanup.
Prerequisites: What You Need Before You Start
You don’t need every tool below to follow along — skip the Kubernetes prerequisites if you’re not doing Step 10, for instance. But for the full walkthrough, have these ready:
- An AWS account with billing enabled and administrator access to create IAM policies (you’ll scope down to least privilege in Step 1)
- AWS CLI v2 (latest 2.x release) installed and configured with
aws configure - Python 3.9 or later with the
boto3SDK (latest version viapip install boto3) for the programmatic-access step - Node.js 18 LTS or later for the complete working project later in this guide
- Terraform 1.x (latest stable release) if you plan to follow the infrastructure-as-code step
- kubectl and Helm 3, plus an existing EKS cluster, only if you’re following the Kubernetes integration step — see our Amazon EKS setup guide if you need one
- Basic familiarity with JSON, IAM policy syntax, and the command line
- Budget: plan for a few dollars if you follow every step and leave resources running for a day or two; the pricing breakdown below shows exactly where that comes from
Time estimate: about 80 minutes for all 13 steps, longer if you’re also standing up a fresh RDS instance or EKS cluster to rotate against.
AWS Secrets Manager Pricing in 2026: What It Actually Costs
AWS Secrets Manager has no permanent free tier — new AWS customers get up to $200 in Free Tier credits during their first 6 months, and after that you’re billed $0.40 per secret per month plus $0.05 per 10,000 API calls, prorated hourly for partial months (source: AWS Secrets Manager pricing). That’s the number most teams get surprised by: fifteen secrets across a handful of environments comes out to $6/month before you’ve made a single API call, and cross-region replicas are billed as separate secrets at the same rate.
The obvious cheaper alternative is AWS Systems Manager Parameter Store, which is genuinely free for standard-tier parameters with no call charge under standard throughput. The catch is capacity: standard parameters cap out at 4 KB and 10,000 parameters per account per Region, with no rotation, no resource policies, and no cross-account sharing. Advanced parameters lift those limits to 8 KB and 100,000 parameters and add policies and cross-account sharing — but then you’re paying $0.05 per parameter per month plus $0.05 per 10,000 API calls, which is where a lot of the pricing gap with Secrets Manager narrows (source: AWS Systems Manager pricing, Parameter Store tier documentation).
| Service | Price per secret/version per month | Price per 10,000 API calls | Free tier | Native rotation |
|---|---|---|---|---|
| AWS Secrets Manager | $0.40 | $0.05 | 30-day trial (new secrets only) | Yes — RDS, Redshift, DocumentDB, Neptune, MongoDB Atlas |
| AWS Parameter Store (Standard) | $0.00 | $0.00 (standard throughput) | Permanently free, up to 10,000 params | No |
| AWS Parameter Store (Advanced) | $0.05 | $0.05 | None | No |
| Azure Key Vault (Standard) | Included in transaction fee | $0.03 per 10,000 transactions | None published | Depends on integration |
| Google Cloud Secret Manager | $0.06 per active version | $0.03 | 6 versions + 10,000 ops/month | No (app-managed) |
| HashiCorp Vault (Open Source) | $0.00 (self-hosted) | $0.00 (self-hosted) | Fully free, self-managed | Yes — dynamic secrets engines |
Sources: AWS, AWS Systems Manager, Microsoft Azure, Google Cloud. HashiCorp’s managed HCP Vault adds a separate subscription cost on top of the free open-source core; check HashiCorp’s current pricing page for your deployment size before budgeting.
Run the numbers on a mid-size application: three environments (dev, staging, production), each with a database credential, an API key for a payment processor, and an OAuth client secret — nine secrets total. At $0.40/month plus a modest 50,000 API calls monthly across all environments, that’s $3.60 in per-secret charges plus $0.25 in API charges, or roughly $3.85/month. Add production replicated to a second Region for disaster recovery (three more secrets) and you’re at just over $5/month. That’s a trivial line item next to the cost of a single leaked production database password, which is exactly the trade-off AWS is pricing for.
In practice, the right call is usually both: Parameter Store standard tier for configuration values and feature flags that don’t need rotation, Secrets Manager for anything that touches a database, API key, or credential that should rotate on a schedule.
Step 1: Create an IAM Policy for Least-Privilege Secrets Access
Start with permissions, not the secret itself. The single most common AWS Secrets Manager misconfiguration is a policy that grants secretsmanager:GetSecretValue on Resource: "*" — every secret in the account, to every principal that happens to have that policy attached. Scope it to a path prefix and a resource tag instead:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowReadProductionAppSecrets",
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret"
],
"Resource": "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/app/*",
"Condition": {
"StringEquals": {
"secretsmanager:ResourceTag/Environment": "production"
}
}
}
]
}
Save that as secrets-read-policy.json and create it:
aws iam create-policy \
--policy-name ProdAppSecretsReadOnly \
--policy-document file://secrets-read-policy.json
Attach this to the IAM role your application or Lambda function actually runs as — not to individual developer users. Developers who need to create or rotate secrets should get a separate, broader policy that’s still scoped to a path prefix (prod/app/*, staging/app/*) rather than the whole account.
Step 2: Create Your First Secret in the AWS Console
Open the Secrets Manager console and choose Store a new secret. For a database credential, select Credentials for a database, choose the database engine, and enter the username and password. If you’re storing something that isn’t a database credential — an API key, an OAuth client secret — choose Other type of secret and add key-value pairs directly.
Name the secret with a path-style prefix that matches the IAM policy you just wrote — prod/app/db-credentials, for example. This isn’t cosmetic: it’s what makes path-prefix IAM conditions and cost-tracking-by-tag actually work later. On the next screen, choose whether to enable automatic rotation now (we’ll configure this properly in Step 5, so it’s fine to skip it here) and pick the KMS key to encrypt the secret — the account’s default aws/secretsmanager key is fine for most setups, or choose a customer-managed key if you need separate audit trails or cross-account key policies.
Review and store the secret. AWS immediately shows you sample code snippets in Python, Java, JavaScript, .NET, PHP, Ruby, and Go for retrieving that exact secret — worth copying since it saves you from typos in the ARN.
Step 3: Create and Retrieve Secrets with the AWS CLI
For anything beyond a one-off, the CLI is faster than clicking through the console. Create a secret with a full JSON payload in one command:
aws secretsmanager create-secret \
--name prod/app/db-credentials \
--description "Production database credentials for app" \
--secret-string '{"username":"appuser","password":"CHANGE-ME","host":"mydb.abc123.us-east-1.rds.amazonaws.com","port":5432,"dbname":"appdb"}'
AWS returns the ARN, name, and version ID of the new secret:
{
"ARN": "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/app/db-credentials-a1B2c3",
"Name": "prod/app/db-credentials",
"VersionId": "a1b2c3d4-5678-90ab-cdef-EXAMPLE11111"
}
Notice the ARN has a random six-character suffix appended to the name. Don’t hardcode that full ARN anywhere — if you ever force-delete and recreate the secret, the suffix changes and every hardcoded reference breaks. Reference secrets by name wherever the API accepts it.
To read it back:
aws secretsmanager get-secret-value \
--secret-id prod/app/db-credentials \
--query SecretString \
--output text
{"username":"appuser","password":"CHANGE-ME","host":"mydb.abc123.us-east-1.rds.amazonaws.com","port":5432,"dbname":"appdb"}
Pipe that through jq to pull a single field (jq -r .password) in shell scripts and CI pipelines.
Step 4: Retrieve Secrets Programmatically with Python (boto3)
Application code should never shell out to the CLI. Use the SDK directly, and cache the result — every uncached call to GetSecretValue is billed and counts against the 10,000 TPS per-Region rate limit:
import boto3
import json
from botocore.exceptions import ClientError
def get_secret(secret_name, region_name="us-east-1"):
session = boto3.session.Session()
client = session.client(service_name="secretsmanager", region_name=region_name)
try:
response = client.get_secret_value(SecretId=secret_name)
except ClientError as e:
raise e
return json.loads(response["SecretString"])
db_creds = get_secret("prod/app/db-credentials")
print(db_creds["username"])
For anything that calls this on every request instead of once at startup or on a timer, wrap it with an in-memory cache (a simple TTL dict, or functools.lru_cache with manual invalidation) so you’re not paying $0.05 per 10,000 calls for a value that changes maybe once a quarter.
Step 5: Enable Automatic Rotation with a Lambda Rotation Function
This is the feature that separates Secrets Manager from “an encrypted key-value store.” For supported engines — RDS MySQL, PostgreSQL, Oracle, SQL Server, plus Redshift, DocumentDB, Neptune, and MongoDB Atlas — AWS provides ready-made Lambda rotation function templates you deploy from the Serverless Application Repository directly in the console’s Edit rotation screen, or reference by ARN via CLI:
aws secretsmanager rotate-secret \
--secret-id prod/app/db-credentials \
--rotation-lambda-arn arn:aws:lambda:us-east-1:123456789012:function:SecretsManagerRDSPostgreSQLRotationSingleUser \
--rotation-rules AutomaticallyAfterDays=30
Under the hood, rotation runs a four-step Lambda invocation: createSecret generates a new password and stages it as AWSPENDING, setSecret applies it to the actual database, testSecret verifies the new credential works, and finishSecret flips the AWSCURRENT label to the new version. If any step fails, the old AWSCURRENT version stays active and your application never sees a broken credential — which is exactly why you should test the rotation function against a non-production secret first. A rotation Lambda with the wrong VPC configuration (no route to the database, or missing a NAT gateway/VPC endpoint to reach the Secrets Manager API) will fail silently until the next scheduled run and leave the secret stuck mid-rotation.
Step 6: Set a Resource Policy for Cross-Account Access
If a second AWS account — a shared services account, a CI/CD pipeline account — needs to read this secret, attach a resource policy instead of copying the secret into both accounts:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::999988887777:role/cross-account-reader"
},
"Action": "secretsmanager:GetSecretValue",
"Resource": "*"
}
]
}
aws secretsmanager put-resource-policy \
--secret-id prod/app/db-credentials \
--resource-policy file://resource-policy.json
Resource policies are capped at 20,480 characters, which is generous enough for dozens of cross-account principals on a single secret. The account making the request (999988887777 in this example) still needs a matching identity-based policy on its side — a resource policy alone doesn’t grant access, it only permits it.
Step 7: Turn On Cross-Region Replication
For disaster recovery or multi-region active-active applications, replicate the secret to a second Region:
aws secretsmanager replicate-secret-to-regions \
--secret-id prod/app/db-credentials \
--add-replica-regions Region=us-west-2
Replicas are read-only copies that sync automatically whenever the primary secret changes. Remember the pricing table above: each replica bills as its own secret at $0.40/month, so replicating twenty secrets to a second Region quietly doubles that line item on your bill. You also can’t delete a primary secret while replicas exist — remove the replicas first with remove-regions-from-replication, then delete the primary.
Step 8: Inject Secrets into AWS Lambda Functions
Lambda doesn’t have a native “secrets” field in its configuration the way ECS does, so the function’s execution role needs secretsmanager:GetSecretValue and calls the SDK directly, ideally once per cold start rather than per invocation:
import boto3, json, os
_secrets_client = boto3.client("secretsmanager")
_cached_secret = None
def get_db_credentials():
global _cached_secret
if _cached_secret is None:
response = _secrets_client.get_secret_value(SecretId="prod/app/db-credentials")
_cached_secret = json.loads(response["SecretString"])
return _cached_secret
def handler(event, context):
creds = get_db_credentials()
# module-level cache persists across warm invocations of the same execution environment
return {"statusCode": 200, "body": "connected as " + creds["username"]}
The module-level cache trick works because Lambda reuses the execution environment across warm invocations, so _cached_secret only gets populated on a cold start. If you’d rather not manage caching yourself, the AWS Parameters and Secrets Lambda Extension runs a local HTTP cache alongside your function and handles TTL invalidation automatically. If we haven’t covered building the surrounding API yet, see our AWS Lambda API tutorial for the full setup.
Step 9: Inject Secrets into ECS and Fargate Task Definitions
ECS and Fargate, unlike Lambda, support secrets natively at the task definition level — the container gets the value injected as an environment variable at launch, with no SDK call in your application code at all. Reference the specific JSON key you need with the :key:: suffix on the ARN:
{
"containerDefinitions": [
{
"name": "app",
"image": "123456789012.dkr.ecr.us-east-1.amazonaws.com/app:latest",
"secrets": [
{
"name": "DB_PASSWORD",
"valueFrom": "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/app/db-credentials:password::"
},
{
"name": "DB_USERNAME",
"valueFrom": "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/app/db-credentials:username::"
}
]
}
]
}
The task execution role (not the task role) needs secretsmanager:GetSecretValue for this ARN — that’s a common point of confusion since ECS has two separate IAM roles with different jobs. Omit the :password:: suffix and ECS injects the entire JSON blob as a single environment variable string instead of one field, which is rarely what you want. See our ECS vs EKS vs Fargate comparison if you’re still deciding which compute layer to run this on.
Step 10: Sync Secrets to Kubernetes with the External Secrets Operator
If your workloads run on EKS, the cleanest pattern is the open-source External Secrets Operator, which syncs AWS Secrets Manager values into native Kubernetes Secrets on a refresh interval, authenticating via IAM Roles for Service Accounts (IRSA) instead of long-lived static credentials:
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: aws-secrets-store
spec:
provider:
aws:
service: SecretsManager
region: us-east-1
auth:
jwt:
serviceAccountRef:
name: external-secrets-sa
---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-credentials
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-store
kind: SecretStore
target:
name: db-credentials
data:
- secretKey: password
remoteRef:
key: prod/app/db-credentials
property: password
Install the operator with Helm, create the IRSA-linked service account referenced above, apply the two manifests, and Kubernetes pods can mount db-credentials as a normal Secret volume with no awareness that AWS Secrets Manager exists behind it. Need the cluster itself first? Our Amazon EKS setup guide covers that from scratch.
Step 11: Manage Secrets as Code with Terraform
Clicking through the console doesn’t scale past a handful of secrets. The aws_secretsmanager_secret, aws_secretsmanager_secret_version, and aws_secretsmanager_secret_rotation resources in the official Terraform AWS provider let you define secrets, their values, and rotation schedules alongside the rest of your infrastructure:
resource "aws_secretsmanager_secret" "db_credentials" {
name = "prod/app/db-credentials"
description = "Production database credentials for app"
tags = {
Environment = "production"
}
}
resource "aws_secretsmanager_secret_version" "db_credentials" {
secret_id = aws_secretsmanager_secret.db_credentials.id
secret_string = jsonencode({
username = "appuser"
password = var.db_password
host = aws_db_instance.app.address
port = 5432
dbname = "appdb"
})
}
resource "aws_secretsmanager_secret_rotation" "db_credentials" {
secret_id = aws_secretsmanager_secret.db_credentials.id
rotation_lambda_arn = aws_lambda_function.rotator.arn
rotation_rules {
automatically_after_days = 30
}
}
Mark var.db_password as a sensitive variable so Terraform redacts it from plan and apply output, and keep the actual value out of your .tfvars file entirely — pull it from a data source or generate it with the random_password resource on first apply instead. If you’re new to the tool itself, start with our Terraform on AWS tutorial before layering secrets on top.
Step 12: Turn On CloudTrail Logging and a CloudWatch Alarm
Every Secrets Manager API call — GetSecretValue included — generates a CloudTrail event, but GetSecretValue is logged as a data event, which most accounts don’t capture by default (management events alone won’t show you who actually read a secret’s value). Enable data event logging for Secrets Manager on your trail, then build a metric filter that watches for reads of a specific secret:
aws logs put-metric-filter \
--log-group-name CloudTrail/SecretsManagerEvents \
--filter-name SecretRetrievedFilter \
--filter-pattern '{ ($.eventName = "GetSecretValue") && ($.requestParameters.secretId = "prod/app/db-credentials") }' \
--metric-transformations \
metricName=DeletedSecretAccessed,metricNamespace=CustomSecretsManager,metricValue=1
This pattern is exactly what AWS recommends for catching access to a secret that’s scheduled for deletion — useful both as a safety net during Step 13 and as an ongoing anomaly signal if a secret gets read from somewhere it shouldn’t be. Point a CloudWatch alarm at the resulting DeletedSecretAccessed metric and route it to SNS. If you’re already running AWS CloudTrail for other services, this is just one more trail configuration, not a new pipeline.
Step 13: Clean Up — Delete Secrets Safely
Secrets Manager deliberately makes deletion hard to reverse by accident. A standard delete schedules the secret for removal after a recovery window of at least 7 days — the secret is immediately inaccessible to applications, but you can still bring it back:
aws secretsmanager delete-secret \
--secret-id prod/app/db-credentials \
--recovery-window-in-days 7
aws secretsmanager restore-secret --secret-id prod/app/db-credentials
For test secrets you’re certain nothing depends on, skip the recovery window entirely:
aws secretsmanager delete-secret \
--secret-id staging/app/scratch-secret \
--force-delete-without-recovery
There’s no charge for secrets in the recovery window, so leaving the default 7-day buffer costs nothing and gives you an escape hatch. If the secret has replicas in other Regions, remove those first with remove-regions-from-replication — Secrets Manager won’t let you delete a primary secret that still has active replicas.
AWS Secrets Manager vs Parameter Store vs Azure Key Vault vs GCP Secret Manager vs Vault
The pricing table above answers “what does it cost.” This table answers “which one should I actually use,” since cost is rarely the deciding factor once you need rotation or multi-cloud support.
| Feature | AWS Secrets Manager | AWS Parameter Store | HashiCorp Vault |
|---|---|---|---|
| Max value size | 65,536 bytes | 4 KB standard / 8 KB advanced | Configurable, no hard cap |
| Native rotation | Yes, built-in Lambda templates | No | Yes, dynamic secrets engines |
| Cross-account sharing | Resource policies | Advanced tier only | Namespaces / auth methods |
| Cross-region replication | Built-in, billed per replica | Manual (copy/sync) | Manual (enterprise replication add-on) |
| Multi-cloud support | AWS only | AWS only | AWS, Azure, GCP, on-prem |
| Best fit | Rotating credentials, production databases | Static config, feature flags | Multi-cloud, dynamic short-lived credentials |
Azure Key Vault and Google Cloud Secret Manager follow roughly the same shape as AWS’s two services combined into one product each — both support secrets, keys, and certificates in a single service, and both charge per-operation rather than splitting a free config-storage tier from a paid secrets tier the way AWS does. If you’re running workloads across AWS and Azure already, our AWS vs Azure comparison covers the broader platform differences beyond just secrets. And if you’d rather self-host something simpler than Vault for smaller teams or personal infrastructure, Vaultwarden is a lighter-weight, open-source option built specifically for credential storage rather than infrastructure secrets.
The decision usually comes down to how many clouds you actually run. Single-cloud AWS shops get the most value from Secrets Manager because IAM, KMS, and CloudTrail integration are already there with zero extra plumbing. Teams split across AWS, Azure, and on-prem infrastructure tend to standardize on HashiCorp Vault specifically to avoid maintaining three separate rotation and access-control models — the operational cost of running Vault yourself is traded for a single consistent interface everywhere. There’s no universally “correct” answer here; it’s a build-versus-buy decision scoped to how many platforms your secrets actually need to live on.
Complete Working Project: A Node.js API That Loads Database Credentials at Runtime
Here’s everything assembled into a minimal but complete Express API that fetches its database credentials from Secrets Manager on startup, caches them in memory, and exposes a health check that confirms the connection actually works. Start a new project:
mkdir secrets-demo-api && cd secrets-demo-api
npm init -y
npm install express pg @aws-sdk/client-secrets-manager
Create secrets.js to handle retrieval and caching:
const { SecretsManagerClient, GetSecretValueCommand } = require("@aws-sdk/client-secrets-manager");
const client = new SecretsManagerClient({ region: "us-east-1" });
let cachedSecret = null;
async function getDbCredentials() {
if (cachedSecret) return cachedSecret;
const command = new GetSecretValueCommand({ SecretId: "prod/app/db-credentials" });
const response = await client.send(command);
cachedSecret = JSON.parse(response.SecretString);
return cachedSecret;
}
module.exports = { getDbCredentials };
Then index.js, which loads the credentials once at startup and reuses a single connection pool for every request:
const express = require("express");
const { Pool } = require("pg");
const { getDbCredentials } = require("./secrets");
const app = express();
let pool;
async function start() {
const creds = await getDbCredentials();
pool = new Pool({
host: creds.host,
port: creds.port,
user: creds.username,
password: creds.password,
database: creds.dbname,
max: 10,
});
app.get("/health", async (req, res) => {
try {
await pool.query("SELECT 1");
res.json({ status: "ok", db: "connected" });
} catch (err) {
res.status(500).json({ status: "error", message: err.message });
}
});
app.listen(3000, () => console.log("API listening on port 3000"));
}
start().catch((err) => {
console.error("Failed to start: could not load secrets", err);
process.exit(1);
});
Run it with node index.js, then confirm it against your actual secret:
curl http://localhost:3000/health
{"status":"ok","db":"connected"}
The IAM role or user running this process needs secretsmanager:GetSecretValue on that specific secret ARN — reuse the policy from Step 1. Deploy this same pattern to ECS with the Step 9 task definition (skip the SDK call entirely and read from process.env.DB_PASSWORD instead, since ECS injects it directly) or to Lambda with the Step 8 caching pattern.
Common Pitfalls When Setting Up AWS Secrets Manager
Most of the mistakes teams make with AWS Secrets Manager aren’t dramatic security failures — they’re small configuration choices that either inflate the bill or create an outage during rotation. In rough order of how often they show up:
- Hardcoding the full secret ARN, suffix included. The six-character random suffix changes if you ever force-delete and recreate a secret with the same name. Reference secrets by name, not by pinned ARN, in application config.
- Calling
GetSecretValueon every request instead of caching. This is both a cost problem ($0.05 per 10,000 calls adds up under real traffic) and a reliability problem — you’re now dependent on the Secrets Manager API being available for every single request instead of once at startup. - Using
Resource: "*"in IAM policies out of convenience. It’s faster to write during setup and much harder to audit six months later when you’re trying to figure out which of forty roles can read a production database password. - Storing non-secret configuration in Secrets Manager. Feature flags, non-sensitive environment names, and public configuration values belong in Parameter Store’s free standard tier. Paying $0.40/month per value for things that aren’t actually secret is a common source of bill creep.
- Deploying a rotation Lambda without testing it against a non-production secret first. A rotation function with the wrong VPC subnet, a missing NAT gateway, or insufficient database permissions can leave a secret stuck mid-rotation, and you won’t find out until the scheduled rotation window.
- Forgetting cross-region replicas are billed separately. Replicating fifteen secrets to a second Region for disaster recovery adds another $6/month, not a discounted “backup” rate.
- Omitting the JSON key suffix in ECS
valueFromARNs. Without:password::at the end, ECS injects the entire secret JSON blob as one environment variable string instead of the single field you wanted.
Troubleshooting AWS Secrets Manager: 10 Common Errors and Fixes
These are the errors and symptoms that come up most often once you move past the console basics and into real integrations. Most trace back to one of three root causes: an IAM policy that’s missing a permission on one side of the call, a KMS key policy that wasn’t updated when the IAM policy was, or a networking path (VPC, NAT gateway, security group) that a Lambda or ECS task can’t actually reach. Check those three first before assuming the service itself is misbehaving.
| Error or symptom | Likely cause | Fix |
|---|---|---|
| AccessDeniedException on GetSecretValue | IAM identity-based policy missing the action, or resource policy doesn’t grant it | Check both the caller’s IAM policy and the secret’s resource policy; both must allow the call |
| ResourceNotFoundException | Secret deleted, still in recovery window, or you’re querying the wrong Region | Confirm the Region in your client matches where the secret lives; restore if it’s in the recovery window |
| DecryptionFailure | IAM principal lacks kms:Decrypt on the KMS key used to encrypt the secret | Add kms:Decrypt permission for that specific key, not just secretsmanager:GetSecretValue |
| ThrottlingException | Exceeding the 50 TPS write quota, or calling PutSecretValue faster than once every 10 minutes | Add exponential backoff and retries; batch updates instead of calling PutSecretValue repeatedly |
| Rotation stuck “in progress” | Rotation Lambda timed out, has no network path to the database, or lacks database permissions | Check Lambda CloudWatch Logs for the specific step (createSecret/setSecret/testSecret/finishSecret) that failed |
| Secret value is empty or malformed in ECS container | Missing JSON key suffix on valueFrom, or wrong IAM role given permissions | Add the :key:: suffix to the ARN; verify the task execution role (not the task role) has GetSecretValue |
| “A secret with this name already exists” on create | A previous secret with the same name is still inside its recovery window | Restore and reuse it, wait out the recovery window, or force-delete it first if it’s disposable |
| External Secrets Operator shows SecretSyncedError | IRSA misconfigured — missing OIDC trust policy or wrong service account annotation | Verify the EKS cluster’s OIDC provider is registered and the IAM role trust policy matches the service account |
| Unexpectedly high monthly bill | Orphaned test/scratch secrets never cleaned up, each billed at $0.40/month regardless of use | Run ListSecrets periodically and tag everything with an owner and environment for auditing |
| CloudTrail shows no GetSecretValue events | Only management events are enabled; GetSecretValue is a data event | Enable data event logging for Secrets Manager specifically on your CloudTrail trail |
Advanced Tips for Production-Grade Secrets Management
Once the basics are running, these are the changes that separate a “it works” setup from one that holds up under audit and scale:
- Move from per-secret IAM policies to attribute-based access control (ABAC). Tag every secret with
Environment,Team, andApp, then write one policy per team that referencessecretsmanager:ResourceTag/*conditions instead of maintaining a growing list of hardcoded ARNs. - Use VPC interface endpoints (PrivateLink) for Secrets Manager. Lambda and ECS tasks in private subnets can reach the Secrets Manager API without a NAT gateway or any path to the public internet, which is both cheaper and reduces the network attack surface.
- Deploy the AWS Parameters and Secrets Lambda Extension instead of hand-rolling a caching layer — it runs a local HTTP server inside the Lambda execution environment and handles TTL-based cache invalidation for you.
- Design applications around staging labels, not raw values. Reading
AWSCURRENTexplicitly (rather than assuming “whatever GetSecretValue returns”) makes zero-downtime rotation actually zero-downtime, since your app and the rotation Lambda agree on what “current” means. - Feed Secrets Manager into AWS Config or Security Hub. A custom Config rule that flags secrets without rotation enabled, or without a rotation event in the last 90 days, turns “we should rotate credentials” into something a dashboard actually tracks.
- Centralize secrets in a dedicated security account for multi-account organizations, and share into workload accounts via resource policies plus AWS Resource Access Manager, instead of duplicating the same database credential in five accounts with five separate rotation schedules to keep in sync.
Frequently Asked Questions
Is AWS Secrets Manager free?
No, not on an ongoing basis. New secrets get a 30-day free trial, and after that AWS bills $0.40 per secret per month plus $0.05 per 10,000 API calls (source: AWS pricing). For static configuration that doesn’t need rotation, AWS Systems Manager Parameter Store’s standard tier is permanently free.
What’s the real difference between AWS Secrets Manager and Parameter Store?
Native automatic rotation, cross-region replication, and resource-based policies for cross-account access are all exclusive to Secrets Manager. Parameter Store’s advantage is the standard tier being free indefinitely, at the cost of no rotation and lower size and count limits (4 KB, 10,000 parameters) unless you upgrade to the paid advanced tier (8 KB, 100,000 parameters).
What is the maximum size of a secret in AWS Secrets Manager?
65,536 bytes (64 KB) per secret value, per the official AWS Secrets Manager quotas documentation. That’s enough for a JSON blob with dozens of fields, but not for large certificate bundles or binary files — use S3 with a Secrets Manager pointer for those.
Can I use AWS Secrets Manager with Kubernetes?
Yes, most commonly through the open-source External Secrets Operator (covered in Step 10), which syncs values into native Kubernetes Secrets on a refresh interval. The AWS Secrets and Configuration Provider (ASCP) for the Secrets Store CSI Driver is the other common route, mounting secrets as files directly into pods instead of creating a Kubernetes Secret object.
Which databases support automatic rotation out of the box?
Amazon RDS for MySQL, PostgreSQL, Oracle, and SQL Server, plus Redshift, DocumentDB, Neptune, and MongoDB Atlas all have pre-built Lambda rotation templates (source: AWS rotation documentation). Anything else needs a custom rotation Lambda built against the generic four-step rotation contract.
Does AWS Secrets Manager encrypt data at rest?
Yes, every secret is encrypted with AWS KMS, either the account’s default aws/secretsmanager key or a customer-managed key you specify. The IAM principal reading the secret needs both secretsmanager:GetSecretValue and kms:Decrypt on that specific key.
Can I recover a secret after deleting it?
Yes, as long as you didn’t use --force-delete-without-recovery. The default recovery window is a minimum of 7 days, during which restore-secret brings it back fully intact. There’s no charge for secrets sitting in the recovery window.
How much does cross-region replication cost?
Each replica is billed as a full separate secret at the standard $0.40/month rate — there’s no discounted “replica” pricing tier. Replicating ten secrets to one additional Region adds roughly $4/month on top of the primary cost.
Getting AWS Secrets Manager running takes an afternoon. Getting it running well — least-privilege IAM from the start, rotation tested before it’s relied on, replicas and API calls accounted for in the budget, and CloudTrail actually watching who reads what — is the difference between a service that quietly does its job and one that shows up as a surprise line item or, worse, a surprise breach report. Start with Step 1’s IAM policy, resist the urge to widen it “just for now,” and the rest of this setup holds up under an audit a lot better than a shared .env file ever did.
Related Coverage
- How to Set Up AWS CloudTrail: 13 Steps, 70 Min [2026]
- How to Build an AWS Lambda API: 12 Steps, 60 Min [2026]
- How to Set Up Amazon EKS: 12 Steps, 90 Min [2026]
- Terraform Tutorial: Provision AWS in 13 Steps [2026]
- ECS vs EKS vs Fargate: $0 vs $73/mo Control Plane [2026]
- AWS vs Azure 2026: 31% vs 24% Market Share and a 75% Archive Cost Gap [Tested]
- How to Set Up Vaultwarden: 12 Steps, 80 Min [2026]
For more cloud infrastructure tutorials and comparisons, browse the full Cloud Computing section.


