Elasticsearch Index Lifecycle

Time-based data loses value as it ages. Yesterday's logs get searched constantly. Last year's logs get searched rarely. Keeping everything on the fastest, most expensive storage wastes money, and keeping everything forever fills the disks. Index Lifecycle Management (ILM) moves indexes through stages automatically and deletes them when their time ends.

The Lifecycle Phases

An index passes through up to five phases. Each phase matches a different level of attention and a different type of hardware.

  HOT          WARM          COLD          FROZEN        DELETE
  ─────────    ─────────     ─────────     ─────────     ─────────
  Receives     Read often,   Read rarely,  Read very     Removed
  new data     no writes     cheaper disk  rarely        for good
  Fast disks   Slower disks  Slowest disks Snapshot-based

  Day 0        Day 7         Day 30        Day 90        Day 180
  ──────────────────────────── age of index ───────────────────→
PhaseIndex StatusTypical Hardware
HotAccepts writes, searched oftenFast SSD nodes
WarmRead-only, searched sometimesMid-range nodes
ColdRead-only, searched rarelyLarge, cheaper disks
FrozenSearched occasionally from snapshotsObject storage
DeleteRemovedNone

Smaller projects skip phases freely. A policy with only hot and delete phases is perfectly valid. Frozen storage and searchable snapshots belong to paid license tiers, so check your subscription before relying on them.

Rollover: Starting a New Index at the Right Moment

One endless index grows too large to manage. Rollover creates a fresh index when the current one reaches a size, age, or document limit. Writes move to the new index, and the old one stays searchable.

logs-000001  ──(reaches 50 GB or 7 days)──→  closed to writes
                                              │
                                              ▼
logs-000002  ←── new writes arrive here  ─────┘

Creating a Policy

A policy lists phases, the age when each begins, and the actions to run in each phase.

PUT /_ilm/policy/logs-policy
{
  "policy": {
    "phases": {
      "hot": {
        "actions": {
          "rollover": {
            "max_primary_shard_size": "50gb",
            "max_age": "7d"
          }
        }
      },
      "warm": {
        "min_age": "7d",
        "actions": {
          "shrink": { "number_of_shards": 1 },
          "forcemerge": { "max_num_segments": 1 }
        }
      },
      "delete": {
        "min_age": "90d",
        "actions": { "delete": {} }
      }
    }
  }
}

The min_age counts from the moment of rollover, not from the first write. Rollover time marks the start of the clock for later phases.

Common Actions

ActionPhaseEffect
rolloverHotStarts a new index when limits hit
shrinkWarmReduces the number of shards
forcemergeWarmMerges segments for faster reads and less disk use
allocateWarm, ColdChanges replica count or placement
readonlyWarmBlocks writes
deleteDeleteRemoves the index

Attaching the Policy with a Template

A template gives every new index the policy name. Combine this with the template topic from earlier in the course for a fully automatic setup.

PUT /_index_template/logs-template
{
  "index_patterns": ["logs-*"],
  "template": {
    "settings": {
      "index.lifecycle.name": "logs-policy",
      "index.lifecycle.rollover_alias": "logs"
    }
  }
}

PUT /logs-000001
{
  "aliases": { "logs": { "is_write_index": true } }
}

Applications write to the alias "logs." Elasticsearch routes each write to the current write index and rolls over when the limits trigger.

Data Streams: The Simpler Route

Data streams wrap the alias and rollover setup into a single concept for append-only data. A template with a data_stream section creates one, and ILM handles the rest. New log and metric projects usually start here.

PUT /_index_template/app-stream
{
  "index_patterns": ["app-logs"],
  "data_stream": {},
  "template": { "settings": { "index.lifecycle.name": "logs-policy" } }
}

POST /app-logs/_doc
{ "@timestamp": "2026-11-04T10:00:00Z", "message": "Service started" }

Checking Progress

The explain API reveals the current phase, the action in progress, and any error that blocked a step.

GET /logs-000001/_ilm/explain

Output highlights:
  "phase": "warm"
  "action": "shrink"
  "step": "complete"

Policy checks run every ten minutes by default, so phase changes may lag the exact age slightly. A stuck index shows a failed step in the explain output, and the retry endpoint restarts it after the cause is fixed.

POST /logs-000001/_ilm/retry

Policy Design Tips

Match retention to business or legal needs, and write the number of days into the policy name or notes. Size rollover by shard size rather than only by age, since traffic varies from day to day. Test policies on a small index with short ages before applying them to production data.

Sizing the Phases

Estimate daily data volume first. A system that writes 20 GB per day fills a 50 GB shard in about two and a half days, so a seven-day age limit rarely triggers before the size limit does. Keep the policy honest by checking which limit fires most often, and adjust the numbers when the traffic pattern changes.