「SSH鍵を複数人で共有している。誰がいつ何のために接続したか分からなくて怖い」
こういった声は、AWSでインフラを運用しはじめたLinuxエンジニアからよく聞きます。踏み台サーバー(Bastion Host)方式は「公開鍵認証+ポート22開放」が必須のため、鍵の紛失・共有・ローテーションが運用負荷になりがちです。
この記事では、AWS Systems Manager(SSM)のSession Managerを使って踏み台サーバーなしでEC2に接続する方法を解説します。IAMロールによるアクセス制御、Run Commandを使った一括コマンド実行、Parameter StoreによるDBパスワードなどの秘密情報管理まで、実務で使えるSSMの設計パターンをひとまとめに解説します。Parameter StoreとSecrets Managerの使い分け基準もあわせて整理します。
動作確認環境: Amazon Linux 2023 / AWS CLI 2.17.x / AWS Systems Manager Agent 3.3.x
この記事のポイント
・aws ssm start-session でSSHなし・鍵なしでプライベートサブネットのEC2に接続できる
・IAMロール(AmazonSSMManagedInstanceCore)のアタッチが唯一の前提条件
・Run Commandでタグ指定した複数EC2へ同時コマンドを実行できる
・Parameter Storeは無料で設定値管理向け、Secrets Managerはパスワード自動ローテーションが必要な場合に使い分ける
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
なぜ踏み台サーバーを廃止してSSMに移行すべきか
従来の踏み台サーバー方式では次のような問題が生じます。・ポート22の開放が必要: 踏み台EC2のセキュリティグループでポート22を外部に開放しなければなりません。ポート22は攻撃者が最初に狙うポートのひとつです
・SSH鍵の管理コスト: 開発者ごとに鍵を発行・失効する管理が必要です。誰かが退職した際のローテーションを忘れると、不正アクセスのリスクが残ります
・踏み台EC2自体の管理コスト: 「管理するためのサーバー」を24時間365日稼働させながら、OS更新や設定変更のメンテナンスも必要です。t3.microでも月1,000円前後のコストが発生します
・接続ログの取得が煩雑: bashの履歴だけでは「誰がいつ何を実行したか」の監査証跡が不完全です
AWS Systems ManagerのSession Managerに移行すると、次のような設計が実現できます。
・ポート22を完全に閉じられる: セキュリティグループのインバウンドルールにSSHポートが不要になります。攻撃対象を大幅に削減できます
・IAMによる認証: SSH鍵の代わりにAWS IAMポリシーでアクセス制御します。IAMロールの無効化で即時に接続を遮断できます
・全セッションがCloudWatch Logsに記録: 誰がいつどのEC2に接続し何を実行したか、コマンドの出力まで含めた監査ログが自動で残ります
・踏み台サーバーが不要: プライベートサブネットのEC2へも直接接続できます(VPCエンドポイント設定時)
Session Managerの設定手順
1. EC2にIAMロールをアタッチする
Session Managerを使うにはEC2にSSMへのアクセス権限を持つIAMロールをアタッチする必要があります。# Step 1: IAMロールを作成(EC2がSSMを呼び出せる信頼ポリシー) aws iam create-role \ --role-name EC2-SSMRole \ --assume-role-policy-document '{ "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Principal": {"Service": "ec2.amazonaws.com"}, "Action": "sts:AssumeRole" }] }' # Step 2: AWS管理ポリシー「AmazonSSMManagedInstanceCore」をアタッチ aws iam attach-role-policy \ --role-name EC2-SSMRole \ --policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore # Step 3: EC2インスタンスプロファイルを作成してロールを紐づける aws iam create-instance-profile \ --instance-profile-name EC2-SSMProfile aws iam add-role-to-instance-profile \ --instance-profile-name EC2-SSMProfile \ --role-name EC2-SSMRole # Step 4: 既存EC2にインスタンスプロファイルをアタッチ aws ec2 associate-iam-instance-profile \ --instance-id i-0a1b2c3d4e5f67890 \ --iam-instance-profile Name=EC2-SSMProfile
2. SSM Agentのインストール確認
Session ManagerはEC2上で動作するSSM Agentを通じてコマンドを送信します。Amazon Linux 2023・Amazon Linux 2・Ubuntu Server 18.04以降はAmazon提供のAMIにSSM Agentがプリインストールされています。# SSM Agentの起動状態を確認 sudo systemctl status amazon-ssm-agent # 出力例(正常動作時) # amazon-ssm-agent.service - amazon-ssm-agent # Loaded: loaded (/lib/systemd/system/amazon-ssm-agent.service; enabled) # Active: active (running) since Tue 2026-07-07 10:23:15 UTC; 2h 14min ago # Main PID: 1247 (amazon-ssm-agen) # インストールされていない場合(Amazon Linux 2023) sudo dnf install -y amazon-ssm-agent sudo systemctl enable amazon-ssm-agent --now
3. VPCエンドポイントの設定(プライベートサブネット使用時)
プライベートサブネットのEC2にインターネットへのアウトバウンドがない場合、VPCエンドポイント(AWS PrivateLink)を使ってSSMとプライベートに通信させます。Session Managerには3つのエンドポイントが必要です。# SSM用VPCエンドポイントを3つ作成(ap-northeast-1の例) for svc in ssm ssmmessages ec2messages; do aws ec2 create-vpc-endpoint \ --vpc-id vpc-0abc1234 \ --service-name com.amazonaws.ap-northeast-1.${svc} \ --vpc-endpoint-type Interface \ --subnet-ids subnet-0def5678 subnet-0ghi9012 \ --security-group-ids sg-0ssm1234 \ --private-dns-enabled echo "Created: ${svc}" done # 出力例 # Created: ssm # Created: ssmmessages # Created: ec2messages
Session ManagerでSSHなしでEC2に接続する方法
1. Session Manager用プラグインのインストール(初回のみ)
`aws ssm start-session` コマンドの実行には、作業するPC(自分のPC)へ「Session Managerプラグイン」をインストールする必要があります。# Linux(Amazon Linux 2023 / RHEL9 / AlmaLinux 9) sudo dnf install -y \ https://s3.amazonaws.com/session-manager-downloads/plugin/latest/linux_64bit/session-manager-plugin.rpm # macOS(Homebrewを使う場合) brew install --cask session-manager-plugin # インストール確認 session-manager-plugin --version # 1.2.650.0
2. aws ssm start-session でEC2に接続する
インスタンスIDを指定してセッションを開始します。パスワードも鍵ファイルも不要です。# 接続先のEC2インスタンスIDをタグ名で確認 aws ec2 describe-instances \ --filters "Name=tag:Name,Values=web-server-prod" \ --query 'Reservations[*].Instances[*].{ID:InstanceId,State:State.Name}' \ --output table # 出力例 # ------------------------------------------ # | DescribeInstances | # +---------------------+------------------+ # | ID | State | # +---------------------+------------------+ # | i-0a1b2c3d4e5f67890 | running | # +---------------------+------------------+ # Session Managerで接続(SSH不要・鍵不要) aws ssm start-session --target i-0a1b2c3d4e5f67890 # 接続後のプロンプト(シェルが起動する) # Starting session with SessionId: tomohiro.miyazaki-0a1b2c3d4e5f67890 # sh-5.2$ whoami # ssm-user # sh-5.2$ sudo su - # [root@ip-10-0-1-42 ~]# hostname # ip-10-0-1-42.ap-northeast-1.compute.internal
3. 接続ログをCloudWatch Logsに保存する設定
Session Managerのセッションログはデフォルトでは保存されません。監査証跡を残すにはSSMのセッション設定(SSM-SessionManagerRunShell)を更新します。# CloudWatch Logsのロググループを作成 aws logs create-log-group \ --log-group-name /ssm/session-manager # セッション設定を更新(ログ出力を有効化) aws ssm update-document \ --name SSM-SessionManagerRunShell \ --document-version "\$LATEST" \ --content '{ "schemaVersion": "1.0", "sessionType": "Standard_Stream", "inputs": { "cloudWatchLogGroupName": "/ssm/session-manager", "cloudWatchEncryptionEnabled": false, "idleSessionTimeout": "20" } }' # セッション終了後にログを確認 aws logs get-log-events \ --log-group-name /ssm/session-manager \ --log-stream-name i-0a1b2c3d4e5f67890 \ --query 'events[].message' \ --output text | head -30
4. ポートフォワードでプライベートなRDS・Redisへ接続する
Session Managerのポートフォワード機能を使うと、自分のPCからプライベートサブネットのRDSやElastiCacheへ直接接続できます。踏み台サーバー経由のSSHトンネルが不要になります。# ポートフォワード(自分のPCの13306番→EC2経由でRDS 3306番) # EC2はトンネルの中継役になる(EC2自体にはSSHしない) aws ssm start-session \ --target i-0a1b2c3d4e5f67890 \ --document-name AWS-StartPortForwardingSessionToRemoteHost \ --parameters '{ "host":["mydb.xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com"], "portNumber":["3306"], "localPortNumber":["13306"] }' # 上記コマンド実行中に別ターミナルからRDSへ接続 mysql -h 127.0.0.1 -P 13306 -u admin -p # Redis(ElastiCache)へのポートフォワード例 aws ssm start-session \ --target i-0a1b2c3d4e5f67890 \ --document-name AWS-StartPortForwardingSessionToRemoteHost \ --parameters '{ "host":["my-redis.xxxxxx.0001.apne1.cache.amazonaws.com"], "portNumber":["6379"], "localPortNumber":["16379"] }'
Run CommandでEC2に一括コマンドを実行する方法
Run CommandはSSH接続なしに複数EC2へ同時コマンドを送信できる機能です。パッチ適用やサービス再起動などの定型作業を、踏み台サーバーを経由せずに実行できます。1. 単一EC2にコマンドを送信する
# AWS-RunShellScriptドキュメントを使って単一EC2でコマンド実行 aws ssm send-command \ --instance-ids i-0a1b2c3d4e5f67890 \ --document-name "AWS-RunShellScript" \ --parameters '{"commands":["df -h","systemctl status nginx | head -10"]}' \ --comment "ディスク確認とnginxステータス" # 出力例(コマンドIDが返る) # { # "Command": { # "CommandId": "ab12cd34-ef56-7890-gh12-ij3456klmnop", # "DocumentName": "AWS-RunShellScript", # "Status": "Pending" # } # } # 実行結果を取得 aws ssm get-command-invocation \ --command-id "ab12cd34-ef56-7890-gh12-ij3456klmnop" \ --instance-id i-0a1b2c3d4e5f67890 \ --query '{Status:Status,Output:StandardOutputContent}'
2. タグを使って複数EC2に一括実行する
# tag:Environment=production の全EC2にセキュリティアップデートを一括適用 aws ssm send-command \ --targets 'Key=tag:Environment,Values=production' \ --document-name "AWS-RunShellScript" \ --parameters '{"commands":["sudo dnf update -y --security"]}' \ --comment "セキュリティアップデート一括適用" \ --timeout-seconds 600 # 全対象インスタンスの実行状況を一覧表示 aws ssm list-command-invocations \ --command-id "ab12cd34-ef56-7890-gh12-ij3456klmnop" \ --details \ --query 'CommandInvocations[*].{ID:InstanceId,Status:Status}' \ --output table # 出力例(3台のEC2に実行した場合) # ------------------------------------------- # | ListCommandInvocations | # +---------------------+-------------------+ # | ID | Status | # +---------------------+-------------------+ # | i-0a1b2c3d4e5f67890 | Success | # | i-0b2c3d4e5f678901 | Success | # | i-0c3d4e5f6789012 | Success | # +---------------------+-------------------+
3. 実行ログをS3に保存して大量出力を管理する
コマンドの出力が大きい場合(数MB超)や、複数EC2の実行ログをまとめて保管する場合は、`--output-s3-bucket-name` でS3バケットに自動保存できます。# 実行ログをS3に保存(長時間のパッチ適用ログ管理に便利) aws ssm send-command \ --targets 'Key=tag:Environment,Values=production' \ --document-name "AWS-RunShellScript" \ --parameters '{"commands":["sudo dnf update -y"]}' \ --output-s3-bucket-name "my-ssm-logs-bucket" \ --output-s3-key-prefix "run-command/$(date +%Y%m%d)" \ --comment "本番環境フルアップデート" # S3に保存されたログを確認 aws s3 ls s3://my-ssm-logs-bucket/run-command/ --recursive
Parameter Storeで秘密情報を安全に管理する設計
Parameter Storeを使うとDBパスワードやAPIキーをコードやEC2のファイルに直書きせずに管理できます。KMSによる暗号化(SecureString型)と階層パスによる整理が設計の基本です。1. パスの命名規則(階層設計)
# 推奨の命名規則(/{環境}/{アプリ名}/{パラメータ名}) /prod/api-server/db-password /prod/api-server/db-endpoint /prod/api-server/stripe-secret-key /staging/api-server/db-password # パスに階層をつけることで、アプリ単位・環境単位でまとめて取得できる
2. SecureStringでKMS暗号化して登録・取得する
# SecureString型でパラメータを登録(KMSで自動暗号化される) aws ssm put-parameter \ --name "/prod/api-server/db-password" \ --value "P@ssw0rd!Secure2026" \ --type SecureString \ --description "本番DBの接続パスワード" # パスワードを復号して取得(--with-decryptionが必要) aws ssm get-parameter \ --name "/prod/api-server/db-password" \ --with-decryption \ --query 'Parameter.Value' \ --output text # 出力例 # P@ssw0rd!Secure2026 # 階層パス配下のパラメータを一覧表示 aws ssm get-parameters-by-path \ --path "/prod/api-server/" \ --recursive \ --query 'Parameters[*].{Name:Name,Type:Type}' \ --output table # 出力例 # ---------------------------------------------------------- # | GetParametersByPath | # +-------------------------------------+-----------------+ # | Name | Type | # +-------------------------------------+-----------------+ # | /prod/api-server/db-endpoint | String | # | /prod/api-server/db-password | SecureString | # | /prod/api-server/stripe-secret-key | SecureString | # +-------------------------------------+-----------------+
# EC2インスタンス用IAMポリシー(パスで権限を絞る例) { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "ssm:GetParameter", "ssm:GetParametersByPath" ], "Resource": "arn:aws:ssm:ap-northeast-1:123456789012:parameter/prod/api-server/*" }, { "Effect": "Allow", "Action": "kms:Decrypt", "Resource": "arn:aws:kms:ap-northeast-1:123456789012:key/your-kms-key-id" } ] }
3. EC2のスクリプトからParameter Storeを参照する
#!/bin/bash # アプリ起動スクリプト(Parameter Storeから秘密情報を取得する例) DB_PASS=$(aws ssm get-parameter \ --name "/prod/api-server/db-password" \ --with-decryption \ --query 'Parameter.Value' \ --output text \ --region ap-northeast-1) DB_HOST=$(aws ssm get-parameter \ --name "/prod/api-server/db-endpoint" \ --query 'Parameter.Value' \ --output text \ --region ap-northeast-1) # 取得した値を環境変数にセットしてアプリを起動 export DATABASE_PASSWORD="${DB_PASS}" export DATABASE_HOST="${DB_HOST}" ./myapp --config /etc/myapp/config.yaml
4. Parameter StoreとSecrets Managerの使い分け
AWSには機密情報を扱うマネージドサービスが2つあります。Parameter Store(SSM)とSecrets Managerです。用途に応じた使い分けが重要です。| 比較項目 | Parameter Store(SecureString) | Secrets Manager |
|---|---|---|
| 料金 | Standard: 無料(~10,000件) | 1シークレットあたり月$0.40 |
| パスワード自動ローテーション | なし(手動更新が必要) | あり(RDS・Redshift・DocumentDB対応) |
| バージョン管理 | 最新値のみ(変更履歴は保持) | AWSCURRENT/AWSPREVIOUS/AWSPENDINGの3ステージ管理 |
| 用途の目安 | DB接続先・APIエンドポイント・設定値 | 定期ローテーションが必要なDBパスワード・APIキー |
| 主なコマンド | aws ssm put-parameter / get-parameter |
aws secretsmanager create-secret / get-secret-value |
AWSのLinuxサーバー構築と管理を体系的に学びたい方には、AWSでLinuxサーバーを学ぶ実践ロードマップもあわせてご覧ください。
トラブルシュート|Session Manager接続できない時の確認手順
1. IAMロールのアタッチ状況を確認する
接続できない最多原因はIAMロールがアタッチされていないことです。# EC2に紐付いているIAMロールを確認 aws ec2 describe-instances \ --instance-ids i-0a1b2c3d4e5f67890 \ --query 'Reservations[0].Instances[0].IamInstanceProfile.Arn' # 出力例(設定済みの場合) # "arn:aws:iam::123456789012:instance-profile/EC2-SSMProfile" # IAMロールに必要なポリシーが付いているか確認 aws iam list-attached-role-policies \ --role-name EC2-SSMRole \ --query 'AttachedPolicies[*].PolicyName' \ --output text # 出力例(正常) # AmazonSSMManagedInstanceCore
2. SSM Agentの動作確認とマネージドインスタンス登録状態
# EC2にSSH接続できる場合はAgentのログを確認 sudo journalctl -u amazon-ssm-agent -n 30 --no-pager # 典型的なエラーと原因 # "Failed to load instance info from metadata" # → EC2のIAMロールが設定されていない # "No credential providers found" # → IMDSv2のみ有効な環境でIAMロール未設定 # Agentを再起動して状態確認 sudo systemctl restart amazon-ssm-agent sudo systemctl status amazon-ssm-agent # マネジメントコンソール側でのインスタンス登録状態確認 aws ssm describe-instance-information \ --query 'InstanceInformationList[*].{ID:InstanceId,Status:PingStatus,Agent:AgentVersion}' \ --output table # 出力例(正常登録済み) # --------------------------------------------------------- # | DescribeInstanceInformation | # +---------------------+---------+---------------------+ # | ID | Agent | Status | # +---------------------+---------+---------------------+ # | i-0a1b2c3d4e5f67890 | 3.3.xxx | Online | # +---------------------+---------+---------------------+ # "Online"でなく一覧に表示されない場合はIAMロールかSSM Agentを確認する
3. VPCエンドポイントの通信経路を確認する(プライベートサブネット限定)
プライベートサブネットのEC2がSSMに登録されない(PingStatusがOffline)場合は、VPCエンドポイントの設定ミスが原因であることが多いです。# エンドポイントの作成状況を確認(3つ必要) aws ec2 describe-vpc-endpoints \ --filters "Name=vpc-id,Values=vpc-0abc1234" \ --query 'VpcEndpoints[*].{Service:ServiceName,State:State,DNS:PrivateDnsEnabled}' \ --output table # 正常な出力例 # ----------------------------------------------------------------------- # | DescribeVpcEndpoints | # +--------------------------------------------------+----------+------+ # | Service | State | DNS | # +--------------------------------------------------+----------+------+ # | com.amazonaws.ap-northeast-1.ssm | available| True | # | com.amazonaws.ap-northeast-1.ssmmessages | available| True | # | com.amazonaws.ap-northeast-1.ec2messages | available| True | # +--------------------------------------------------+----------+------+ # EC2内からSSMエンドポイントの名前解決を確認(プライベートIPが返れば正常) dig ssm.ap-northeast-1.amazonaws.com +short # 正常な出力例(プライベートIPアドレスが返る) # 10.0.1.45 # 10.0.2.67
本記事のまとめ
| やりたいこと | コマンド / 設定 |
|---|---|
| EC2にSession Managerで接続する | aws ssm start-session --target i-xxxx |
| マネージドインスタンスの登録状態を確認する | aws ssm describe-instance-information |
| プライベートRDSへポートフォワードで接続する | aws ssm start-session --target i-xxxx --document-name AWS-StartPortForwardingSessionToRemoteHost |
| 単一EC2にコマンドを送信する | aws ssm send-command --instance-ids i-xxxx --document-name AWS-RunShellScript |
| タグ指定で複数EC2に一括実行する | aws ssm send-command --targets Key=tag:Env,Values=prod ... |
| コマンドの実行結果を取得する | aws ssm get-command-invocation --command-id xxx --instance-id i-xxx |
| SecureStringでパラメータを登録する | aws ssm put-parameter --type SecureString ... |
| Parameter Storeの値を復号して取得する | aws ssm get-parameter --with-decryption ... |
| 階層パス配下のパラメータを一覧取得する | aws ssm get-parameters-by-path --path /prod/api-server/ --recursive |
| VPCエンドポイントを作成する(プライベートサブネット用) | aws ec2 create-vpc-endpoint --service-name com.amazonaws.ap-northeast-1.ssm ... |
AWSのインフラをSSHなし・IAM管理の「現代的な設計」で構築しませんか?
Session Managerのコマンドは調べれば分かります。でも「IAMポリシーをどう設計するか」「VPCエンドポイントをどう組み合わせるか」を説明できますか?
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、『Linuxサーバー構築入門マニュアル(図解60P)』を完全無料でプレゼントしています。
「独学の時間がもったいない」「プロから直接、現場の技術を最短で学びたい」という本気の方には、2日で実務レベルのスキルが身につく【初心者向けハンズオンセミナー】も開催しています。
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。
登録無料・いつでも解除できます