AWS SSM Parameter Store
Hierarchical config + secret storage. Part of AWS Systems Manager. The cheaper, simpler sibling of Secrets Manager — and “which one do I use?” is a guaranteed interview question.
Parameter types
| Type | Encrypted? | Use for |
|---|---|---|
String |
no | plain config — feature flags, URLs, log levels |
StringList |
no | comma-separated lists |
SecureString |
yes (KMS) | secrets — passwords, API keys, tokens |
SecureString encrypts the value with KMS (default aws/ssm key or a customer-managed key). Reading it requires both ssm:GetParameter and kms:Decrypt on the key.
Standard vs Advanced tier
| Standard | Advanced | |
|---|---|---|
| Cost | free | ~$0.05 per parameter / month |
| Max value size | 4 KB | 8 KB |
| Parameters per account | 10,000 | 100,000 |
| Parameter policies (expiration, notifications) | no | yes |
| Higher throughput | shared | optional higher-throughput setting (paid) |
Default to Standard. Go Advanced only when you need >4 KB values, >10k parameters, or parameter policies (e.g., “expire this param in 30 days”, “notify if not changed in 90 days”).
Hierarchical organization
Parameters are paths. Organize by environment / service / key:
/myapp/prod/database/url
/myapp/prod/database/password (SecureString)
/myapp/prod/feature-flags/new-checkout
/myapp/staging/database/url
GetParametersByPath fetches a whole subtree in one call — load all of /myapp/prod/ at startup:
import boto3
ssm = boto3.client("ssm")
resp = ssm.get_parameters_by_path(
Path="/myapp/prod/",
Recursive=True,
WithDecryption=True, # decrypt SecureStrings
)
config = {p["Name"]: p["Value"] for p in resp["Parameters"]}
# paginate if > 10 params — use the paginator
IAM can be scoped to a path prefix — prod role can read /myapp/prod/*, staging role only /myapp/staging/*.
Parameter Store vs Secrets Manager
The decision matrix:
| Parameter Store | Secrets Manager | |
|---|---|---|
| Cost | free (Standard tier) | ~$0.40 per secret / month + per-API-call |
| Automatic rotation | no (you’d build it with EventBridge + Lambda) | yes — built-in, RDS-native rotation, custom Lambda rotation |
| Max value size | 4 KB (8 KB Advanced) | 64 KB |
| Cross-region replication | no (manual) | yes (built-in) |
| Versioning | yes (numeric versions) | yes (version stages: AWSCURRENT/PENDING/PREVIOUS) |
| Resource policies (cross-account) | no | yes |
| Generates random secrets | no | yes (get_random_password) |
| Hierarchical paths | yes | no (flat names, slashes are just naming) |
| Plain (non-secret) config | yes — this is its sweet spot | overkill |
Decision shortcut:
- Plain config (URLs, flags, tuning params) → Parameter Store
String. Always. Secrets Manager would be wasteful. - Secrets that don’t need rotation (third-party API keys you set once) → Parameter Store
SecureString— free, encrypted, fine. - Secrets that need automatic rotation (DB passwords, especially RDS) → Secrets Manager — the built-in rotation is the whole value proposition.
- Cross-account secret sharing, >4 KB secrets, version-staged rotation testing → Secrets Manager.
A common pattern: most config + static secrets in Parameter Store; only the rotation-critical credentials (RDS password) in Secrets Manager. You pay $0.40/mo per rotating secret instead of per every config value.
Note: Secrets Manager can reference Parameter Store and vice-versa in some integrations, but treat them as separate stores with the decision above.
Reading parameters in an app
import boto3
ssm = boto3.client("ssm")
# Single parameter
db_url = ssm.get_parameter(Name="/myapp/prod/database/url")["Parameter"]["Value"]
# Single SecureString — decrypt it
db_pw = ssm.get_parameter(
Name="/myapp/prod/database/password", WithDecryption=True
)["Parameter"]["Value"]
# Many at once
resp = ssm.get_parameters(
Names=["/myapp/prod/database/url", "/myapp/prod/database/password"],
WithDecryption=True,
)
Don’t call get_parameter on every request. Like Secrets Manager, fetch once at startup / cold start and cache. For Lambda, the AWS Parameters and Secrets Lambda Extension provides a local cache + HTTP endpoint so you avoid hitting the SSM API on every invocation:
GET http://localhost:2773/systemsmanager/parameters/get?name=/myapp/prod/database/url
Versioning
Every update creates a new numeric version. You can read a specific version (Name:3) or use parameter labels for named versions. Useful for “roll back this config value” without keeping a separate history.
EnvVar injection in ECS / Lambda
Both ECS task definitions and Lambda can pull Parameter Store values at launch and inject them as environment variables — no SDK call in your code:
// ECS task definition
"secrets": [
{"name": "DATABASE_URL", "valueFrom": "arn:aws:ssm:us-east-1:123:parameter/myapp/prod/database/url"},
{"name": "DB_PASSWORD", "valueFrom": "arn:aws:ssm:us-east-1:123:parameter/myapp/prod/database/password"}
]
ECS resolves these at task start (the execution role needs ssm:GetParameters + kms:Decrypt). The container sees plain env vars. Same idea works with Secrets Manager ARNs.
Common gotchas
- Calling
get_parameterper request — API rate limits + latency. Cache at startup; use the Lambda extension. - Forgetting
WithDecryption=True—SecureStringreturns the ciphertext, not the value. - Missing
kms:Decrypt— the role needs bothssm:GetParameterandkms:Decrypton the encryption key. - Standard-tier throughput — shared account-wide throughput; a chatty app can throttle. Cache, or enable the Advanced higher-throughput setting.
- Using Secrets Manager for plain config — wasteful at $0.40/secret/mo. Parameter Store
Stringis free. - Using Parameter Store for rotating DB credentials — you’d have to build the rotation yourself; Secrets Manager does it natively. Use the right tool.
- Path-based IAM mistakes —
arn:.../parameter/myapp/prod/*scopes correctly; forgetting the leading/in the parameter name breaks the ARN match.
Interview angle
- “Parameter Store vs Secrets Manager — when each?” — Parameter Store for plain config (free) and static secrets (
SecureString, free, encrypted). Secrets Manager when you need automatic rotation (especially RDS-native), cross-account sharing, >4 KB values, or version-staged rotation. Common pattern: config + static secrets in Parameter Store, only rotation-critical credentials in Secrets Manager. - “How do you give a service its config without baking it into the image?” — store under a path like
/myapp/prod/*; either inject into env vars via the ECS task definition / Lambda config at launch, or fetch withget_parameters_by_pathat startup. IAM scoped to the path prefix. - “SecureString — how does it work?” — value encrypted with KMS.
WithDecryption=Trueon read returns the plaintext; the caller needsssm:GetParameterandkms:Decrypton the key. - “How do you avoid hitting the SSM API on every request?” — fetch + cache at startup/cold start; for Lambda use the AWS Parameters and Secrets Lambda Extension (local cache + localhost HTTP endpoint).
- “Standard vs Advanced tier?” — Standard is free, 4 KB values, 10k params. Advanced is paid, 8 KB values, 100k params, plus parameter policies (expiration, change notifications). Default to Standard.