AWS Parameter Store vs Secrets Manager in 2026: Hierarchical Config, Rotation, and Terraform
Compare AWS SSM Parameter Store vs Secrets Manager: use cases, cost, rotation, hierarchical configuration paths, Lambda and ECS integration, and Terraform configuration.
AWS Parameter Store vs Secrets Manager in 2026: Hierarchical Config, Rotation, and Terraform
Two AWS services handle application secrets and configuration: SSM Parameter Store and Secrets Manager. Teams frequently pick the wrong one, pay too much, or miss rotation features. The choice comes down to: do you need automatic rotation with Lambda? Use Secrets Manager. Otherwise, Parameter Store is cheaper, simpler, and more flexible for hierarchical configuration.
This post covers both services, when to use each, hierarchical parameter paths, reading from Lambda and ECS, automatic secret rotation, and the Terraform configuration for both.
Quick Comparison
| Feature | Parameter Store | Secrets Manager |
|---|---|---|
| Cost | Free (standard), $0.05/10K API calls (advanced) | $0.40/secret/month + $0.05/10K API calls |
| Secret rotation | Manual only | Automatic (Lambda-based) |
| Versioning | Yes (parameter history) | Yes (with staging labels) |
| Cross-region replication | No | Yes |
| Hierarchical paths | Yes (/app/prod/db-url) | No (flat namespace) |
| Max value size | 4KB (standard), 8KB (advanced) | 64KB |
| JSON support | Store as String, parse manually | Native JSON with named keys |
| CloudFormation integration | ✅ | ✅ |
| Lambda Layer for rotation | No (manual only) | Yes (built-in for common services) |
Rule of thumb:
- Database passwords, API keys that need automatic rotation → Secrets Manager
- Environment config, non-rotating keys, large hierarchies → Parameter Store
Parameter Store: Hierarchical Configuration
# terraform/parameters.tf
locals {
prefix = "/${var.name}/${var.environment}"
}
# Non-sensitive config: String type, free tier
resource "aws_ssm_parameter" "app_config" {
for_each = {
"LOG_LEVEL" = var.environment == "production" ? "WARN" : "DEBUG"
"ALLOWED_ORIGINS" = "https://viprasol.com,https://app.viprasol.com"
"MAX_UPLOAD_SIZE" = "52428800" # 50MB in bytes
"FEATURE_FLAGS_URL" = "https://flags.viprasol.com"
}
name = "${local.prefix}/config/${each.key}"
type = "String"
value = each.value
tags = var.common_tags
}
# Sensitive config: SecureString type (encrypted with KMS)
resource "aws_ssm_parameter" "app_secrets" {
name = "${local.prefix}/secrets/STRIPE_WEBHOOK_SECRET"
type = "SecureString"
value = var.stripe_webhook_secret # From Terraform variable, stored in .tfvars
key_id = aws_kms_key.parameter_store.arn # Custom KMS key
tags = var.common_tags
lifecycle {
ignore_changes = [value] # Managed externally after initial creation
}
}
resource "aws_ssm_parameter" "resend_api_key" {
name = "${local.prefix}/secrets/RESEND_API_KEY"
type = "SecureString"
value = var.resend_api_key
key_id = aws_kms_key.parameter_store.arn
tags = var.common_tags
lifecycle {
ignore_changes = [value]
}
}
# KMS key for Parameter Store encryption
resource "aws_kms_key" "parameter_store" {
description = "${var.name}-${var.environment}-parameter-store"
enable_key_rotation = true
deletion_window_in_days = 7
tags = var.common_tags
}
resource "aws_kms_alias" "parameter_store" {
name = "alias/${var.name}-${var.environment}-parameter-store"
target_key_id = aws_kms_key.parameter_store.key_id
}
☁️ Is Your Cloud Costing Too Much?
Most teams overspend 30–40% on cloud — wrong instance types, no reserved pricing, bloated storage. We audit, right-size, and automate your infrastructure.
- AWS, GCP, Azure certified engineers
- Infrastructure as Code (Terraform, CDK)
- Docker, Kubernetes, GitHub Actions CI/CD
- Typical audit recovers $500–$3,000/month in savings
Secrets Manager: Database Password with Auto-Rotation
# terraform/secrets.tf
# Database credentials in Secrets Manager (supports auto-rotation)
resource "aws_secretsmanager_secret" "db_credentials" {
name = "${var.name}/${var.environment}/db-credentials"
recovery_window_in_days = 7 # 7-day deletion window
replica {
region = "eu-west-1" # Replicate to EU for disaster recovery
}
tags = var.common_tags
}
resource "aws_secretsmanager_secret_version" "db_credentials" {
secret_id = aws_secretsmanager_secret.db_credentials.id
secret_string = jsonencode({
username = aws_rds_cluster.main.master_username
password = aws_rds_cluster.main.master_password
host = aws_rds_cluster.main.endpoint
port = aws_rds_cluster.main.port
dbname = aws_rds_cluster.main.database_name
})
}
# Enable automatic rotation (uses AWS-managed Lambda for RDS)
resource "aws_secretsmanager_secret_rotation" "db_credentials" {
secret_id = aws_secretsmanager_secret.db_credentials.id
rotation_lambda_arn = "arn:aws:lambda:${var.region}:${data.aws_caller_identity.current.account_id}:function:SecretsManagerRDSPostgreSQLRotationSingleUser"
rotation_rules {
automatically_after_days = 30
}
}
IAM Policies for Access
# IAM policy: allow ECS/Lambda to read parameters in their path
data "aws_iam_policy_document" "read_parameters" {
# Parameter Store: read all params under this app's prefix
statement {
actions = ["ssm:GetParameter", "ssm:GetParameters", "ssm:GetParametersByPath"]
resources = [
"arn:aws:ssm:${var.region}:${data.aws_caller_identity.current.account_id}:parameter/${var.name}/${var.environment}/*"
]
}
# KMS: decrypt SecureString parameters
statement {
actions = ["kms:Decrypt"]
resources = [aws_kms_key.parameter_store.arn]
}
}
data "aws_iam_policy_document" "read_secrets" {
# Secrets Manager: read the database secret
statement {
actions = ["secretsmanager:GetSecretValue"]
resources = [aws_secretsmanager_secret.db_credentials.arn]
}
}
resource "aws_iam_role_policy" "ecs_parameters" {
name = "read-parameters"
role = aws_iam_role.ecs_task.id
policy = data.aws_iam_policy_document.read_parameters.json
}
resource "aws_iam_role_policy" "ecs_secrets" {
name = "read-secrets"
role = aws_iam_role.ecs_task.id
policy = data.aws_iam_policy_document.read_secrets.json
}
⚙️ DevOps Done Right — Zero Downtime, Full Automation
Ship faster without breaking things. We build CI/CD pipelines, monitoring stacks, and auto-scaling infrastructure that your team can actually maintain.
- Staging + production environments with feature flags
- Automated security scanning in the pipeline
- Uptime monitoring + alerting + runbook automation
- On-call support handover docs included
Reading Parameters in Node.js
// lib/config/parameter-store.ts
import {
SSMClient,
GetParametersByPathCommand,
GetParameterCommand,
} from "@aws-sdk/client-ssm";
import {
SecretsManagerClient,
GetSecretValueCommand,
} from "@aws-sdk/client-secrets-manager";
const ssm = new SSMClient({ region: process.env.AWS_REGION });
const secretsManager = new SecretsManagerClient({ region: process.env.AWS_REGION });
// Cache for Lambda warm invocations (refresh every 5 min)
const cache = new Map<string, { value: string; expiresAt: number }>();
const CACHE_TTL_MS = 5 * 60 * 1000;
export async function getParameter(name: string, decrypt = true): Promise<string> {
const cached = cache.get(name);
if (cached && cached.expiresAt > Date.now()) return cached.value;
const { Parameter } = await ssm.send(new GetParameterCommand({
Name: name,
WithDecryption: decrypt,
}));
const value = Parameter?.Value ?? "";
cache.set(name, { value, expiresAt: Date.now() + CACHE_TTL_MS });
return value;
}
// Bulk load all parameters for an app prefix
export async function loadConfigFromParameterStore(prefix: string) {
const params: Record<string, string> = {};
let nextToken: string | undefined;
do {
const { Parameters, NextToken } = await ssm.send(
new GetParametersByPathCommand({
Path: prefix,
Recursive: true,
WithDecryption: true,
NextToken: nextToken,
})
);
for (const param of Parameters ?? []) {
if (param.Name && param.Value) {
// Strip prefix from key: /myapp/prod/secrets/STRIPE_KEY → STRIPE_KEY
const key = param.Name.replace(`${prefix}/`, "").replace(/\//g, "_");
params[key] = param.Value;
}
}
nextToken = NextToken;
} while (nextToken);
return params;
}
// Read from Secrets Manager (JSON secrets)
export async function getDatabaseCredentials(): Promise<{
username: string;
password: string;
host: string;
port: number;
dbname: string;
}> {
const cacheKey = "db-credentials";
const cached = cache.get(cacheKey);
if (cached && cached.expiresAt > Date.now()) {
return JSON.parse(cached.value);
}
const { SecretString } = await secretsManager.send(
new GetSecretValueCommand({
SecretId: process.env.DB_SECRET_ARN!,
})
);
const credentials = JSON.parse(SecretString ?? "{}");
cache.set(cacheKey, {
value: SecretString ?? "{}",
expiresAt: Date.now() + CACHE_TTL_MS,
});
return credentials;
}
ECS: Inject Parameters as Environment Variables
# ECS task definition: inject Parameter Store values as env vars
resource "aws_ecs_task_definition" "app" {
family = "${var.name}-${var.environment}"
container_definitions = jsonencode([{
name = "app"
image = var.image_uri
environment = [
{ name = "NODE_ENV", value = var.environment },
{ name = "PORT", value = "3000" },
]
# Inject from Parameter Store (SecureString) — decrypted at container start
secrets = [
{
name = "STRIPE_WEBHOOK_SECRET"
valueFrom = aws_ssm_parameter.app_secrets["STRIPE_WEBHOOK_SECRET"].arn
# Or from Secrets Manager:
# valueFrom = "${aws_secretsmanager_secret.db_credentials.arn}:password::"
},
{
name = "RESEND_API_KEY"
valueFrom = aws_ssm_parameter.resend_api_key.arn
},
]
}])
execution_role_arn = aws_iam_role.ecs_execution.arn # Must have SSM/KMS access
task_role_arn = aws_iam_role.ecs_task.arn
}
CLI: Managing Parameters
# Write a parameter
aws ssm put-parameter \
--name "/myapp/production/secrets/STRIPE_API_KEY" \
--value "sk_live_..." \
--type "SecureString" \
--key-id "alias/myapp-production-parameter-store" \
--overwrite
# Read a parameter
aws ssm get-parameter \
--name "/myapp/production/secrets/STRIPE_API_KEY" \
--with-decryption \
--query "Parameter.Value" \
--output text
# List all parameters for an app
aws ssm get-parameters-by-path \
--path "/myapp/production" \
--recursive \
--with-decryption \
--query "Parameters[*].{Name:Name,Value:Value}"
# Rotate a Secrets Manager secret immediately
aws secretsmanager rotate-secret \
--secret-id "myapp/production/db-credentials"
Cost Analysis
| Scenario | Parameter Store | Secrets Manager |
|---|---|---|
| 10 secrets, 100K reads/month | Free | $4.00 + $0.50 = $4.50 |
| 50 secrets, 1M reads/month | $5.00 (advanced) | $20.00 + $5.00 = $25.00 |
| With auto-rotation (RDS) | Not available | $0.40/secret + rotation Lambda |
| Cross-region replication | Not available | $0.40/replica/month |
For most SaaS products: use Parameter Store for all non-rotating secrets and config, Secrets Manager only for database credentials that need auto-rotation.
See Also
- AWS Secrets Manager — Deeper rotation patterns and cross-account access
- Terraform State Management — Storing Terraform secrets safely
- AWS ECS Fargate Production — Injecting secrets into ECS containers
- AWS Lambda Container — Lambda reading from Parameter Store
Working With Viprasol
We design and implement AWS secret management strategies for SaaS products — from initial setup through automatic rotation and cross-region replication. Our cloud team has migrated clients from .env files and hardcoded credentials to fully managed Parameter Store hierarchies.
What we deliver:
- Parameter Store hierarchy design (
/app/env/category/key) - KMS key setup for SecureString encryption
- Secrets Manager with automatic RDS rotation
- IAM policy setup (least-privilege per service)
- ECS/Lambda secrets injection via
secretsblock in task definition - Terraform module for the complete secret management stack
See our cloud infrastructure services or contact us to secure your application secrets.
About the Author
Viprasol Tech Team
Custom Software Development Specialists
The Viprasol Tech team specialises in algorithmic trading software, AI agent systems, and SaaS development. With 100+ projects delivered across MT4/MT5 EAs, fintech platforms, and production AI systems, the team brings deep technical experience to every engagement. Based in India, serving clients globally.
Need DevOps & Cloud Expertise?
Scale your infrastructure with confidence. AWS, GCP, Azure certified team.
Free consultation • No commitment • Response within 24 hours
Making sense of your data at scale?
Viprasol builds end-to-end big data analytics solutions — ETL pipelines, data warehouses on Snowflake or BigQuery, and self-service BI dashboards. One reliable source of truth for your entire organisation.