Oct 30, 20266 min read

How to Connect ECS Fargate to ElastiCache Redis

Reaching an ElastiCache Redis or Valkey cache from Fargate tasks: subnets and security groups, TLS and AUTH, cluster mode clients, and a cache declared in code that Encore provisions in your own AWS account.

Adding a cache to a Fargate service looks like one connection string. Then the task times out on connect because the security group does not allow it, the client fails the handshake because in-transit encryption is on, or MOVED errors appear because the cache was created with cluster mode enabled and the client is not cluster-aware. Each one is a few minutes to fix once you know it, and an afternoon when you do not.

This guide covers the networking, TLS, authentication and client choices for connecting ECS Fargate to ElastiCache for Redis OSS or Valkey, starting with the option where the cache is declared in code: Encore.

Fargate to ElastiCache options at a glance

EncoreElastiCache node-based clusterElastiCache Serverless
SetupA cache cluster declared in GoSubnet group, security group, replication groupSecurity group and a serverless cache
TLSConfigured by EncoreOptional, set at creationAlways on
AuthConfigured by EncoreAUTH token or RBAC usersRBAC users or IAM
Local developmentIn-memory Redis under encore runSeparate local RedisSeparate local Redis
Pricing modelElastiCache in your accountPer node hourPer GB stored and per ECPU

Each option is covered below, starting with Encore.

Declaring a cache in code with Encore

Encore combines an open source infrastructure SDK for TypeScript and Go with a platform that provisions and deploys into your own AWS or GCP account. In Go, a cache is a cluster plus typed keyspaces, declared next to the code that uses them. This read-through cache sits in front of a Postgres table:

-- products/migrations/1_create_products.up.sql CREATE TABLE products ( id BIGSERIAL PRIMARY KEY, name TEXT NOT NULL, price_cents INT NOT NULL );
// products/products.go package products import ( "context" "errors" "time" "encore.dev/beta/errs" "encore.dev/rlog" "encore.dev/storage/cache" "encore.dev/storage/sqldb" ) var db = sqldb.NewDatabase("products", sqldb.DatabaseConfig{Migrations: "./migrations"}) // ElastiCache on AWS, Memorystore on GCP, an in-memory Redis under encore run. var cluster = cache.NewCluster("products-cache", cache.ClusterConfig{ EvictionPolicy: cache.AllKeysLRU, }) type Product struct { ID int64 Name string PriceCents int } var byID = cache.NewStructKeyspace[int64, Product](cluster, cache.KeyspaceConfig{ KeyPattern: "product/:key", DefaultExpiry: cache.ExpireIn(5 * time.Minute), }) //encore:api public method=GET path=/products/:id func Get(ctx context.Context, id int64) (*Product, error) { p, err := byID.Get(ctx, id) if err == nil { return &p, nil } if !errors.Is(err, cache.Miss) { // The cache is an optimization, so fall through to Postgres on errors. rlog.Warn("cache read failed", "err", err) } p = Product{ID: id} err = db.QueryRow(ctx, `SELECT name, price_cents FROM products WHERE id = $1`, id). Scan(&p.Name, &p.PriceCents) if errors.Is(err, sqldb.ErrNoRows) { return nil, &errs.Error{Code: errs.NotFound, Message: "product not found"} } else if err != nil { return nil, err } if err := byID.Set(ctx, id, p); err != nil { rlog.Warn("cache write failed", "err", err) } return &p, nil }
Get started
brew install encoredev/tap/encore &&
encore app create
Copy

Run in your terminal to get started locally.

The keyspace is typed, so byID.Get returns a Product and the key pattern keeps product keys from colliding with other data in the same cluster. Locally, encore run starts Postgres and uses an in-memory Redis implementation, and tests get a separate in-memory cache each.

On AWS, Encore provisions the cluster as ElastiCache starting with cache.m6g.large nodes, Multi-AZ replication with automatic failover, and Redis ACLs for access control. It places the cache in the private storage subnets, which accept traffic only from the compute subnets where your services run, and connects your services to it, so there is no security group rule, endpoint or password to pass around. On GCP the same declaration becomes Memorystore.

cache.m6g.large is a production-sized node, so for a small side project it costs more than the smallest node you would pick by hand.

Option 1: a node-based ElastiCache cluster

The by-hand setup needs four pieces.

Networking. Create a subnet group from private subnets in the tasks' VPC, then a security group for the cache that allows the tasks' security group in:

aws ec2 authorize-security-group-ingress \ --group-id sg-0cache0000000000 \ --protocol tcp --port 6379 \ --source-group sg-0tasks0000000000

Referencing the task security group instead of a CIDR range keeps the rule correct as Fargate tasks come and go with new IPs.

Encryption. In-transit encryption makes the client connect with TLS, rediss:// instead of redis://. In go-redis that is a TLSConfig on the options, and a client without it often fails with a timeout or a closed connection rather than an error that mentions TLS.

Authentication. Either an AUTH token, which requires in-transit encryption, or RBAC users and user groups with per-user permissions. Store the token in Secrets Manager and inject it through the task definition secrets block, which needs secretsmanager:GetSecretValue on the task execution role.

Cluster mode. With cluster mode enabled, keys are sharded across node groups and the client must follow MOVED redirects, so use go-redis redis.NewClusterClient (or ioredis Cluster in Node.js) against the configuration endpoint. With cluster mode disabled, connect to the primary endpoint for writes and the reader endpoint for reads.

Option 2: ElastiCache Serverless

Serverless caches remove node sizing and scale on their own. In-transit encryption is always on, so the client must use TLS, and the endpoint behaves like a cluster mode enabled cache, so use a cluster-aware client. The networking steps are the same: same VPC, and a security group that allows the tasks in.

Billing is per GB of data stored and per ElastiCache Processing Unit, which suits spiky or unpredictable traffic and can cost more than a small node for steady load.

Good to know

  • Choose Valkey for new caches unless you depend on Redis-specific features. ElastiCache supports both, and the Redis clients work with Valkey.
  • Set an eviction policy that matches the use. A cache should evict (allkeys-lru). A job queue such as BullMQ requires noeviction, and should usually live on its own cluster.
  • Treat the cache as optional in the code path, as the Go example does: log cache errors and fall through to the database, so a cache failover slows requests instead of failing them.

How to choose

Use Encore when you want the cache declared in code, an in-memory Redis locally, and ElastiCache provisioned in private subnets in your own AWS account. Build a node-based ElastiCache cluster by hand when you need a specific node size, engine version or cluster mode layout, and use ElastiCache Serverless for spiky workloads where sizing nodes is the bigger cost.

Frequently asked questions

Why can my ECS task not connect to ElastiCache?

The three usual causes are networking, TLS and the client. The cache must be in the same VPC as the tasks (or a peered one), and its security group must allow inbound TCP 6379 from the tasks' security group. If in-transit encryption is enabled, which it always is on serverless caches, the client must connect with TLS (rediss://). And a cluster mode enabled cache needs a cluster-aware client.

Can I connect to ElastiCache from outside AWS?

Not directly. ElastiCache is reachable only from inside its VPC, so a laptop needs a tunnel through something in the VPC, such as SSM port forwarding through an EC2 instance, or a VPN. For local development, running Redis on your machine is usually simpler.

What is the easiest way to add Redis to a Go service on Fargate?

With Encore, declare cache.NewCluster("products-cache", ...) and a typed keyspace in your Go code. encore run uses an in-memory Redis implementation locally, and on AWS Encore provisions ElastiCache in private subnets and connects your services to it, with no security groups or connection strings to manage.

Deploy to your cloud on AWS and Google Cloud
without managing infrastructure

Encore lets developers and agents define application infrastructure directly in code, then automatically provisions it from local development to production in your AWS or GCP account.

~/orders — claude
Claude Code
Claude Code v2.1.180Opus 5.5~/orders