A secure-by-default Amazon ElastiCache deployment β encrypted at rest, TLS-required in transit, Multi-AZ with automatic failover, and snapshot-backed β delivering a Redis/Valkey replication group (cluster mode disabled or enabled) or a Memcached cluster, with its subnet group, parameter group, and optional RBAC users/user group, from a single composite call. Built for the AWS provider v6.x.
- β‘ Provisions an Amazon ElastiCache cache β the keystone is
aws_elasticache_replication_groupfor Redis/Valkey, oraws_elasticache_clusterfor Memcached. The two paths are mutually exclusive and selected byvar.engine. - π Owns the subnet group (private subnets, β₯ 2 AZs for Multi-AZ) and an optional parameter group so a single call yields a complete, private topology.
- π₯ Optionally creates RBAC users (
aws_elasticache_user) as afor_eachmap keyed by user id, bundled into one user group (aws_elasticache_user_group) and bound to the replication group β the modern alternative to a sharedauth_token. - π Secure by default: at-rest encryption on (AWS-managed key, or a caller CMK), in-transit TLS on, Multi-AZ + automatic failover on, and 7-day snapshot retention β all opt-out, never opt-in.
- π§© Consumes networking, security, and KMS by reference β it never creates a VPC, security group, or KMS key itself.
- π Secrets (AUTH token, RBAC passwords) are referenced from Secrets Manager rather than inlined; the module emits no plaintext credential.
π‘ Why it matters: ElastiCache routinely holds session state, tokens, and cached PII under privacy-regulation. An unencrypted or publicly-reachable cache is a far larger blast radius than the convenience saved by a loose default β so this module ships locked down and makes you opt out explicitly.
If these Terraform modules have been helpful to you or your organization, I'd appreciate your support in any of the following ways:
- β Star this repository to help others discover this Terraform module.
- π€ Connect with me on LinkedIn: linkedin.com/in/microsoftexpert
- β Buy me a coffee: buymeacoffee.com/microsoftexpert
Whether it's a star, a professional connection, or a coffee, every gesture helps keep these modules actively maintained and continually improving. Thank you for being part of the community!
flowchart LR
VPC["terraform-aws-vpc"]
SG["terraform-aws-security-group"]
KMS["terraform-aws-kms"]
SM["terraform-aws-secrets-manager"]
CWL["terraform-aws-cloudwatch-log-group"]
EC["terraform-aws-elasticache"]
APP["Application tier<br/>(ECS / EKS / EC2)"]
VPC -->|subnet_ids| EC
SG -->|security_group_ids| EC
KMS -->|kms_key_arn| EC
SM -->|auth_token / user passwords| EC
EC -->|slow-log / engine-log| CWL
EC -->|primary / reader / configuration endpoint| APP
style EC fill:#FF9900,color:#fff
ElastiCache sits at the data / caching tier. It is downstream of the networking, security-group, and KMS foundations and upstream of the application tier that connects to its endpoints. Secrets Manager supplies the AUTH token / RBAC passwords; CloudWatch Logs (or Kinesis Firehose) is the optional log destination.
flowchart TD
subgraph caller["Caller-supplied (by reference)"]
SUBNETS["subnet_ids<br/>(terraform-aws-vpc)"]
SGS["security_group_ids<br/>(terraform-aws-security-group)"]
CMK["kms_key_arn<br/>(terraform-aws-kms)"]
SECRET["auth_token / passwords<br/>(terraform-aws-secrets-manager)"]
end
subgraph mod["terraform-aws-elasticache"]
SUB["aws_elasticache_subnet_group.this"]
PG["aws_elasticache_parameter_group.this<br/>(optional)"]
U["aws_elasticache_user.this<br/>for_each β RBAC users (Redis/Valkey)"]
UG["aws_elasticache_user_group.this<br/>(optional)"]
RG["aws_elasticache_replication_group.this<br/>keystone A β Redis/Valkey"]
MC["aws_elasticache_cluster.this<br/>keystone B β Memcached"]
end
SUBNETS --> SUB
SUB --> RG
SUB --> MC
PG --> RG
PG --> MC
SGS --> RG
SGS --> MC
CMK --> RG
SECRET --> RG
SECRET --> U
U --> UG
UG --> RG
style RG fill:#FF9900,color:#fff
| Resource | Role |
|---|---|
aws_elasticache_replication_group.this |
Keystone A β Redis/Valkey replication group (cluster mode disabled or enabled). Encryption, HA, backups, RBAC binding |
aws_elasticache_cluster.this |
Keystone B β Memcached cluster (selectable alternative, mutually exclusive with A) |
aws_elasticache_subnet_group.this |
Placement across caller-supplied private subnets (β₯ 2 AZs for Multi-AZ) |
aws_elasticache_parameter_group.this |
Engine parameters (created only when parameter_group is set; e.g. cluster-enabled = yes) |
aws_elasticache_user.this |
RBAC users, for_each over var.users (Redis/Valkey only) |
aws_elasticache_user_group.this |
RBAC user group bound to the replication group (created only when user_group is set) |
| Requirement | Version |
|---|---|
| Terraform | >= 1.12.0 |
hashicorp/aws |
>= 6.0, < 7.0 |
No provider {} block is declared inside the module β it inherits the caller's configured provider (and credential chain / Region). See the AWS Prerequisites Region note below.
The Terraform identity needs the following (least-privilege). RBAC and KMS actions are needed only on the Redis/Valkey path with their respective features enabled.
| Action | Required for | Notes |
|---|---|---|
elasticache:CreateReplicationGroup, elasticache:ModifyReplicationGroup, elasticache:DeleteReplicationGroup, elasticache:DescribeReplicationGroups |
Redis/Valkey lifecycle | Keystone A |
elasticache:CreateCacheCluster, elasticache:ModifyCacheCluster, elasticache:DeleteCacheCluster, elasticache:DescribeCacheClusters |
Memcached lifecycle | Keystone B |
elasticache:CreateCacheSubnetGroup, elasticache:ModifyCacheSubnetGroup, elasticache:DeleteCacheSubnetGroup, elasticache:DescribeCacheSubnetGroups |
Subnet group | Module-owned |
elasticache:CreateCacheParameterGroup, elasticache:ModifyCacheParameterGroup, elasticache:DeleteCacheParameterGroup, elasticache:DescribeCacheParameterGroups |
Parameter group | Only when parameter_group is set |
elasticache:CreateUser, elasticache:ModifyUser, elasticache:DeleteUser, elasticache:DescribeUsers |
RBAC users | Redis/Valkey; only when users is set |
elasticache:CreateUserGroup, elasticache:ModifyUserGroup, elasticache:DeleteUserGroup, elasticache:DescribeUserGroups |
RBAC user group | Redis/Valkey; only when user_group is set |
elasticache:AddTagsToResource, elasticache:RemoveTagsFromResource, elasticache:ListTagsForResource |
Tagging | All taggable resources |
elasticache:CreateSnapshot, elasticache:DescribeSnapshots |
Final / manual snapshots | final_snapshot_identifier on destroy (Redis/Valkey) |
kms:DescribeKey, kms:CreateGrant, kms:RetireGrant |
CMK at-rest encryption | Only when kms_key_arn (CMK) supplied (Redis/Valkey) |
iam:CreateServiceLinkedRole |
First-time AWSServiceRoleForElastiCache creation |
One-time per account; harmless if it already exists |
β οΈ Scope the resource ARNs in your policy toarn:aws:elasticache:<region>:<account>:replicationgroup:*/:cluster:*/:subnetgroup:*/:parametergroup:*/:user:*/:usergroup:*patterns rather than"*"where your governance allows.
- Service-linked role:
AWSServiceRoleForElastiCacheis auto-created on first ElastiCache use (iam:CreateServiceLinkedRole). It lets the service manage ENIs and resources inside your VPC. - Networking: the supplied
subnet_idsmust be private (no public endpoint β PII/privacy-regulation baseline) and span at least two Availability Zones when Multi-AZ / automatic failover is enabled (both defaulttrue). The security groups must allow the cache port (6379 Redis/Valkey, 11211 Memcached) from the application security group only β never0.0.0.0/0. - Customer-managed KMS key (optional, Redis/Valkey): to use a CMK for at-rest encryption, its key policy must allow the ElastiCache service principal (
elasticache.amazonaws.com) the standardkms:Encrypt/Decrypt/GenerateDataKey*/CreateGrantset. Togglingat_rest_encryption_enabledis FORCE-NEW. - In-transit encryption + AUTH:
transit_encryption_enabled = true(default) is required to use anauth_tokenor RBAC. AUTH tokens are 16β128 printable characters. On engine versions < 7.0.5 enabling in-transit encryption is FORCE-NEW; from 7.0.5+ usetransit_encryption_mode(preferredβrequired) for a no-downtime migration. - RBAC
defaultuser: ElastiCache requires thedefaultuser to be a member of any user group β include it inusersor viauser_group.additional_user_ids. - Durability mode (optional):
durability(Valkey) requires Valkey 9.0+, cluster mode enabled, Multi-AZ, and one replica per shard. See ElastiCache durability limitations. - Engine / node availability: the chosen
engine(redis/valkey/memcached),engine_version, andnode_typemust be offered in the target Region.data_tiering_enabledrequires an r6gd node type. - Region model: the module relies on provider inheritance β there is no
regionvariable. The caller's provider block (or alias) sets the Region. ElastiCache is a regional service β no us-east-1 global-service constraint applies. - Service quotas: default soft limit of 300 nodes per Region (and per cluster/replication-group limits), all raisable via Service Quotas. See Quotas for ElastiCache.
terraform-aws-elasticache/
βββ providers.tf # terraform{} + required_providers (aws >= 6.0, < 7.0); no provider{} block
βββ variables.tf # typed inputs: identity β required β optional β tags β timeouts
βββ main.tf # subnet group, parameter group, RBAC users/group, replication group (this) / cluster (this)
βββ outputs.tf # id + arn, endpoints, RBAC maps, subnet/parameter group, tags_all
βββ SCOPE.md # boundary, IAM, prerequisites, gotchas, secure defaults
βββ README.md # this file
The smallest working call β networking, security, and KMS wired from upstream modules:
module "elasticache" {
source = "git::https://github.com/microsoftexpert/terraform-aws-elasticache?ref=v1.0.0"
name = "casey-core-redis"
engine = "redis"
node_type = "cache.t4g.medium"
subnet_ids = module.vpc.private_subnet_ids
security_group_ids = [module.redis_sg.id]
tags = {
Environment = "prod"
DataClass = "PII"
CostCenter = "platform-data"
}
}Everything not shown inherits the secure baseline: at-rest encryption on, TLS on, Multi-AZ + automatic failover on (num_cache_clusters = 2), and 7-day snapshot retention.
| Input | Type | Source module |
|---|---|---|
subnet_ids |
list(string) |
terraform-aws-vpc (private subnets, β₯ 2 AZs) |
security_group_ids |
list(string) |
terraform-aws-security-group |
kms_key_arn |
string (KMS key ARN, optional) |
terraform-aws-kms |
auth_token / user passwords |
string (sensitive, optional) |
terraform-aws-secrets-manager |
notification_topic_arn |
string (SNS ARN, optional) |
app-integration module |
log_delivery_configuration[*].destination |
string (log group / Firehose name) |
terraform-aws-cloudwatch-log-group |
| Output | Description | Consumed by |
|---|---|---|
id |
Replication group id (Redis/Valkey) or cluster id (Memcached) | references |
arn |
ElastiCache ARN β cross-resource reference type | IAM policies, monitoring, AWS Backup |
name |
Cache identifier | CLI / console |
engine |
redis / valkey / memcached |
conditionals |
engine_version_actual |
Running engine version | drift / audit |
primary_endpoint_address |
Redis/Valkey primary (write) endpoint β cluster mode disabled | application write config |
reader_endpoint_address |
Redis/Valkey reader endpoint β cluster mode disabled | read traffic |
configuration_endpoint_address |
Cluster-mode-enabled / Memcached configuration endpoint | client auto-discovery |
cluster_address |
Memcached DNS name without port | application config |
port |
Cache port (6379 / 11211) | application config |
cluster_enabled |
Whether sharding is on (Redis/Valkey) | tooling |
member_clusters |
Member node identifiers (Redis/Valkey) | monitoring |
cache_nodes |
Per-node objects (Memcached) | monitoring / DNS |
subnet_group_name / subnet_group_arn |
Cache subnet group | references |
parameter_group_name / parameter_group_arn |
Parameter group | references |
user_ids / user_arns |
RBAC users created (id list / idβARN map) | audit |
user_group_id / user_group_arn |
RBAC user group bound to the group | references |
tags_all |
All tags incl. provider default_tags |
governance / audit |
1 Β· Minimal Redis (secure defaults)
module "elasticache" {
source = "git::https://github.com/microsoftexpert/terraform-aws-elasticache?ref=v1.0.0"
name = "casey-redis-min"
engine = "redis"
node_type = "cache.t4g.medium"
subnet_ids = module.vpc.private_subnet_ids
security_group_ids = [module.redis_sg.id]
}At-rest + in-transit encryption, Multi-AZ, automatic failover, and 7-day snapshots are all on by default.
2 Β· Customer-managed KMS key (CMK) for at-rest encryption
module "elasticache" {
source = "git::https://github.com/microsoftexpert/terraform-aws-elasticache?ref=v1.0.0"
name = "casey-redis-cmk"
engine = "redis"
node_type = "cache.r7g.large"
subnet_ids = module.vpc.private_subnet_ids
security_group_ids = [module.redis_sg.id]
at_rest_encryption_enabled = true # default
kms_key_arn = module.kms.arn # auditable, independently-revocable CMK
}Toggling
at_rest_encryption_enabledis FORCE-NEW β choose encryption (and the CMK) at creation.
3 Β· Tags (merge with provider default_tags)
# Provider-level default_tags is the CALLER's concern (root module / pipeline):
provider "aws" {
default_tags { tags = { ManagedBy = "terraform", Org = "" } }
}
module "elasticache" {
source = "git::https://github.com/microsoftexpert/terraform-aws-elasticache?ref=v1.0.0"
name = "casey-redis-tagged"
engine = "redis"
node_type = "cache.t4g.medium"
subnet_ids = module.vpc.private_subnet_ids
security_group_ids = [module.redis_sg.id]
tags = {
Environment = "prod"
DataClass = "PII"
Org = "-Data" # resource tag wins over default_tags on key conflict
}
}
# module.elasticache.tags_all => { ManagedBy, Org=-Data, Environment, DataClass }4 Β· Redis cluster mode enabled (sharded)
module "elasticache" {
source = "git::https://github.com/microsoftexpert/terraform-aws-elasticache?ref=v1.0.0"
name = "casey-redis-sharded"
engine = "redis"
node_type = "cache.r7g.large"
subnet_ids = module.vpc.private_subnet_ids # 3 AZs
security_group_ids = [module.redis_sg.id]
# Cluster mode enabled β num_node_groups forbids num_cache_clusters
num_node_groups = 3
replicas_per_node_group = 2
# Cluster mode requires a parameter group with cluster-enabled = yes
parameter_group = {
family = "redis7"
parameters = { "cluster-enabled" = "yes" }
}
}Three shards Γ (1 primary + 2 replicas) = 9 nodes. Clients use the configuration endpoint for auto-discovery.
5 Β· RBAC users + user group (preferred over AUTH token)
module "elasticache" {
source = "git::https://github.com/microsoftexpert/terraform-aws-elasticache?ref=v1.0.0"
name = "casey-redis-rbac"
engine = "redis"
node_type = "cache.r7g.large"
subnet_ids = module.vpc.private_subnet_ids
security_group_ids = [module.redis_sg.id]
transit_encryption_enabled = true # required for RBAC
users = {
app-rw = {
user_name = "app-rw"
access_string = "on ~* +@all"
passwords = [data.aws_secretsmanager_secret_version.app_rw.secret_string] # sensitive
}
app-ro = {
user_name = "app-ro"
access_string = "on ~* +@read"
passwords = [data.aws_secretsmanager_secret_version.app_ro.secret_string]
}
default = {
user_name = "default"
access_string = "off ~* -@all" # locked-down default user
no_password_required = true
}
}
user_group = {
user_group_id = "casey-redis-rbac-ug"
engine = "redis"
# the module includes every `users` key automatically; add externally-managed ids here
}
}ElastiCache requires the
defaultuser to be a member of the user group β included above.
6 Β· Legacy AUTH token from Secrets Manager
data "aws_secretsmanager_secret_version" "redis_auth" {
secret_id = "casey/redis/auth-token"
}
module "elasticache" {
source = "git::https://github.com/microsoftexpert/terraform-aws-elasticache?ref=v1.0.0"
name = "casey-redis-authtoken"
engine = "redis"
node_type = "cache.t4g.medium"
subnet_ids = module.vpc.private_subnet_ids
security_group_ids = [module.redis_sg.id]
transit_encryption_enabled = true # required for auth_token
auth_token = data.aws_secretsmanager_secret_version.redis_auth.secret_string
auth_token_update_strategy = "ROTATE"
}Prefer RBAC (example 5). Where an AUTH token is unavoidable, source it from Secrets Manager β never inline a literal.
7 Β· Valkey engine
module "elasticache" {
source = "git::https://github.com/microsoftexpert/terraform-aws-elasticache?ref=v1.0.0"
name = "casey-valkey"
engine = "valkey"
engine_version = "8.0"
node_type = "cache.r7g.large"
subnet_ids = module.vpc.private_subnet_ids
security_group_ids = [module.redis_sg.id]
}Valkey is the Redis-compatible, lower-cost engine β RBAC, encryption, and HA all apply identically.
8 Β· Memcached cluster (cross-AZ)
module "elasticache" {
source = "git::https://github.com/microsoftexpert/terraform-aws-elasticache?ref=v1.0.0"
name = "casey-memcached"
engine = "memcached"
node_type = "cache.t4g.medium"
subnet_ids = module.vpc.private_subnet_ids
security_group_ids = [module.memcached_sg.id]
num_cache_nodes = 3
az_mode = "cross-az"
transit_encryption_enabled = true # Memcached 1.6.12+ supports in-transit; at-rest is unsupported
}Memcached takes the
aws_elasticache_clusterpath. RBAC, replication, Multi-AZ, snapshots, and at-rest encryption do not apply β those inputs are ignored.
9 Β· Custom parameter group (module-created)
module "elasticache" {
source = "git::https://github.com/microsoftexpert/terraform-aws-elasticache?ref=v1.0.0"
name = "casey-redis-params"
engine = "redis"
node_type = "cache.r7g.large"
subnet_ids = module.vpc.private_subnet_ids
security_group_ids = [module.redis_sg.id]
parameter_group = {
family = "redis7"
description = "Redis 7 tuned parameters"
parameters = {
"maxmemory-policy" = "allkeys-lru"
"timeout" = "300"
}
}
}The module creates <name>-params and associates it (takes precedence over parameter_group_name).
10 Β· Log delivery to CloudWatch Logs
module "elasticache" {
source = "git::https://github.com/microsoftexpert/terraform-aws-elasticache?ref=v1.0.0"
name = "casey-redis-logs"
engine = "redis"
node_type = "cache.r7g.large"
subnet_ids = module.vpc.private_subnet_ids
security_group_ids = [module.redis_sg.id]
log_delivery_configuration = {
slow = {
destination = module.redis_slow_log.name # terraform-aws-cloudwatch-log-group
destination_type = "cloudwatch-logs"
log_format = "json"
log_type = "slow-log"
}
engine = {
destination = module.redis_engine_log.name
destination_type = "cloudwatch-logs"
log_format = "json"
log_type = "engine-log"
}
}
}Max 2 configs β one
slow-logand oneengine-log. The log destination is caller-owned and referenced by name.
11 Β· Custom backup + maintenance windows, 35-day retention
module "elasticache" {
source = "git::https://github.com/microsoftexpert/terraform-aws-elasticache?ref=v1.0.0"
name = "casey-redis-backups"
engine = "redis"
node_type = "cache.r7g.large"
subnet_ids = module.vpc.private_subnet_ids
security_group_ids = [module.redis_sg.id]
snapshot_retention_limit = 35
snapshot_window = "04:00-05:00" # UTC
maintenance_window = "sun:05:30-sun:06:30" # UTC
final_snapshot_identifier = "casey-redis-backups-final"
}12 Β· Data tiering (r6gd nodes)
module "elasticache" {
source = "git::https://github.com/microsoftexpert/terraform-aws-elasticache?ref=v1.0.0"
name = "casey-redis-tiered"
engine = "redis"
node_type = "cache.r6gd.xlarge" # r6gd required for data tiering
subnet_ids = module.vpc.private_subnet_ids
security_group_ids = [module.redis_sg.id]
data_tiering_enabled = true
}Data tiering offloads colder data to local NVMe SSD β only on r6gd node types.
13 Β· Restore from snapshot
module "elasticache" {
source = "git::https://github.com/microsoftexpert/terraform-aws-elasticache?ref=v1.0.0"
name = "casey-redis-restored"
engine = "redis"
node_type = "cache.r7g.large"
subnet_ids = module.vpc.private_subnet_ids
security_group_ids = [module.redis_sg.id]
snapshot_name = "casey-core-redis-2026-06-18" # FORCE-NEW
}14 Β· Disposable dev cache (secure defaults relaxed)
module "elasticache" {
source = "git::https://github.com/microsoftexpert/terraform-aws-elasticache?ref=v1.0.0"
name = "casey-redis-dev"
engine = "redis"
node_type = "cache.t4g.small"
subnet_ids = module.vpc.private_subnet_ids
security_group_ids = [module.redis_sg.id]
# OPT-OUTS β dev only, never in prod / PII:
num_cache_clusters = 1 # single node
automatic_failover_enabled = false
multi_az_enabled = false
snapshot_retention_limit = 0 # disables snapshots
apply_immediately = true
tags = { Environment = "dev", DataClass = "synthetic" }
}At-rest and in-transit encryption remain on β even disposable caches stay encrypted.
15 Β· π End-to-end composition (VPC β SG β KMS β Secrets β ElastiCache)
module "vpc" {
source = "git::https://github.com/microsoftexpert/terraform-aws-vpc?ref=v1.0.0"
name = "casey-data"
vpc_cidr = "10.40.0.0/16"
#... produces private_subnet_ids across 3 AZs
}
module "kms" {
source = "git::https://github.com/microsoftexpert/terraform-aws-kms?ref=v1.0.0"
description = "CMK for ElastiCache at-rest encryption"
alias = "alias/casey-redis"
}
module "redis_sg" {
source = "git::https://github.com/microsoftexpert/terraform-aws-security-group?ref=v1.0.0"
name = "casey-redis-sg"
vpc_id = module.vpc.id
ingress_rules = {
redis = {
from_port = 6379
to_port = 6379
ip_protocol = "tcp"
referenced_security_group_id = module.app_sg.id # app tier only β never 0.0.0.0/0
description = "Redis from application tier"
}
}
}
data "aws_secretsmanager_secret_version" "redis_app" {
secret_id = "casey/redis/app-rw"
}
module "elasticache" {
source = "git::https://github.com/microsoftexpert/terraform-aws-elasticache?ref=v1.0.0"
name = "casey-core-redis"
engine = "redis"
engine_version = "7.1"
node_type = "cache.r7g.large"
subnet_ids = module.vpc.private_subnet_ids
security_group_ids = [module.redis_sg.id]
kms_key_arn = module.kms.arn
transit_encryption_enabled = true
users = {
app-rw = { user_name = "app-rw", access_string = "on ~* +@all", passwords = [data.aws_secretsmanager_secret_version.redis_app.secret_string] }
default = { user_name = "default", access_string = "off ~* -@all", no_password_required = true }
}
user_group = { user_group_id = "casey-core-redis-ug", engine = "redis" }
snapshot_retention_limit = 14
final_snapshot_identifier = "casey-core-redis-final"
tags = {
Environment = "prod"
DataClass = "PII"
Compliance = "privacy-regulation"
}
}
output "redis_primary_endpoint" { value = module.elasticache.primary_endpoint_address }
output "redis_reader_endpoint" { value = module.elasticache.reader_endpoint_address }
output "redis_arn" { value = module.elasticache.arn }| Name | Type | Default | Description |
|---|---|---|---|
name |
string |
β (required) | Replication group / cluster id. FORCE-NEW. 1β40 chars, lowercase |
engine |
string |
"redis" |
redis / valkey β replication group; memcached β cluster. FORCE-NEW |
node_type |
string |
β (required) | Node instance class (e.g. cache.r7g.large). r6gd required for data tiering |
subnet_ids |
list(string) |
β (required) | Subnet group members (private, β₯ 2 AZs for Multi-AZ) |
security_group_ids |
list(string) |
[] |
Security groups attached to the cache |
description |
string |
null |
Replication group description (Redis/Valkey) |
engine_version |
string |
null |
Engine version; null = latest at create |
port |
number |
null |
Cache port; null = engine default (6379 / 11211) |
num_cache_clusters |
number |
2 |
Nodes (1 primary + replicas), cluster mode disabled |
num_node_groups / replicas_per_node_group |
number |
null |
Shards / replicas-per-shard, cluster mode enabled |
cluster_mode / durability |
string |
null |
Explicit cluster-mode / Valkey durability |
data_tiering_enabled |
bool |
false |
Data tiering (r6gd only) |
at_rest_encryption_enabled |
bool |
true |
At-rest encryption. FORCE-NEW (Redis/Valkey) |
kms_key_arn |
string |
null |
CMK for at-rest encryption (else AWS-managed key) |
transit_encryption_enabled |
bool |
true |
In-transit TLS; required for AUTH/RBAC |
transit_encryption_mode |
string |
null |
preferred / required β no-downtime migration |
auth_token / auth_token_update_strategy |
string |
null / ROTATE |
Legacy AUTH token (sensitive); prefer RBAC |
automatic_failover_enabled |
bool |
true |
Auto-promote replica on failure (Redis/Valkey) |
multi_az_enabled |
bool |
true |
Multi-AZ support (Redis/Valkey) |
snapshot_retention_limit |
number |
7 |
Snapshot retention days (0β35; 0 disables) |
snapshot_window / maintenance_window |
string |
null |
UTC windows |
snapshot_arns / snapshot_name |
list/string |
null |
Seed / restore from snapshot. snapshot_name FORCE-NEW |
final_snapshot_identifier |
string |
null |
Final snapshot on destroy (Redis/Valkey) |
parameter_group_name |
string |
null |
Existing parameter group to associate |
parameter_group |
object |
null |
Module-creates a dedicated parameter group <name>-params |
log_delivery_configuration |
map(object) |
{} |
slow-log / engine-log to CloudWatch / Firehose (max 2) |
users |
map(object) |
{} |
RBAC users keyed by user id (Redis/Valkey) |
user_group / user_group_ids |
object / list |
null |
RBAC user group (module-created or external) |
num_cache_nodes / az_mode / preferred_availability_zones |
β | 1 / null / null |
Memcached topology |
outpost_mode / preferred_outpost_arn |
string |
null |
Memcached Outposts placement |
apply_immediately / auto_minor_version_upgrade |
bool |
false / true |
Maintenance behavior |
notification_topic_arn |
string |
null |
SNS topic for ElastiCache notifications |
ip_discovery / network_type |
string |
null |
IP version / dual-stack |
tags |
map(string) |
{} |
Tags merged onto all taggable resources |
timeouts |
object |
{} |
create / update / delete timeouts |
Full type schemas and per-field descriptions live in
variables.tf.
See the Emits table above. Primary outputs are id and arn; connectivity is exposed as primary_endpoint_address / reader_endpoint_address (cluster mode disabled) or configuration_endpoint_address (cluster mode enabled / Memcached); RBAC data is exposed as user_ids / user_arns / user_group_id; tags_all reflects the merged tag set.
- ARN / ID formats.
- Redis/Valkey
arn:arn:aws:elasticache:<region>:<account>:replicationgroup:<name>;id= the replication group id (name). - Memcached
arn:arn:aws:elasticache:<region>:<account>:cluster:<name>;id= the cluster id (name). - Subnet group
arn::subnetgroup:<name>-subnets; parameter grouparn::parametergroup:<name>-params; RBAC userarn::user:<user_id>. - Two mutually-exclusive keystones.
engine = memcachedselectsaws_elasticache_cluster.this; everything else selectsaws_elasticache_replication_group.this. RBAC, replication, Multi-AZ, snapshots, and at-rest encryption are Redis/Valkey-only β those inputs are silently ignored on the Memcached path. - FORCE-NEW (immutable) fields.
name(replication_group_id / cluster_id),engine,subnet_group_namemembership, togglingat_rest_encryption_enabled, enabling/disabling cluster mode (cluster_enabled),snapshot_name, and β on engine versions < 7.0.5 βtransit_encryption_enabledall destroy-and-recreate the cache (and its data). The module-created parameter group usescreate_before_destroyso family changes don't deadlock. - Cluster mode disabled vs enabled.
num_cache_clusters(disabled) andnum_node_groups/replicas_per_node_group(enabled) are mutually exclusive β the module nullsnum_cache_clusterswhenevernum_node_groupsis set. Cluster mode enabled also needs a parameter group withcluster-enabled = yes. tagsβtags_allβdefault_tags. The module sets only resource-leveltags(merged with per-user tags on RBAC users viamerge(var.tags, each.value.tags)). The provider'sdefault_tagsis the caller's concern (never set inside a module). On a key collision the resource tag wins.tags_all(output) is the computed union AWS actually applied β use it for drift checks and audit.- Eventual consistency. Replication-group and node creation is asynchronous and can take many minutes; endpoints (
primary_endpoint_address,reader_endpoint_address,configuration_endpoint_address) are stable DNS names that follow failover, so applications should resolve them at connect time rather than caching IPs. - Destroy ordering. Terraform tears down the cache β user group β users / parameter group / subnet group. A subnet group or parameter group cannot be deleted while a cache still references it; the dependency graph normally orders this, but a half-failed destroy can leave a group pinned by a lingering cache. With
final_snapshot_identifierset, a final snapshot is taken before the Redis/Valkey group is deleted. There are no NAT/ENI destroy hazards beyond the service-managed ENIs the SLR cleans up. - No us-east-1 constraint. ElastiCache is a regional service β none of the us-east-1 global-service rules (CloudFront/WAF/ACM) apply. Rely on provider inheritance for the Region.
Secure by default; every weakening is an explicit, documented opt-out.
| Posture | Default | How to opt out |
|---|---|---|
| At-rest encryption | at_rest_encryption_enabled = true (AWS-managed key) |
false (Redis/Valkey; discouraged) |
| Customer-managed key | available via kms_key_arn |
omit for the AWS-managed key |
| In-transit TLS | transit_encryption_enabled = true |
false (strongly discouraged) |
| Public exposure | private subnets only β no public endpoint | n/a |
| Authentication | RBAC user group or AUTH token (TLS on) | open access (discouraged) |
| Automatic failover | automatic_failover_enabled = true |
false (single node) |
| Multi-AZ | multi_az_enabled = true |
false (single AZ) |
| Backups | snapshot_retention_limit = 7 |
0 (disables snapshots; discouraged) |
| Final snapshot on destroy | recommended via final_snapshot_identifier |
leave null (skips it) |
Other principles: exactly four .tf files; one keystone named this per engine path; child collections (users, parameter group, user group) via for_each over map(object) (never count); deeply-typed object schemas with optional defaults; validation {} on every closed value set; no credential or region variables; secrets referenced not stored; primary outputs id + arn; tags_all surfaced.
# Validate (no credentials needed)
terraform init -backend=false
terraform validate
terraform fmt -check
# Plan / apply (requires AWS credentials + Region)
# credentials via AWS_PROFILE / SSO / OIDC; Region via the provider block
terraform plan -out tfplan
terraform apply tfplan
plan/applyrequire a valid credential chain (profile / SSO / OIDC web-identity) and a configured Region. The module declares noprovider {}block β supply it (and anyassume_role) at the root. Replication-group creation can take several minutes; raisetimeouts.createfor large sharded topologies.
terraform init -backend=false && terraform validateβ schema and reference integrity.terraform fmt -checkβ canonical formatting.terraform planagainst a sandbox account β confirm secure defaults render (at-rest + in-transit encryption, Multi-AZ, automatic failover, 7-day snapshots) and that the correct keystone (replication_groupvscluster) is selected byengine.- Post-apply smoke test: connect over TLS from an in-VPC host to the
primary_endpoint_address(orconfiguration_endpoint_address) on 6379, authenticate with an RBAC user, and runPING/INFO replication.
Apply complete! Resources: 5 added, 0 changed, 0 destroyed.
Outputs:
arn = "arn:aws:elasticache:us-east-2:123456789012:replicationgroup:casey-core-redis"
id = "casey-core-redis"
redis_primary_endpoint = "master.casey-core-redis.abc123.use2.cache.amazonaws.com"
redis_reader_endpoint = "replica.casey-core-redis.abc123.use2.cache.amazonaws.com"
user_group_id = "casey-core-redis-ug"
| Symptom | Likely cause | Fix |
|---|---|---|
| Tag drift on every plan | default_tags overlaps a key the module also sets |
Drop the duplicate from one side; resource tags win β reconcile in the root module |
AccessDenied on elasticache:CreateReplicationGroup |
Identity lacks the elasticache: actions |
Attach the Required IAM Permissions |
InvalidParameterValue:... AWSServiceRoleForElastiCache |
SLR not yet created and iam:CreateServiceLinkedRole missing |
Grant iam:CreateServiceLinkedRole, or pre-create the SLR |
automatic_failover_enabled rejected |
num_cache_clusters < 2 |
Set num_cache_clusters >= 2, or disable failover for single-node dev |
| Cluster-mode parameter group error | num_node_groups set without cluster-enabled = yes |
Supply a parameter_group with parameters = { "cluster-enabled" = "yes" } |
auth_token / RBAC rejected |
transit_encryption_enabled = false |
Enable in-transit encryption β it is required for AUTH/RBAC |
User group create fails β missing default |
ElastiCache requires default user membership |
Add default to users or user_group.additional_user_ids |
| Toggling encryption shows full replacement | at_rest_encryption_enabled / transit_encryption_enabled (<7.0.5) are FORCE-NEW |
Set them at creation; for in-transit on 7.0.5+ use transit_encryption_mode migration |
| Subnet/parameter group won't delete | A lingering cache still references it (half-failed destroy) | Remove the cache first; Terraform's graph normally orders this |
CacheClusterNotFound / quota errors |
Region soft limit (300 nodes) hit | Raise the quota via Service Quotas |
| Credential-chain errors on plan/apply | No profile/SSO/OIDC resolved, or wrong Region | Set AWS_PROFILE / assume the role; confirm the provider Region |
- Amazon ElastiCache User Guide
- Quotas for ElastiCache
- Using service-linked roles for ElastiCache
- ElastiCache durability limitations (Valkey 9.0+)
- Authenticating with RBAC
- Terraform
aws_elasticache_replication_group - Terraform
aws_elasticache_cluster - Module
SCOPE.mdβ boundary, IAM, prerequisites, gotchas - Upstream:
terraform-aws-vpc,terraform-aws-security-group,terraform-aws-kms,terraform-aws-secrets-manager,terraform-aws-cloudwatch-log-group
π§‘ "Infrastructure as Code should be standardized, consistent, and secure."