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.

Best practice: Create a dedicated instance profile (e.g., 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 size4 KB8 KB
Max parameters per account/region10,000100,000
Parameter policies (TTL, expiry alerts)NoYes
CostFree 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/ssm or a customer-managed key). Only the KMS Decrypt permission 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"
Use 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
CostFree (standard) or $0.05/param/month (advanced)$0.40/secret/month + $0.05 per 10,000 API calls
Automatic rotationNo (must build custom Lambda)Yes — built-in for RDS, Redshift, DocumentDB
Cross-account accessRequires custom KMS key + resource-based policyNative resource-based policy support
Max value size4 KB (standard) / 8 KB (advanced)64 KB
VersioningInteger versions, no labels by defaultStaging labels (AWSCURRENT, AWSPENDING, AWSPREVIOUS)
Best forApp config, non-rotating secrets, hierarchical namespacesDatabase credentials, API keys that need automatic rotation
Rule of thumb: Use Parameter Store for configuration values and secrets that change rarely. Use Secrets Manager when automatic credential rotation is a hard requirement — typically database passwords and third-party API keys that must rotate on a schedule.

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'})
    }
The Lambda execution role must have 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>.ssm
  • com.amazonaws.<region>.ssmmessages
  • com.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.
For compliance frameworks (SOC 2, PCI-DSS, ISO 27001), session logging to both CloudWatch and S3 simultaneously is recommended. Enable KMS encryption on the S3 bucket and CloudWatch log group for data at rest. See Security Best Practices for encryption configuration.

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
Rate controls explained: --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
Important: Patch Manager installs patches but does not reboot instances automatically unless you set 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 checks
  • AWS-CreateImage — create an AMI from a running instance
  • AWS-DeleteSnapshot — clean up old EBS snapshots
  • AWS-EnableS3BucketEncryption — enforce KMS encryption on an S3 bucket
  • AWS-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
The 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/param to 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-concurrency and --max-errors to 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:approve step) 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