Back to Blog

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.

Viprasol Tech Team
February 18, 2027
13 min read

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

FeatureParameter StoreSecrets Manager
CostFree (standard), $0.05/10K API calls (advanced)$0.40/secret/month + $0.05/10K API calls
Secret rotationManual onlyAutomatic (Lambda-based)
VersioningYes (parameter history)Yes (with staging labels)
Cross-region replicationNoYes
Hierarchical pathsYes (/app/prod/db-url)No (flat namespace)
Max value size4KB (standard), 8KB (advanced)64KB
JSON supportStore as String, parse manuallyNative JSON with named keys
CloudFormation integration
Lambda Layer for rotationNo (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

ScenarioParameter StoreSecrets Manager
10 secrets, 100K reads/monthFree$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 replicationNot 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


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 secrets block in task definition
  • Terraform module for the complete secret management stack

See our cloud infrastructure services or contact us to secure your application secrets.

Share this article:

About the Author

V

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.

MT4/MT5 EA DevelopmentAI Agent SystemsSaaS DevelopmentAlgorithmic Trading

Need DevOps & Cloud Expertise?

Scale your infrastructure with confidence. AWS, GCP, Azure certified team.

Free consultation • No commitment • Response within 24 hours

Viprasol · Big Data & Analytics

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.