AWS ElastiCache with a Bastion Host using Terraform

Monday, Mar 9, 2020 | 3 minute read

Ricardo Ferreira

AWS ElastiCache is a fully managed service that allows users to easily and quickly use cache technologies like MemCached and Redis without the gory implementation details. But this doesn’t come for free. Developers often complain about the fact that the service is deployed in private subnets and due to that fact – they are not entitled to easily access for troubleshooting purposes.

A pattern to solve this problem is the usage of a bastion host, that sits between your machine and the private subnet to allow troubleshooting with ease. This post will show how to deploy a AWS ElastiCache service for Redis along with a bastion host using Terraform.

If you are used to implement Infrastructure-as-a-Code using Terraform then most of the code from this post won’t be new to you. The real magic relies on the networking plumbing that you need to code to ensure the end-to-end communication. For starters, you need to create an internet gateway to allow inbound communication from the internet:

resource "aws_internet_gateway" "internet_gateway" {
  vpc_id = aws_vpc.aws_vpc.id
  tags = {
    Name = "${var.global_prefix}-internet-gateway"
  }
}

resource "aws_route" "main_route" {
  route_table_id = aws_vpc.aws_vpc.main_route_table_id
  gateway_id = aws_internet_gateway.internet_gateway.id
  destination_cidr_block = "0.0.0.0/0"
}

Then, you need to ensure that the subnets for both the cache server and the bastion host are deployed in the same availability zones for the selected region:

data "aws_availability_zones" "available" {
  state = "available"
}

resource "aws_subnet" "cache_server" {
  vpc_id = aws_vpc.aws_vpc.id
  count = length(data.aws_availability_zones.available.names)
  cidr_block = element(var.private_cidr_blocks, count.index)
  map_public_ip_on_launch = false
  availability_zone = data.aws_availability_zones.available.names[count.index]
  tags = {
    Name = "${var.global_prefix}-cache-server-${count.index}"
  }
}

resource "aws_subnet" "bastion_host" {
  vpc_id = aws_vpc.aws_vpc.id
  cidr_block = "10.0.10.0/24"
  map_public_ip_on_launch = true
  availability_zone = data.aws_availability_zones.available.names[0]
  tags = {
    Name = "${var.global_prefix}-bastion-host"
  }
}

There has to have firewall rules that will allow SSH inbound access to the bastion host, as well as TCP inbound access from the bastion host to the cache server. Therefore you need to setup some security groups:

resource "aws_security_group" "cache_server" {
  name = "${var.global_prefix}-cache-server"
  description = "AWS ElastiCache for Redis"
  vpc_id = aws_vpc.aws_vpc.id
  ingress {
    from_port = 6379
    to_port = 6379
    protocol = "tcp"
    security_groups = [aws_security_group.bastion_host.id]
  }
  ingress {
    from_port = 6379
    to_port = 6379
    protocol = "tcp"
    cidr_blocks = var.private_cidr_blocks
  }
  egress {
    from_port = 0
    to_port = 0
    protocol = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
  tags = {
    Name = "${var.global_prefix}-cache-server"
  }
}

resource "aws_security_group" "bastion_host" {
  name = "${var.global_prefix}-bastion-host"
  description = "Cache Server Bastion Host"
  vpc_id = aws_vpc.aws_vpc.id
  ingress {
    from_port = 22
    to_port = 22
    protocol = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
  tags = {
    Name = "${var.global_prefix}-bastion-host"
  }
}

In order to access the bastion host from your machine, you will need to use the same private key that was used to provision the bastion host. Create a private key using the following constructs:

resource "tls_private_key" "private_key" {
  algorithm = "RSA"
  rsa_bits  = 4096
}

resource "aws_key_pair" "private_key" {
  key_name = var.global_prefix
  public_key = tls_private_key.private_key.public_key_openssh
}

resource "local_file" "private_key" {
  content  = tls_private_key.private_key.private_key_pem
  filename = "cert.pem"
}

resource "null_resource" "private_key_permissions" {
  depends_on = [local_file.private_key]
  provisioner "local-exec" {
    command = "chmod 600 cert.pem"
    interpreter = ["bash", "-c"]
    on_failure  = continue
  }
}

…finally, make sure the bastion host is created with the private key associated:

resource "aws_instance" "bastion_host" {
  depends_on = [aws_elasticache_replication_group.cache_server]
  ami = data.aws_ami.amazon_linux_2.id
  instance_type = "t2.micro"
  key_name = aws_key_pair.private_key.key_name
  subnet_id = aws_subnet.bastion_host.id
  vpc_security_group_ids = [aws_security_group.bastion_host.id]
  user_data = data.template_file.bastion_host.rendered
  root_block_device {
    volume_type = "gp2"
    volume_size = 100
  }
  tags = {
    Name = "${var.global_prefix}-bastion-host"
  }
}

I have created an end-to-end example on GitHub that shows how this is done:

https://github.com/riferrei/aws-elasticache-with-bastion-host

Happy troubleshooting!

© 2018 - 2026 Ricardo Ferreira

Search is powered by Pagefind. Just hit CTRL+K or CMD+K to start searching.

Powered by Hugo with Dream and Devrel themes.

Open Source

I contribute to LangChain4j, an idiomatic open source Java library for building LLM-powered applications on the JVM. Recent work includes adding native vector search embedding stores so developers can build RAG, recommendation engines, and AI memory systems.

I also ported RedisVL to Go, an open source, AI-native client that brings vector search, semantic caching, LLM memory, semantic routing, rerankers, and an MCP server to the Redis ecosystem for Golang developers.

Speaking

I’ve been speaking at conferences since 2008 and doing it full time as part of my work since 2018. Some of the events I’ve spoken at include AWS re:Invent, Microsoft Ignite, Google Cloud Next, KubeCon, Oracle OpenWorld, QCon, Strange Loop, Kafka Summit, Pulsar Summit, JavaOne, DevNexus, JFokus, JNation, and All Things Open.

Recordings of my talks live on my YouTube channel, and the code I write for talks, demos, and tutorials is on my GitHub.

Who am I?

I work at the intersection of distributed systems, AI, and data infrastructure, turning complex technology into things developers can understand, adopt, and build with.

Lately, that means hands-on AI engineering: building vector search, semantic caching, agent memory, and RAG into the data layer; contributing to LangChain4j and RedisVL for Golang; and figuring out how to make AI agents secure enough to ship.

The AI-native work isn’t a pivot. It draws on the same systems-design foundation I’ve built for 20+ years, moving data fast, at scale, close to compute, watching where systems break; now applied to vectors and agents. I have worked on event streaming with Apache Kafka and Flink at AWS and Confluent; observability at Elastic; as well as RDBMS, NoSQL, and Big Data at Oracle. That foundation is exactly what separates AI demos that work on stage from AI systems that survive production.

Social Links