In-Memory Caching: ElastiCache Redis Cluster with Subnet Group and Auth
Scenario
Your application's database is under heavy read load because every API response requires multiple queries for session data, product catalog lookups, and user preferences. You need an in-memory caching layer using Amazon ElastiCache for Redis with high availability (Multi-AZ with automatic failover), encryption in-transit and at-rest, and AUTH token authentication to prevent unauthorized access.
- Create an ElastiCache subnet group covering private subnets in at least 2 AZs.
- Create an ElastiCache parameter group for Redis 7.x that sets maxmemory-policy to allkeys-lru (evict least recently used keys when memory is full).
- Generate a secure AUTH token using random_password and store it in Secrets Manager.
- Create an aws_elasticache_replication_group with 2 nodes (cache.r7g.large), Multi-AZ automatic failover, TLS (transit_encryption_enabled), at-rest encryption (at_rest_encryption_enabled), and the AUTH token.
- Create a security group allowing Redis port (6379) only from the application tier CIDR.
- Output the primary endpoint, the reader endpoint, and the secret ARN containing the auth token.
Additional Context
Replication Group vs Cluster Mode: A replication group without cluster mode has one primary shard with up to 5 read replicas — great for read-heavy workloads. Cluster mode splits data across multiple shards for write scaling (e.g., 5 shards × 2 nodes = 10 nodes). For most applications, a single-shard replication group with replicas is the right starting point.
AUTH token requirement: When transit_encryption_enabled (TLS) is enabled and an auth_token is set, clients must use the AUTH command with the token before running any Redis commands. Without TLS, the AUTH token cannot be set (Redis rejects it). The token must be 16-128 characters.
variables.tf
variable "aws_region" { type = string; default = "us-east-1" }
variable "vpc_id" { type = string }
variable "subnet_ids" { type = list(string); description = "Private subnets in 2+ AZs." }
variable "app_cidr" { type = string; default = "10.0.0.0/16"; description = "App tier CIDR allowed to connect." }
variable "environment" { type = string; default = "prod" }
variable "cache_name" { type = string; default = "app-cache" }main.tf
provider "aws" { region = var.aws_region }
locals { tags = { Environment = var.environment, ManagedBy = "terraform" } }
# AUTH token — must be 16-128 characters, stored in Secrets Manager.
resource "random_password" "redis_auth" {
length = 32
special = false # Redis AUTH token must not contain @, : or /
}
resource "aws_secretsmanager_secret" "redis_auth" {
name = "${var.cache_name}-auth-token-${var.environment}"
recovery_window_in_days = 7
tags = local.tags
}
resource "aws_secretsmanager_secret_version" "redis_auth" {
secret_id = aws_secretsmanager_secret.redis_auth.id
secret_string = random_password.redis_auth.result
}
# Subnet group — must span 2+ AZs for Multi-AZ.
resource "aws_elasticache_subnet_group" "main" {
name = "${var.cache_name}-subnet-group-${var.environment}"
subnet_ids = var.subnet_ids
tags = local.tags
}
# Parameter group — configure eviction policy.
resource "aws_elasticache_parameter_group" "redis7" {
name = "${var.cache_name}-params-${var.environment}"
family = "redis7"
# evict least recently used keys when memory is full.
parameter { name = "maxmemory-policy"; value = "allkeys-lru" }
# Increase the number of databases.
parameter { name = "databases"; value = "16" }
tags = local.tags
}
# Security group — only app tier can connect on Redis port.
resource "aws_security_group" "redis" {
name = "${var.cache_name}-sg-${var.environment}"
vpc_id = var.vpc_id
ingress {
description = "Redis from app tier"
from_port = 6379; to_port = 6379; protocol = "tcp"
cidr_blocks = [var.app_cidr]
}
egress { from_port = 0; to_port = 0; protocol = "-1"; cidr_blocks = ["0.0.0.0/0"] }
tags = merge(local.tags, { Name = "${var.cache_name}-sg" })
}
# ElastiCache Replication Group — Redis 7 with Multi-AZ, TLS, and AUTH.
resource "aws_elasticache_replication_group" "main" {
replication_group_id = "${var.cache_name}-${var.environment}"
description = "Redis cache for ${var.cache_name} — ${var.environment}"
node_type = "cache.r7g.large"
num_cache_clusters = 2 # 1 primary + 1 replica
automatic_failover_enabled = true
multi_az_enabled = true
engine_version = "7.1"
port = 6379
parameter_group_name = aws_elasticache_parameter_group.redis7.name
subnet_group_name = aws_elasticache_subnet_group.main.name
security_group_ids = [aws_security_group.redis.id]
# Encryption — both required when using AUTH token.
transit_encryption_enabled = true
at_rest_encryption_enabled = true
auth_token = random_password.redis_auth.result
maintenance_window = "sun:05:00-sun:06:00"
snapshot_retention_limit = 7 # daily snapshots for 7 days
snapshot_window = "03:00-04:00"
# Avoid destroy/recreate on minor version upgrades.
apply_immediately = false
tags = local.tags
}outputs.tf
output "primary_endpoint" {
value = aws_elasticache_replication_group.main.primary_endpoint_address
}
output "reader_endpoint" {
value = aws_elasticache_replication_group.main.reader_endpoint_address
}
output "redis_port" { value = 6379 }
output "auth_secret_arn" { value = aws_secretsmanager_secret.redis_auth.arn }
output "auth_token" { value = random_password.redis_auth.result; sensitive = true }Workflow Commands
terraform init
terraform apply -var="vpc_id=vpc-xxxx" -var='subnet_ids=["subnet-aaa","subnet-bbb"]'
# Retrieve auth token from Secrets Manager
AUTH=$(aws secretsmanager get-secret-value \
--secret-id $(terraform output -raw auth_secret_arn) \
--query SecretString --output text)
# Test connectivity (from EC2 in the app tier)
redis-cli -h $(terraform output -raw primary_endpoint) \
-p 6379 --tls --no-auth-warning -a "$AUTH" ping
# Expected: PONG
# Test a key-value operation
redis-cli -h $(terraform output -raw primary_endpoint) \
-p 6379 --tls --no-auth-warning -a "$AUTH" \
SET session:user123 '{"role":"admin","lastLogin":"2024-01-15"}' EX 3600
redis-cli -h $(terraform output -raw reader_endpoint) \
-p 6379 --tls --no-auth-warning -a "$AUTH" GET session:user123
terraform destroy -var="vpc_id=..." -var='subnet_ids=["..."]'Apply complete! Resources: 7 added, 0 changed, 0 destroyed. Outputs: auth_secret_arn = "arn:aws:secretsmanager:us-east-1:123456789012:secret:app-cache-auth-token-prod-xxxxxx" auth_token = <sensitive> primary_endpoint = "app-cache-prod.abc123.ng.0001.use1.cache.amazonaws.com" reader_endpoint = "app-cache-prod-ro.abc123.ng.0001.use1.cache.amazonaws.com" redis_port = 6379

