AWS SSM Parameter Store and Session Manager Guide
June 6, 2026 | 18 min read
What Is AWS Systems Manager?
AWS Systems Manager (SSM) is a management service that gives you visibility and control over your AWS infrastructure at scale. It consolidates operational tasks — configuration management, secret storage, remote access, command execution, and patching — into a single unified console, eliminating the need for bastion hosts, hardcoded credentials, or disparate third-party tools.
SSM is not one product; it is a suite of over a dozen integrated capabilities. This guide focuses on the five most impactful ones for day-to-day operations:
- Parameter Store — hierarchical key-value store for configuration and secrets
- Session Manager — interactive shell access to EC2 and on-premises servers without SSH ports or bastion hosts
- Run Command — execute scripts and commands across a fleet of instances
- Patch Manager — automated patching with compliance reporting
- Automation Documents — runbooks that orchestrate multi-step operational workflows
SSM Agent
All SSM capabilities depend on the SSM Agent — a lightweight daemon installed on every managed node. Amazon Linux 2, Amazon Linux 2023, Ubuntu 20.04+, Windows Server 2016+, and most AWS-managed AMIs ship with the agent pre-installed and running. For older or custom AMIs, install it manually:
# Amazon Linux 2 / RHEL / CentOS
sudo yum install -y amazon-ssm-agent
sudo systemctl enable amazon-ssm-agent
sudo systemctl start amazon-ssm-agent
# Ubuntu / Debian
sudo snap install amazon-ssm-agent --classic
sudo snap start amazon-ssm-agent
IAM Prerequisites
The EC2 instance (or on-premises server) must carry an IAM role with the AmazonSSMManagedInstanceCore managed policy attached. This policy grants the agent permission to communicate with the SSM service endpoint, write session logs, and read Parameter Store values that the instance is authorised to access.
EC2-SSM-InstanceProfile), attach AmazonSSMManagedInstanceCore plus any application-specific Parameter Store read policies, and associate that profile with every EC2 instance at launch. See IAM Roles & Policies for how to create and attach instance profiles.
Parameter Store
Parameter Store provides durable, encrypted storage for configuration data and secrets. Parameters live in a hierarchical namespace (similar to a file system path) and can be versioned, referenced in CloudFormation stacks, injected into ECS task definitions, and read at runtime by Lambda functions and EC2 applications.
Tiers: Standard vs Advanced
| Feature | Standard | Advanced |
|---|---|---|
| Max parameter size | 4 KB | 8 KB |
| Max parameters per account/region | 10,000 | 100,000 |
| Parameter policies (TTL, expiry alerts) | No | Yes |
| Cost | Free for standard API calls | $0.05 per advanced parameter/month |
Standard tier covers the vast majority of use cases. Upgrade to Advanced only when you need parameter policies (e.g., automatic expiry notifications) or exceed the 10,000-parameter limit.
Parameter Types
- String — plain text; any UTF-8 string up to the tier limit.
- StringList — comma-separated list of strings; useful for allow-lists or multi-value configs.
- SecureString — value encrypted at rest with an AWS KMS key (default alias
aws/ssmor a customer-managed key). Only the KMSDecryptpermission holder can retrieve the plaintext value.
Hierarchical Naming Convention
A well-structured hierarchy makes bulk retrieval and IAM path-based policies straightforward:
/<app>/<environment>/<parameter-name>
Examples:
/myapp/prod/db-host
/myapp/prod/db-port
/myapp/prod/db-password ← SecureString
/myapp/staging/db-host
/myapp/staging/db-password ← SecureString
/shared/smtp-relay-host
CLI: Creating, Reading, and Deleting Parameters
1. Create a plain-text parameter:
aws ssm put-parameter \
--name "/myapp/prod/db-host" \
--value "mydb.cluster-abc123.us-east-1.rds.amazonaws.com" \
--type String \
--description "Production RDS cluster endpoint" \
--tags Key=App,Value=myapp Key=Env,Value=prod
2. Create a SecureString parameter using the default SSM KMS key:
aws ssm put-parameter \
--name "/myapp/prod/db-password" \
--value "S3cur3P@ssw0rd!" \
--type SecureString \
--description "Production RDS master password"
3. Read a single parameter (with decryption):
aws ssm get-parameter \
--name "/myapp/prod/db-password" \
--with-decryption \
--query "Parameter.Value" \
--output text
4. Bulk read — all parameters under a path:
aws ssm get-parameters-by-path \
--path "/myapp/prod/" \
--recursive \
--with-decryption \
--query "Parameters[*].{Name:Name,Value:Value}" \
--output table
5. Update an existing parameter (overwrite):
aws ssm put-parameter \
--name "/myapp/prod/db-password" \
--value "N3wP@ssw0rd!2026" \
--type SecureString \
--overwrite
6. Delete a parameter:
aws ssm delete-parameter --name "/myapp/prod/db-password"
CloudFormation Integration
CloudFormation can resolve Parameter Store values at stack create/update time using the resolve: dynamic reference syntax:
Resources:
MyRDSInstance:
Type: AWS::RDS::DBInstance
Properties:
DBInstanceClass: db.t3.medium
MasterUsername: admin
MasterUserPassword: "{{resolve:ssm-secure:/myapp/prod/db-password}}"
DBName: myappdb
Engine: mysql
EngineVersion: "8.0"
resolve:ssm-secure: for SecureString parameters. CloudFormation decrypts the value at deployment time; it is never stored in the stack template in plaintext.
Parameter Store vs Secrets Manager
Both services store secrets securely, but they serve different needs. The table below will help you choose:
| Capability | SSM Parameter Store (SecureString) | Secrets Manager |
|---|---|---|
| Cost | Free (standard) or $0.05/param/month (advanced) | $0.40/secret/month + $0.05 per 10,000 API calls |
| Automatic rotation | No (must build custom Lambda) | Yes — built-in for RDS, Redshift, DocumentDB |
| Cross-account access | Requires custom KMS key + resource-based policy | Native resource-based policy support |
| Max value size | 4 KB (standard) / 8 KB (advanced) | 64 KB |
| Versioning | Integer versions, no labels by default | Staging labels (AWSCURRENT, AWSPENDING, AWSPREVIOUS) |
| Best for | App config, non-rotating secrets, hierarchical namespaces | Database credentials, API keys that need automatic rotation |
Reading Parameters from Lambda with Python boto3
A common pattern is for a Lambda function to read database credentials from Parameter Store at cold-start and cache them for the lifetime of the execution environment. This avoids hardcoding credentials in environment variables or code.
import boto3
import os
import json
from functools import lru_cache
ssm = boto3.client('ssm', region_name=os.environ.get('AWS_REGION', 'us-east-1'))
@lru_cache(maxsize=None)
def get_db_config():
"""
Reads all /myapp/prod/ parameters in a single API call.
@lru_cache ensures we only hit SSM once per Lambda execution environment.
"""
response = ssm.get_parameters_by_path(
Path='/myapp/prod/',
Recursive=True,
WithDecryption=True # required for SecureString parameters
)
# Build a flat dict keyed by the last path segment
# e.g. /myapp/prod/db-password -> {'db-password': 'S3cur3...'}
config = {}
for param in response['Parameters']:
key = param['Name'].rsplit('/', 1)[-1]
config[key] = param['Value']
return config
def lambda_handler(event, context):
cfg = get_db_config()
db_host = cfg['db-host']
db_port = int(cfg.get('db-port', 5432))
db_password = cfg['db-password']
# Use credentials to connect — shown conceptually
print(f"Connecting to {db_host}:{db_port}")
# ... application logic ...
return {
'statusCode': 200,
'body': json.dumps({'status': 'ok'})
}
ssm:GetParametersByPath and kms:Decrypt (for the KMS key used to encrypt the SecureString) in its IAM policy. See Lambda Serverless Guide for execution role setup.
Session Manager
Session Manager provides interactive, browser-based or CLI-driven shell access to EC2 instances and on-premises servers — with no open inbound ports, no bastion hosts, and no SSH key pairs required. All session activity is tunnelled through the SSM service over HTTPS (port 443 outbound only) and can be fully audited.
Architecture and Prerequisites
For instances in public subnets, the SSM Agent connects directly to the SSM service regional endpoint over the internet. For instances in private subnets with no internet gateway, you must create three VPC interface endpoints:
com.amazonaws.<region>.ssmcom.amazonaws.<region>.ssmmessagescom.amazonaws.<region>.ec2messages
Attach these endpoints to the private subnet's security group. The endpoint security group must allow inbound HTTPS (443) from the instance security group. See VPC Networking Guide for VPC endpoint creation steps.
Starting a Session via CLI
Install the Session Manager Plugin for the AWS CLI, then start a session:
# Install Session Manager plugin (Linux)
curl "https://s3.amazonaws.com/session-manager-downloads/plugin/latest/ubuntu_64bit/session-manager-plugin.deb" \
-o "session-manager-plugin.deb"
sudo dpkg -i session-manager-plugin.deb
# Start an interactive session (replaces SSH)
aws ssm start-session \
--target i-0abc1234def567890 \
--region us-east-1
# You get a bash/sh shell prompt on the remote instance
# Session ID: avi-0e9f1234567890abc
# Starting session with SessionId: avi-0e9f1234567890abc
Port Forwarding
Forward a remote port to your local machine — ideal for accessing private RDS endpoints, Redis clusters, or internal HTTP services without a VPN:
# Forward remote port 5432 (PostgreSQL) to local port 15432
aws ssm start-session \
--target i-0abc1234def567890 \
--document-name AWS-StartPortForwardingSession \
--parameters '{"portNumber":["5432"],"localPortNumber":["15432"]}'
# Now connect your local psql client to localhost:15432
psql -h 127.0.0.1 -p 15432 -U admin -d myappdb
SSH Tunnelling Through Session Manager
You can route native SSH traffic through Session Manager by adding a ProxyCommand entry to your SSH config. This means standard SSH tooling (SCP, SFTP, VS Code Remote-SSH) works without opening port 22:
# ~/.ssh/config
Host i-* mi-*
ProxyCommand sh -c "aws ssm start-session \
--target %h \
--document-name AWS-StartSSHSession \
--parameters 'portNumber=%p'"
StrictHostKeyChecking no
User ec2-user
IdentityFile ~/.ssh/my-key.pem
# Then SSH normally using the instance ID as hostname
ssh i-0abc1234def567890
Audit Logging
Every keystroke during a Session Manager session can be logged. Configure logging in SSM Console under Session Manager > Preferences:
- CloudWatch Logs — streams the session transcript to a log group; queryable with CloudWatch Insights.
- S3 — uploads a full session transcript to an S3 bucket after session termination; suitable for long-term compliance archiving.
Run Command
Run Command lets you remotely execute shell scripts, PowerShell scripts, or pre-packaged SSM Documents on one or more managed instances simultaneously — no SSH, no agent login required.
Key Features
- Targeting — by instance ID, tag key/value, resource group, or all managed instances.
- Rate controls — concurrency limit (max instances running command in parallel) and error threshold (abort if N% of instances fail).
- Output capture — standard output/error truncated to 24 KB inline; full output written to S3 or CloudWatch Logs.
Practical Example: Deploy App Update Across a Fleet
Imagine you have 50 EC2 instances tagged App=myapp and Env=prod. You want to pull the latest artifact from S3, stop the service, deploy, and restart — on all 50 instances:
aws ssm send-command \
--document-name "AWS-RunShellScript" \
--targets '[{"Key":"tag:App","Values":["myapp"]},{"Key":"tag:Env","Values":["prod"]}]' \
--parameters '{
"commands":[
"set -euo pipefail",
"echo \"[$(date)] Starting deployment\" | tee -a /var/log/deploy.log",
"aws s3 cp s3://myapp-artifacts/releases/myapp-latest.tar.gz /tmp/",
"sudo systemctl stop myapp || true",
"sudo tar -xzf /tmp/myapp-latest.tar.gz -C /opt/myapp/",
"sudo chown -R myapp:myapp /opt/myapp/",
"sudo systemctl start myapp",
"sudo systemctl is-active --quiet myapp && echo \"[$(date)] Deployment successful\" | tee -a /var/log/deploy.log"
],
"executionTimeout":["300"]
}' \
--max-concurrency "20%" \
--max-errors "5" \
--output-s3-bucket-name "myapp-ssm-output" \
--output-s3-key-prefix "deployments/2026-06-06/" \
--region us-east-1 \
--query "Command.CommandId" \
--output text
After sending the command, poll its status:
# Check overall command status
aws ssm list-command-invocations \
--command-id "abc12345-1234-1234-1234-abc123456789" \
--details \
--query "CommandInvocations[*].{Instance:InstanceId,Status:Status,Output:CommandPlugins[0].Output}" \
--output table
--max-concurrency "20%" means at most 20% of targeted instances run the command at the same time (rolling deploy). --max-errors "5" means if 5 invocations fail, SSM stops sending to the remaining instances, preventing a bad deploy from propagating across the entire fleet.
Run Command integrates naturally with CloudWatch Monitoring — you can set alarms on the command's CloudWatch Events notifications to trigger rollback workflows if the error threshold is crossed.
Patch Manager
Patch Manager automates the process of patching operating systems and applications on your managed instances. It works through three core concepts: patch baselines, maintenance windows, and patch groups.
Patch Baselines
A patch baseline defines which patches are approved and which are rejected for your instances. AWS provides predefined baselines for each supported OS (Amazon Linux 2, Windows Server, Ubuntu, etc.) that auto-approve all critical and security patches after a 7-day delay.
For production environments, create a custom baseline with stricter rules — for example, approve only patches classified as Critical or Security with a severity of Critical or Important, and require a 3-day testing delay:
aws ssm create-patch-baseline \
--name "MyApp-Prod-PatchBaseline" \
--operating-system "AMAZON_LINUX_2" \
--approval-rules '{
"PatchRules": [{
"PatchFilterGroup": {
"PatchFilters": [
{"Key": "CLASSIFICATION", "Values": ["Security", "Bugfix"]},
{"Key": "SEVERITY", "Values": ["Critical", "Important"]}
]
},
"ApproveAfterDays": 3,
"ComplianceLevel": "CRITICAL"
}]
}' \
--description "Custom patch baseline for production app servers"
Patch Groups
Tag your instances with Patch Group=prod-app and register that string with your custom baseline. SSM Patch Manager then uses this tag to determine which baseline applies to each instance:
# Register patch group with baseline
aws ssm register-patch-baseline-for-patch-group \
--baseline-id "pb-0abc1234567890def" \
--patch-group "prod-app"
# Tag your EC2 instances
aws ec2 create-tags \
--resources i-0abc1234def567890 i-0def5678abc901234 \
--tags Key="Patch Group",Value="prod-app"
Maintenance Windows
A maintenance window defines a recurring schedule during which patching (and other disruptive tasks) can run. Create one for Saturday nights between 02:00 and 04:00 UTC:
aws ssm create-maintenance-window \
--name "ProdPatchWindow-SaturdayNight" \
--schedule "cron(0 2 ? * SAT *)" \
--duration 2 \
--cutoff 1 \
--allow-unassociated-targets
Register a task in the window that runs AWS-RunPatchBaseline on all instances tagged Patch Group=prod-app with Operation=Install:
aws ssm register-task-with-maintenance-window \
--window-id "mw-0abc1234567890def" \
--task-type RUN_COMMAND \
--task-arn "AWS-RunPatchBaseline" \
--targets '[{"Key":"tag:Patch Group","Values":["prod-app"]}]' \
--task-invocation-parameters '{
"RunCommand": {
"Parameters": {"Operation": ["Install"]},
"OutputS3BucketName": "myapp-patch-logs",
"OutputS3KeyPrefix": "patch-reports/"
}
}' \
--max-concurrency "20%" \
--max-errors "5" \
--priority 1
Scan vs Install and Compliance Reporting
Run Operation=Scan to assess patch compliance without installing anything — useful in pre-maintenance audits. Run Operation=Install during the maintenance window.
After patching, view compliance in the SSM Console under Patch Manager > Compliance or query it via CLI:
aws ssm list-compliance-summaries \
--filters '[{"Key":"ComplianceType","Values":["Patch"]}]' \
--query "ComplianceSummaryItems[*].{Resource:InstanceId,Status:Status,NonCompliant:NonCompliantCount}" \
--output table
RebootOption=RebootIfNeeded in the task parameters. Plan your maintenance windows to include reboot time for kernel updates.
SSM Automation Documents
SSM Automation Documents (runbooks) let you define multi-step operational workflows as YAML or JSON. Unlike Run Command (which runs shell scripts), Automation Documents can call AWS API actions, invoke Lambda functions, wait for conditions, and branch based on step output.
Built-in Runbooks
AWS provides hundreds of pre-built runbooks under the AWS-* namespace. Commonly used ones include:
AWS-RestartEC2Instance— gracefully stop and start an instance with health checksAWS-CreateImage— create an AMI from a running instanceAWS-DeleteSnapshot— clean up old EBS snapshotsAWS-EnableS3BucketEncryption— enforce KMS encryption on an S3 bucketAWS-PatchInstanceWithRollback— patch an instance, and roll back to a previous AMI snapshot if patching fails
Run a built-in runbook via CLI:
aws ssm start-automation-execution \
--document-name "AWS-RestartEC2Instance" \
--parameters "InstanceId=i-0abc1234def567890" \
--region us-east-1 \
--query "AutomationExecutionId" \
--output text
Custom Automation Document
The following custom runbook demonstrates a three-step workflow: create an AMI snapshot of an instance, install pending patches, and verify the instance is healthy before exiting. If patching fails, the document records the failure without destroying the snapshot (enabling manual rollback).
---
description: "Snapshot, patch, and verify an EC2 instance"
schemaVersion: "0.3"
assumeRole: "{{ AutomationAssumeRole }}"
parameters:
InstanceId:
type: String
description: "Target EC2 instance ID"
AutomationAssumeRole:
type: String
description: "IAM role ARN for the automation execution"
default: ""
mainSteps:
- name: createAmiSnapshot
action: aws:createImage
maxAttempts: 1
onFailure: Abort
inputs:
InstanceId: "{{ InstanceId }}"
ImageName: "pre-patch-{{ InstanceId }}-{{ global:DATE_TIME }}"
NoReboot: true
outputs:
- Name: ImageId
Selector: "$.ImageId"
Type: String
- name: installPatches
action: aws:runCommand
maxAttempts: 1
onFailure: Continue # continue so we can log failure; AMI snapshot is already safe
inputs:
DocumentName: AWS-RunPatchBaseline
InstanceIds:
- "{{ InstanceId }}"
Parameters:
Operation: Install
RebootOption: RebootIfNeeded
outputs:
- Name: CommandId
Selector: "$.CommandId"
Type: String
- name: verifyInstanceOnline
action: aws:waitForAwsResourceProperty
maxAttempts: 5
timeoutSeconds: 300
inputs:
Service: ec2
Api: DescribeInstances
InstanceIds:
- "{{ InstanceId }}"
PropertySelector: "$.Reservations[0].Instances[0].State.Name"
DesiredValues:
- running
- name: runHealthCheck
action: aws:runShellScript
maxAttempts: 2
inputs:
runCommand:
- "curl -sf http://localhost/health || exit 1"
- "echo 'Health check passed on {{ InstanceId }}'"
timeoutSeconds: 30
Save this file as patch-and-verify.yaml, then register and execute it:
# Register the document
aws ssm create-document \
--name "MyApp-PatchAndVerify" \
--content file://patch-and-verify.yaml \
--document-type Automation \
--document-format YAML
# Execute the automation
aws ssm start-automation-execution \
--document-name "MyApp-PatchAndVerify" \
--parameters '{
"InstanceId": ["i-0abc1234def567890"],
"AutomationAssumeRole": ["arn:aws:iam::123456789012:role/SSMAutomationRole"]
}' \
--region us-east-1
AutomationAssumeRole IAM role needs ec2:CreateImage, ssm:SendCommand, ec2:DescribeInstances, and ssm:GetCommandInvocation permissions. Scope these down to the specific instance or resource group rather than using * wildcards.
SSM Best Practices Summary
- Use hierarchical Parameter Store paths — group by
/app/env/paramto enable IAM path-based access control and bulk retrieval. - Always use SecureString for secrets — never store passwords, tokens, or API keys as plain String parameters.
- Replace bastion hosts with Session Manager — eliminates SSH key management overhead, removes the attack surface of open port 22, and provides native audit logging.
- Enable session logging to both S3 and CloudWatch — dual-destination logging satisfies most compliance requirements without additional tooling.
- Use rate controls in Run Command — always set
--max-concurrencyand--max-errorsto prevent a bad command from running on your entire fleet simultaneously. - Create custom patch baselines — never rely solely on the AWS-provided defaults in production; define explicit classification and severity filters to avoid patching regressions.
- Prefer Automation Documents over ad hoc scripts — runbooks are versioned, auditable, reusable, and can integrate approval gates (using
aws:approvestep) for change-control workflows. - Tag everything — Run Command, Patch Manager, and Automation targeting all rely on instance tags. Consistent tagging (
App,Env,Patch Group) is the foundation of fleet-wide automation.
Related AWS Guides
- EC2 Complete Guide — instance lifecycle, AMIs, and instance profiles
- IAM Roles & Policies — creating the instance profile and automation roles SSM requires
- Security Best Practices — encryption, logging, and compliance configuration
- CloudWatch Monitoring — alerting on Run Command events and session activity
- Lambda Serverless — reading Parameter Store from Lambda functions
- VPC Networking — VPC endpoints for private-subnet SSM connectivity
- S3 Tutorial — storing Run Command output and session transcripts