When managing cloud infrastructure, you often want to securely access services like Redis (ElastiCache) that live in private subnets. Direct access from your machine is usually not allowed — and that’s a good thing. But what if you still want to test or inspect your Redis cluster?
One approach is to set up a bastion host with SSM (Session Manager) and use it to tunnel requests to ElastiCache from your localhost — without exposing anything to the public internet.
Let’s break down how to do it using Terraform and the AWS CLI.
Infrastructure Overview
Here’s what we’re setting up:
A bastion EC2 instance in a public subnet.
A Redis ElastiCache cluster in private subnets.
Security Groups allowing just enough access to get the job done.
SSM Session Manager to connect to the bastion host without SSH or public key management.
Terraform Resources
- EC2 Bastion host
resource "aws_instance" "bastion_host" {
ami = "ami-02f3f602d23f1659d"
instance_type = "t3.micro"
subnet_id = var.subnet_id_a
associate_public_ip_address = true
key_name = "your_key"
iam_instance_profile = aws_iam_instance_profile.ssm_profile.name
vpc_security_group_ids = [aws_security_group.bastion_sg.id]
root_block_device {
volume_size = 10
}
user_data = file("user_data.sh")
}
- IAM Role for SSM
resource "aws_iam_role" "ssm_role" {
name = "ssm-role"
assume_role_policy = jsonencode({
Version = "2012-10-17",
Statement = [{
Effect = "Allow",
Principal = {
Service = "ec2.amazonaws.com"
},
Action = "sts:AssumeRole"
}]
})
managed_policy_arns = [
"arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
]
}
resource "aws_iam_instance_profile" "ssm_profile" {
name = "ssm-profile"
role = aws_iam_role.ssm_role.name
}
- Bastion Host Security Group Allow only egress to required ports (Redis, HTTP, HTTPS):
resource "aws_security_group" "bastion_sg" {
name = "bastion-sg"
vpc_id = var.vpc_id
egress {
from_port = 6379
to_port = 6379
protocol = "tcp"
cidr_blocks = ["10.0.0.0/16"] # restrict to your VPC range
}
egress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
- ElastiCache Redis Cluster
resource "aws_elasticache_cluster" "redis" {
cluster_id = "redis-dev"
engine = "redis"
node_type = "cache.t2.micro"
num_cache_nodes = 1
port = 6379
subnet_group_name = aws_elasticache_subnet_group.redis_subnets.name
security_group_ids = [aws_security_group.redis_sg.id]
}
- Redis Security Group
Allow ingress only from the bastion host:
resource "aws_security_group" "redis_sg" {
name = "redis-sg"
vpc_id = var.vpc_id
ingress {
from_port = 6379
to_port = 6379
protocol = "tcp"
security_groups = [aws_security_group.bastion_sg.id]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
Accessing Redis from Localhost
- Start the SSM session On your local machine, use the AWS CLI to start a session:
aws ssm start-session \
--target i-xxxxxxxxxxxxxxxxx \
--profile your-aws-profile \
--region us-east-1
- Connect to Redis from the bastion
redis-cli -h redis-dev.xxxxxx.use1.cache.amazonaws.com -p 6379
You should see:
redis-dev.xxxxxx.use1.cache.amazonaws.com:6379> ping
PONG
Nice — Redis is alive 🎉
Bonus: Security Best Practices
✅ Never use 0.0.0.0/0 unless you’re testing — always restrict IPs or CIDRs.
✅ Use private subnets for ElastiCache.
✅ Limit egress/ingress traffic via security groups.
✅ Prefer SSM over SSH for bastion access (no exposed ports).
✅ Remove public IPs once everything is connected through VPC peering or VPNs.