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

  1. 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")
}
  1. 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
}
  1. 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"]
  }
}
  1. 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]
}
  1. 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

  1. 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
  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.