← cd ~/blog
~/blog/ec2-fleet-commands-without-ssh.md

EC2 Fleet Command Execution Without Opening SSH

EC2 Fleet Command Execution Without Opening SSH

aws-commander is a Go CLI for fleet-wide EC2 command execution via AWS SSM Run Command — no SSH keys, no open ports, target instances by ID or tag.

The standard playbook for running commands across a fleet of EC2 instances used to be: open port 22, distribute SSH keys, write a loop over your inventory. It works, but it trades security for convenience. Every instance becomes a potential entry point, key rotation is painful, and the blast radius of a compromised key spans your entire fleet.

AWS Systems Manager (SSM) Run Command eliminates all of that. No open ports, no SSH keys, no bastion hosts. But the SSM console is clunky for bulk operations — and the AWS CLI interface for Run Command is verbose enough to make scripting it a chore.

aws-commander wraps SSM Run Command in a purpose-built CLI that makes fleet-wide execution feel like ssh, not like wrestling with JSON payloads.

How aws-commander Uses AWS SSM Run Command

aws-commander talks to the AWS SSM API directly. As long as your EC2 instances have the AmazonSSMManagedInstanceCore IAM policy attached and the SSM Agent running (it's pre-installed on Amazon Linux and most modern AMIs), you're ready. No inbound security group rules needed — the agent polls outbound over HTTPS.

You target instances in two ways:

Running Shell Commands, Scripts, and Ansible Playbooks

The simplest use case: run a shell command across every instance tagged as a web server.

aws-commander run \
  --tag "Role=web-server" \
  --command "systemctl status nginx"

For ad-hoc diagnostics that are too complex for a one-liner, point it at a script file:

aws-commander run \
  --tag "Environment=production" \
  --script ./scripts/collect-diagnostics.sh

And for infrastructure automation workflows, it supports Ansible playbooks as first-class input:

aws-commander run \
  --tag "Environment=staging" \
  --playbook ./playbooks/patch-and-restart.yml

Under the hood, aws-commander invokes the appropriate SSM Run Command document (AWS-RunShellScript for shell commands and scripts, AWS-RunAnsiblePlaybook for Ansible) and streams the output back to your terminal as it arrives.

Targeting Specific Instances

When you need to target a known instance directly:

aws-commander run \
  --instance-id i-0abc123def456789 \
  --command "df -h"

Or multiple instance IDs at once:

aws-commander run \
  --instance-ids i-0abc123def456789,i-0def456789abc123 \
  --command "uptime"

IAM Setup

aws-commander needs an IAM identity with ssm:SendCommand and ssm:GetCommandInvocation permissions. A minimal policy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ssm:SendCommand",
        "ssm:GetCommandInvocation",
        "ssm:ListCommandInvocations"
      ],
      "Resource": "*"
    }
  ]
}

For production use, scope the Resource to specific instance ARNs or SSM documents rather than wildcards.

Why Not Just Use the AWS CLI?

You absolutely can. Here's the equivalent aws CLI call for running a single command on a tagged fleet:

aws ssm send-command \
  --document-name "AWS-RunShellScript" \
  --targets "Key=tag:Role,Values=web-server" \
  --parameters "commands=[\"systemctl status nginx\"]" \
  --output json \
  --query "Command.CommandId"

Then you need to poll aws ssm list-command-invocations with that CommandId to get results. aws-commander handles all of this — targeting, document selection, polling, and output aggregation — in a single command invocation.

Security Benefits

This approach is worth emphasising: aws-commander works entirely within the SSM trust boundary. Your instances never need port 22 open. The SSM Agent authenticates outbound to the AWS API using the instance's IAM role — no credentials on disk, no key pairs to manage, no lateral movement risk from a stolen SSH key.

Combined with IMDSv2 enforcement and strict IAM role scoping, this is the correct posture for any EC2 fleet that takes its security surface area seriously.

Conclusion

If you're still SSHing into EC2 instances to run fleet-wide commands, aws-commander and SSM Run Command are the upgrade path. You get better security, less operational overhead, and a clean audit trail in CloudTrail — for free.

Browse the aws-commander source on GitHub and start running SSM commands across your EC2 fleet.

← prev
Mikrotik Backups Made Easy
next →
EVM Chain Performance Testing with tpser
$ esc
cd ~/ home get blog all posts get projects open-source workloads describe engineer resource spec crash pod CrashLoopBackOff demo get post/easy-mikrotik-backup Mikrotik Backups Made Easy get post/ec2-fleet-commands-without-ssh EC2 Fleet Command Execution Without Opening SSH get post/evm-chain-performance-testing-with-tpser EVM Chain Performance Testing with tpser get post/kubernetes-pvc-snapshot-management-with-kmon Kubernetes Storage Operations Made Easy with kmon get post/teams-direct-routing-without-sbc-hardware Microsoft Teams Direct Routing Without the Hardware SBC get post/veeam-backup-grafana-dashboard Monitoring Veeam B&R with Govein get post/vmware-vcenter-vm-inventory-export-to-excel Exporting VMware vCenter VM Inventory to Excel open job/gombak Go-based automation service for MikroTik router backup management — supports single-device and fleet-wide discovery via L2TP, SSH-based access, configurable retention policies, and system service integration for scheduled unattended backups. open deploy/tsbc Containerised Session Border Controller that bridges SIP/UDP-based PBX systems with Microsoft Teams Direct Routing — orchestrates Kamailio, RTPEngine, and LetsEncrypt TLS to handle signalling and media translation without dedicated SBC hardware. open cronjob/aws-commander CLI tool for fleet-wide remote execution on EC2 instances via AWS SSM Run Command — supports ad-hoc shell commands, script files, and Ansible playbooks, targeting instances by ID or tag without requiring inbound SSH access or open security group rules. open exporter/govein Metrics exporter that queries Veeam Backup & Replication 12+ via its REST API and ships structured job telemetry to InfluxDB 2.0 — ships with a Grafana dashboard template and supports standalone binary, Docker Compose, and Kubernetes Helm deployment. open tool/tpser EVM chain performance testing toolkit with two operating modes — a block-range analyser for historical TPS and gas utilisation reporting, and a sustained load generator for stress-testing nodes at configurable transaction rates over extended durations. open cli/vmex CLI utility that queries VMware vCenter via the vSphere API and exports filtered VM inventory data to formatted Excel workbooks — addressing the limitations of vCenter's native CSV-only export for operational reporting and auditing workflows. open cli/kmon Kubernetes administrative CLI and k9s plugin that automates common storage operations — spins up debug pods from live PVCs, restores volumes from VolumeSnapshots, and generates on-demand or CronJob-scheduled snapshots with configurable snapshot class support.