Check ElastiCache Backup Retention and Automatic Backups

Close-up of hard drives mounted in a server storage array

Photo by Patrik Kernstock on Unsplash

ElastiCache backup retention is the SnapshotRetentionLimit on each replication group, node-based cluster and serverless cache: the number of days automatic backups are kept, from 1 to 35. A value of 0 means automatic backups are turned off. Check it with DescribeReplicationGroups, DescribeCacheClusters and DescribeServerlessCaches, and change it with the matching Modify call.

This example is for engineers who run Valkey or Redis OSS on ElastiCache and treat at least some of that data as more than a throwaway cache: sessions, rate limits, leaderboards, queues. If the node fails or someone flushes the wrong database, a backup is the difference between a warm restart and rebuilding state from scratch.

The TypeScript script for the AWS SDK for JavaScript v3 reports ElastiCache backup retention and the backup window for every cache in a Region, including serverless caches and standalone clusters. It’s report only unless you pass --ids, --days and --apply. It doesn’t look at usage or encryption; the scripts to find idle ElastiCache clusters and find ElastiCache clusters without encryption at rest or in transit cover those.

How does ElastiCache backup retention work?

When automatic backups are on, ElastiCache creates one backup a day and stores it in Amazon S3. The settings that matter:

  • Retention limit (SnapshotRetentionLimit): how many days each automatic backup is kept. The maximum is 35 days. At 0, automatic backups are off.
  • Backup window (SnapshotWindow): the daily time range, in UTC, when node-based clusters start the backup, for example 05:00-09:00. If you don’t set one, ElastiCache picks it.
  • Daily snapshot time (DailySnapshotTime): the equivalent setting on serverless caches.

Two behaviors catch people out. Automatic backups are deleted with the cache: deleting a cluster or replication group deletes all its automatic backups too, so take a final backup (FinalSnapshotIdentifier, or FinalSnapshotName for serverless) before deleting anything you might need. And manual backups have no retention limit, so they stay and cost money until someone deletes them.

Which ElastiCache caches can be backed up?

Cache type Automatic backups Where the retention lives
Valkey or Redis OSS replication group Yes; cluster mode enabled groups back up at the replication group level only ReplicationGroup.SnapshotRetentionLimit
Standalone Valkey or Redis OSS cluster Yes CacheCluster.SnapshotRetentionLimit
Serverless Valkey, Redis OSS or Memcached Yes ServerlessCache.SnapshotRetentionLimit
Node-based Memcached cluster No backups at all Not applicable

On node-based clusters, a backup can affect performance depending on available reserved memory. The ElastiCache guide recommends setting reserved-memory-percent and, in groups with replicas, taking backups from a read replica. Serverless backups have no performance impact.

What does backup storage cost?

As of September 2026, the Amazon ElastiCache pricing page lists backup storage at $0.085 per GiB per month for all AWS Regions, with no data transfer fees for creating a backup or restoring from one. The AWS Price List API file for us-east-1 (published 14 September 2026) has the same rate and describes the node-based rate as applying to backup storage beyond a free allocation, without stating the allocation’s size.

Item Price (as of September 2026)
Backup storage, node-based Valkey and Redis OSS $0.085 per GB-month beyond the free allocation
Snapshot storage, serverless Valkey, Redis OSS and Memcached $0.085 per GB-month
Data transfer to create or restore a backup No charge

Worked example. A Valkey replication group holds about 20 GiB of data and you set a retention of 7 days. If each daily backup is roughly the size of the dataset, you store up to 7 × 20 = 140 GiB. At $0.085 per GiB-month that’s at most 140 × $0.085 = $11.90 a month, before any free allocation. The same cache at 35 days would be up to 700 GiB, or $59.50 a month. For most caches, 3 to 7 days covers the realistic “we need yesterday’s data” cases.

To see what you actually pay, filter your bill by ElastiCache usage types; the script to get AWS billing details broken down by service is a starting point.

What does the script do?

  1. Reads replication groupspaginateDescribeReplicationGroups: engine, SnapshotRetentionLimit and SnapshotWindow.
  2. Reads standalone clusterspaginateDescribeCacheClusters, skipping clusters that belong to a replication group. Node-based Memcached is listed as “no backups”.
  3. Reads serverless cachespaginateDescribeServerlessCaches: SnapshotRetentionLimit and DailySnapshotTime.
  4. Flags gapsRetention 0 is “BACKUPS OFF”; retention below --min-days is flagged too.
  5. Sets retention on requestWith --apply, it calls ModifyReplicationGroup, ModifyCacheCluster or ModifyServerlessCache with SnapshotRetentionLimit for each ID in --ids. It doesn’t pass ApplyImmediately, so any unrelated pending changes keep their own schedule.

The paginate* helpers are explained in the guide to AWS SDK v3 paginators.

Prerequisites

  • Node.js 18 or later, npm, tsx and @aws-sdk/client-elasticache.
  • A read-only profile for the report; a profile that can modify ElastiCache for --apply.
  • If the caches are defined in CloudFormation, CDK or Terraform, set the retention there too, or the next deploy resets it.

Which IAM permissions does it need?

elasticache-backup-retention-policy.json

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ReadCaches",
      "Effect": "Allow",
      "Action": [
        "elasticache:DescribeReplicationGroups",
        "elasticache:DescribeCacheClusters",
        "elasticache:DescribeServerlessCaches"
      ],
      "Resource": "*"
    },
    {
      "Sid": "SetRetentionWithApply",
      "Effect": "Allow",
      "Action": [
        "elasticache:ModifyReplicationGroup",
        "elasticache:ModifyCacheCluster",
        "elasticache:ModifyServerlessCache"
      ],
      "Resource": [
        "arn:aws:elasticache:us-east-1:111122223333:replicationgroup:*",
        "arn:aws:elasticache:us-east-1:111122223333:cluster:*",
        "arn:aws:elasticache:us-east-1:111122223333:serverlesscache:*"
      ]
    }
  ]
}

Drop the second statement for an audit-only role. The IAM policy generator for TypeScript AWS SDK code can draft a policy like this from your own scripts.

The script to check ElastiCache backup retention

check-elasticache-backups.ts

// check-elasticache-backups.ts
// Reports the automatic backup settings of every ElastiCache replication group, standalone node-based
// cluster and serverless cache in a Region: backup retention in days (0 = off) and the backup window.
// Report only unless you pass --apply with --ids and --days: then it sets that retention on the caches
// you named (ModifyReplicationGroup, ModifyCacheCluster or ModifyServerlessCache).
// Usage:
//   npx tsx check-elasticache-backups.ts [--region eu-west-1] [--min-days 1]
//   npx tsx check-elasticache-backups.ts --ids sessions-prod,orders-cache --days 7 --apply
import {
  ElastiCacheClient,
  ModifyCacheClusterCommand,
  ModifyReplicationGroupCommand,
  ModifyServerlessCacheCommand,
  paginateDescribeCacheClusters,
  paginateDescribeReplicationGroups,
  paginateDescribeServerlessCaches,
} from "@aws-sdk/client-elasticache";

const args = process.argv.slice(2);
const flag = (name: string): string | undefined => {
  const i = args.indexOf(name);
  return i >= 0 ? args[i + 1] : undefined;
};
const region = flag("--region") ?? process.env.AWS_REGION ?? "us-east-1";
const minDays = Number(flag("--min-days") ?? "1");
const ids = new Set((flag("--ids") ?? "").split(",").map((s) => s.trim()).filter(Boolean));
const days = Number(flag("--days") ?? "0");
const apply = args.includes("--apply");
const ec = new ElastiCacheClient({ region });

type Kind = "replication group" | "cluster" | "serverless";
interface Row {
  Id: string;
  Kind: Kind;
  Engine: string;
  RetentionDays: number | string;
  Window: string;
  Status: string;
}

function verdict(retention: number | undefined): string {
  if (!retention) return "BACKUPS OFF";
  return retention < minDays ? `below ${minDays} days` : "ok";
}

async function main(): Promise<void> {
  if (apply && (ids.size === 0 || !Number.isInteger(days) || days < 1 || days > 35)) {
    console.error("--apply needs --ids and --days between 1 and 35.");
    process.exit(1);
  }
  const rows: Row[] = [];

  for await (const page of paginateDescribeReplicationGroups({ client: ec }, {})) {
    for (const rg of page.ReplicationGroups ?? []) {
      if (!rg.ReplicationGroupId) continue;
      rows.push({
        Id: rg.ReplicationGroupId,
        Kind: "replication group",
        Engine: rg.Engine ?? "redis",
        RetentionDays: rg.SnapshotRetentionLimit ?? 0,
        Window: rg.SnapshotWindow ?? "-",
        Status: verdict(rg.SnapshotRetentionLimit),
      });
    }
  }

  // Node-based clusters that aren't part of a replication group: standalone Valkey/Redis OSS or Memcached.
  for await (const page of paginateDescribeCacheClusters({ client: ec }, {})) {
    for (const c of page.CacheClusters ?? []) {
      if (!c.CacheClusterId || c.ReplicationGroupId) continue;
      const memcached = c.Engine === "memcached";
      rows.push({
        Id: c.CacheClusterId,
        Kind: "cluster",
        Engine: c.Engine ?? "?",
        RetentionDays: memcached ? "n/a" : (c.SnapshotRetentionLimit ?? 0),
        Window: memcached ? "-" : (c.SnapshotWindow ?? "-"),
        Status: memcached ? "no backups for node-based Memcached" : verdict(c.SnapshotRetentionLimit),
      });
    }
  }

  for await (const page of paginateDescribeServerlessCaches({ client: ec }, {})) {
    for (const s of page.ServerlessCaches ?? []) {
      if (!s.ServerlessCacheName) continue;
      rows.push({
        Id: s.ServerlessCacheName,
        Kind: "serverless",
        Engine: s.Engine ?? "?",
        RetentionDays: s.SnapshotRetentionLimit ?? 0,
        Window: s.DailySnapshotTime ? `daily at ${s.DailySnapshotTime}` : "-",
        Status: verdict(s.SnapshotRetentionLimit),
      });
    }
  }

  if (apply) {
    for (const row of rows.filter((r) => ids.has(r.Id))) {
      try {
        if (row.Kind === "replication group") {
          await ec.send(new ModifyReplicationGroupCommand({ ReplicationGroupId: row.Id, SnapshotRetentionLimit: days }));
        } else if (row.Kind === "serverless") {
          await ec.send(new ModifyServerlessCacheCommand({ ServerlessCacheName: row.Id, SnapshotRetentionLimit: days }));
        } else if (row.RetentionDays !== "n/a") {
          await ec.send(new ModifyCacheClusterCommand({ CacheClusterId: row.Id, SnapshotRetentionLimit: days }));
        } else {
          continue;
        }
        row.Status = `retention set to ${days} days`;
      } catch (err) {
        row.Status = `error: ${err instanceof Error ? err.name : String(err)}`;
      }
    }
  }

  const off = rows.filter((r) => r.RetentionDays === 0).length;
  console.log(`ElastiCache caches in ${region}: ${rows.length}; automatic backups off: ${off}`);
  if (rows.length) console.table(rows);
  if (!apply) console.log("Report only. Pass --ids, --days and --apply to change backup retention.");
}

main().catch((err) => {
  console.error(err);
  process.exit(1);
});

How do you run it?

Terminal

npm install @aws-sdk/client-elasticache
npm install --save-dev tsx typescript @types/node

# Report, flag anything kept for less than 7 days
AWS_PROFILE=readonly npx tsx check-elasticache-backups.ts --region us-east-1 --min-days 7

# Turn on 7-day retention for two caches
AWS_PROFILE=cache-admin npx tsx check-elasticache-backups.ts --region us-east-1 --ids sessions-prod,api-rate-limits --days 7 --apply

Sample output

Output

ElastiCache caches in us-east-1: 6; automatic backups off: 3
┌─────────┬─────────────────────┬─────────────────────┬─────────────┬───────────────┬──────────────────┬───────────────────────────────────────┐
│ (index) │ Id                  │ Kind                │ Engine      │ RetentionDays │ Window           │ Status                                │
├─────────┼─────────────────────┼─────────────────────┼─────────────┼───────────────┼──────────────────┼───────────────────────────────────────┤
│ 0       │ 'sessions-prod'     │ 'replication group' │ 'valkey'    │ 0             │ '05:00-06:00'    │ 'BACKUPS OFF'                         │
│ 1       │ 'orders-cache'      │ 'replication group' │ 'redis'     │ 1             │ '03:30-04:30'    │ 'below 7 days'                        │
│ 2       │ 'leaderboard'       │ 'replication group' │ 'valkey'    │ 7             │ '02:00-03:00'    │ 'ok'                                  │
│ 3       │ 'catalog-memcached' │ 'cluster'           │ 'memcached' │ 'n/a'         │ '-'              │ 'no backups for node-based Memcached' │
│ 4       │ 'legacy-redis'      │ 'cluster'           │ 'redis'     │ 0             │ '07:00-08:00'    │ 'BACKUPS OFF'                         │
│ 5       │ 'api-rate-limits'   │ 'serverless'        │ 'valkey'    │ 0             │ 'daily at 04:00' │ 'BACKUPS OFF'                         │
└─────────┴─────────────────────┴─────────────────────┴─────────────┴───────────────┴──────────────────┴───────────────────────────────────────┘
Report only. Pass --ids, --days and --apply to change backup retention.

Names are illustrative. Three caches have backups off, including the sessions-prod replication group and a serverless cache. orders-cache keeps backups for one day, below the 7-day minimum passed on the command line. The Memcached cluster can’t be backed up, which is fine if it only holds data you can rebuild.

What should the retention be?

  • Pure cache in front of a database. Retention 0 can be the right answer: the database is the source of truth and a cold cache refills itself.
  • State that lives only in the cache. Sessions, counters, queues, leaderboards: keep at least a few days, and test a restore into a new cache once.
  • Longer history or cross-account copies. Use manual backups or export to S3 on a schedule, and delete them on a schedule too. For databases, the scripts to find RDS instances without automated backups and enable DynamoDB point-in-time recovery cover the same question. The AWS Backup coverage report shows what AWS Backup already protects.

Troubleshooting

  • InvalidReplicationGroupStateFault or InvalidCacheClusterStateFault. The SDK describes these as the replication group or cluster not being in the available state, for example while it’s being modified or scaled. Wait until it’s available and run the --apply step again.
  • A cluster ID in --ids isn’t changed. Clusters that belong to a replication group are skipped; pass the replication group ID instead.
  • Retention changed, but no backup yet. The first automatic backup should run in the next backup window. Take a manual backup if you need one now.
  • Latency spikes at the same time every day. That’s the node-based backup window. Move SnapshotWindow to a quiet period or take backups from a replica.

Ask ChatWithCloud instead

Ask ChatWithCloud “Which ElastiCache clusters have automatic backups turned off?” It writes AWS SDK for JavaScript v2 code, runs it on your machine with your profile and lists them, for one profile and Region per session. It uses SDK v2, whose support ended on 8 September 2025, so API features added after that date may be missing from its answers. It runs generated code without a confirmation step; keep it on a read-only profile, set up as the guide to connect ChatWithCloud to your AWS account describes, and read the ChatWithCloud security model for what’s sent for processing.

Frequently asked questions

What is the maximum ElastiCache backup retention?

35 days. Set it with SnapshotRetentionLimit; a value of 0 turns automatic backups off.

Does ElastiCache for Memcached support backups?

Serverless Memcached does. Node-based Memcached clusters don’t support backup and restore.

Are ElastiCache backups deleted when I delete the cluster?

Automatic backups are. Manual backups stay until you delete them. Request a final backup when you delete a cluster, replication group or serverless cache.

How much do ElastiCache backups cost?

As of September 2026, $0.085 per GiB-month of backup storage in all Regions, with no data transfer charge for creating or restoring a backup.

Related guides

Ask your AWS account in plain English

Your first 15 runs are free, with no OpenAI key needed.

npx chatwithcloud