On-demand AWS Systems Manager tunnels to RDS databases through EC2 bastion instances.
- On-Demand Tunnels: Automatically start SSM tunnels when connections are detected
- Automatic Cleanup: Close idle tunnels after configurable timeout periods
- Multiple Databases: Manage multiple database tunnels simultaneously
- Multi-Account Support: Per-tunnel AWS profile and region overrides for cross-account setups
- Instance Auto-Discovery: Use wildcard patterns to find EC2 instances dynamically
- SSO Authentication: Automatic detection and browser-based login for AWS SSO profiles
- Graceful Shutdown: Clean termination of all tunnels on exit
- AWS CLI installed and configured
- AWS SSM plugin for AWS CLI (
session-manager-plugin) - EC2 instances with SSM agent installed
- Appropriate IAM permissions for SSM sessions
brew install antero-software/lazy-ssm/lazy-ssmInstall Homebrew for Linux if not already installed, then:
brew install antero-software/lazy-ssm/lazy-ssmbrew services on Linux uses systemd, so the same commands work on both platforms.
git clone https://github.com/antero-software/lazy-ssm.git
cd lazy-ssm
go build- Create your configuration file at
~/.lazy-ssm/config.yaml:
tunnels:
- name: my-database
local_port: 23306
rds_endpoint: my-db.cluster-xxxxx.us-east-1.rds.amazonaws.com
rds_port: 3306ß
instance_id: i-xxxxx
description: "My Database"Note: For local port numbers, use ports ≥1024 to avoid requiring root privileges. Random ports are recommended to prevent conflicts with existing services. The tunnel manager will automatically start tunnels on demand when connections are detected on these ports.
- Start the service:
brew services start lazy-ssm- Connect to your database as normal — the tunnel starts automatically on first connection:
mysql -h localhost -P 23306 -u username -plazy-ssm is designed to run as a Homebrew service. It starts automatically on login and restarts if it crashes.
brew services start lazy-ssm # start
brew services stop lazy-ssm # stop
brew services restart lazy-ssm # restart
brew services list # check statuslazy-ssm status # show brew service status
lazy-ssm tail # show last 25 log lines
lazy-ssm tail -n 50 # show last 50 log lines
lazy-ssm version # show versionLogs are written to $(brew --prefix)/var/log/lazy-ssm.log.
For debugging, run directly in the foreground — logs go to stdout and Ctrl+C shuts it down cleanly:
lazy-ssm
lazy-ssm --config /path/to/config.yamlThe configuration file is loaded from ~/.lazy-ssm/config.yaml by default. You can override this with --config.
app:
log_level: info # debug, info, warn, erroraws:
profile: "" # Global AWS profile (optional, uses default if empty)
region: "" # Global AWS region (optional, uses default if empty)
cli_path: aws # Path to AWS CLI executableGlobal AWS settings can be overridden per tunnel.
timeouts:
tunnel_ready: 15s # Maximum wait for tunnel to become ready
tunnel_ready_poll: 500ms # How often to check tunnel readiness
idle_check: 30s # How often to check for idle tunnels
idle_timeout: 5m # Close tunnel after inactivity
shutdown: 10s # Graceful shutdown timeout
shutdown_poll: 100ms # Shutdown poll interval
connection: 1s # TCP connection timeoutnetwork:
listen_address: localhost # Address to bind listeners
tunnel_port_offset: 10000 # Offset for SSM tunnel portstunnels:
- name: production-db # Unique name for this tunnel
local_port: 3306 # Local port to listen on
rds_endpoint: prod-db.cluster-xxxxx.us-east-1.rds.amazonaws.com
rds_port: 3306 # RDS port
instance_id: i-xxxxx # EC2 bastion instance (explicit ID)
description: "Production DB" # Human-readable description
aws_profile: production # Optional: override AWS profile for this tunnel
aws_region: us-east-1 # Optional: override AWS region for this tunnelEach tunnel requires a unique local_port. Ports below 1024 require root privileges.
Use either instance_id (explicit) or instance_pattern (wildcard auto-discovery) — not both.
Use instance_pattern to match EC2 instances by their Name tag. Useful for auto-scaling groups where instance IDs change frequently.
tunnels:
- name: staging-db
local_port: 3307
rds_endpoint: staging-db.cluster-xxxxx.us-east-1.rds.amazonaws.com
rds_port: 3306
instance_pattern: bastion-staging-*
description: "Staging DB"- Supports
*wildcards (e.g.,bastion-*,*-production) - Only matches running instances with SSM agent installed
- Requires
ec2:DescribeInstancesIAM permission - If multiple instances match, the first one found is used
aws:
profile: default
region: us-east-1
tunnels:
- name: dev-db
local_port: 3306
rds_endpoint: dev-db.cluster-xxxxx.us-east-1.rds.amazonaws.com
rds_port: 3306
instance_id: i-xxxxx
description: "Dev DB"
- name: customer-db
local_port: 5432
rds_endpoint: customer-db.cluster-xxxxx.eu-west-1.rds.amazonaws.com
rds_port: 5432
instance_id: i-zzzzz
description: "Customer DB"
aws_profile: customer-account
aws_region: eu-west-1The tunnel manager automatically detects when a profile requires SSO and handles login for you.
- Before starting a tunnel, it checks if the profile is authenticated
- If credentials are expired, it runs
aws sso login --profile <name> - Your browser opens to the AWS SSO login page
- After login, the tunnel starts automatically
[SSO] Profile 'production-sso' requires authentication. Opening SSO login page...
[SSO] Profile 'production-sso' successfully authenticated
[Production DB] Discovering instance matching pattern: bastion-prod-*
[Production DB] Discovered instance: i-0123456789abcdef0
[Production DB] Starting SSM tunnel on port 13306...
Client → Local Port → Lazy SSM Manager → SSM Tunnel → EC2 Bastion → RDS Database
(e.g., 3306) ↓ (e.g., 13306)
Auto-start/stop
based on activity
- Listening: The tunnel manager listens on configured local ports
- On-Demand: When a connection is detected, an SSM tunnel is started automatically
- Proxying: Traffic is proxied between the client and RDS through the SSM tunnel
- Idle Detection: After the configured idle timeout, unused tunnels are closed
- Reconnection: Tunnels automatically restart when new connections arrive
lazy-ssm/
├── main.go # Entry point
├── cmd/
│ ├── root.go # Root command and tunnel manager startup
│ ├── status.go # Service status via brew services
│ ├── tail.go # Log tail command
│ └── version.go # Version command
├── config/
│ ├── config.go # Configuration loading with Viper
│ └── validation.go # Configuration validation
├── tunnel/
│ ├── tunnel.go # Tunnel implementation and connection proxying
│ └── manager.go # Tunnel lifecycle coordination
├── ec2/
│ └── discovery.go # EC2 instance discovery by Name tag pattern
├── sso/
│ └── auth.go # AWS SSO authentication handling
└── .github/
└── workflows/
├── release.yml # Release workflow
└── update-homebrew.yml # Homebrew formula update
- Configuration Files:
~/.lazy-ssm/config.yamlmay contain sensitive resource IDs — keep it private - AWS Credentials: Never commit AWS credentials. Use AWS CLI profiles or IAM roles
- Network Exposure: Tunnels listen on
localhostby default to prevent external access - Port Permissions: Use ports ≥1024 to avoid requiring root privileges
brew services list # check service state
lazy-ssm tail # check recent logs
lazy-ssm --config ~/.lazy-ssm/config.yaml # run in foreground to see errors directly- Verify AWS CLI is installed:
aws --version - Verify SSM plugin is installed:
session-manager-plugin --version - Check AWS credentials:
aws sts get-caller-identity - Verify EC2 instance has SSM agent running
- Check IAM permissions for SSM sessions
- Verify the Name tag on your EC2 instances matches the pattern
- Check that you have
ec2:DescribeInstancesIAM permission - Ensure at least one matching instance is in running state
- Verify the instance has an IAM instance profile (required for SSM)
- Check your SSO configuration:
aws configure get sso_start_url --profile <name> - Try manual login first:
aws sso login --profile <name> - Verify SSO session hasn't been revoked in AWS IAM Identity Center
- Check
lazy-ssm tailfor tunnel startup errors - Ensure the RDS endpoint is correct
- Verify security groups allow traffic from the bastion instance
Use ports ≥1024 in your configuration, or run with sudo (not recommended).
go build -o lazy-ssm # build
go test ./... # run testsMIT License - See LICENSE file for details
Contributions are welcome! Please open an issue or submit a pull request.