ELK Stack — Index Lifecycle Management

5 minute read

Index Lifecycle Management

Index Lifecycle Management (ILM) automates the process of managing indices as they age. You define a policy that describes what should happen at each phase of an index’s life, and Elasticsearch handles the transitions for you.


Why ILM Matters

Without ILM you must manually:

  • Roll over indices when they get too large.
  • Move old indices to cheaper storage.
  • Force-merge read-only indices to reclaim space.
  • Delete indices after the retention period.

ILM eliminates this operational burden and ensures consistent behavior across your cluster.


ILM Phases Lifecycle

┌──────────┐     ┌──────────┐     ┌──────────┐     ┌──────────┐     ┌──────────┐
│          │     │          │     │          │     │          │     │          │
│   HOT    │────▶│   WARM   │────▶│   COLD   │────▶│  FROZEN  │────▶│  DELETE  │
│          │     │          │     │          │     │          │     │          │
│ Active   │     │ Read-    │     │ Infre-   │     │ Rarely   │     │ Remove   │
│ indexing │     │ heavy    │     │ quent    │     │ accessed │     │ index    │
│ & search │     │ queries  │     │ access   │     │ (search- │     │          │
│          │     │          │     │          │     │  able     │     │          │
│          │     │          │     │          │     │  snapshot)│     │          │
└──────────┘     └──────────┘     └──────────┘     └──────────┘     └──────────┘
   Day 0          Day 7           Day 30           Day 90           Day 365

Each phase is optional except hot. You can skip any phase that does not apply to your use case.


Phase Overview

Phase Purpose Typical Actions
Hot Active writes and frequent queries Rollover, set priority
Warm No more writes, still queried regularly Shrink, forcemerge, allocate, read-only
Cold Infrequent access, lower-cost hardware Allocate to cold nodes, freeze
Frozen Rare access via searchable snapshots Searchable snapshot mount
Delete Data no longer needed Delete index

Creating an ILM Policy

curl -s -X PUT "localhost:9200/_ilm/policy/logs-policy" \
  -H "Content-Type: application/json" \
  -d '{
  "policy": {
    "phases": {
      "hot": {
        "min_age": "0ms",
        "actions": {
          "rollover": {
            "max_primary_shard_size": "50gb",
            "max_age": "7d"
          },
          "set_priority": {
            "priority": 100
          }
        }
      },
      "warm": {
        "min_age": "7d",
        "actions": {
          "shrink": {
            "number_of_shards": 1
          },
          "forcemerge": {
            "max_num_segments": 1
          },
          "allocate": {
            "require": {
              "data": "warm"
            }
          },
          "set_priority": {
            "priority": 50
          }
        }
      },
      "cold": {
        "min_age": "30d",
        "actions": {
          "allocate": {
            "require": {
              "data": "cold"
            }
          },
          "set_priority": {
            "priority": 0
          }
        }
      },
      "delete": {
        "min_age": "365d",
        "actions": {
          "delete": {}
        }
      }
    }
  }
}'
{
  "acknowledged": true
}

Actions by Phase

Hot Phase Actions

Action Description
rollover Create a new index when the current one hits a size, age, or document count threshold
set_priority Set the index priority for recovery ordering

Warm Phase Actions

Action Description
shrink Reduce the number of primary shards
forcemerge Merge segments to reduce storage and improve query speed
allocate Move index to specific nodes (e.g., warm-tier hardware)
readonly Set the index to read-only
set_priority Adjust recovery priority

Cold Phase Actions

Action Description
allocate Move to cold-tier nodes
freeze Freeze the index to reduce memory overhead (deprecated in 8.x, use frozen tier)
searchable_snapshot Mount as a searchable snapshot
set_priority Adjust recovery priority

Delete Phase Actions

Action Description
delete Permanently remove the index
wait_for_snapshot Wait for a snapshot before deleting

Attaching an ILM Policy to an Index Template

The recommended approach is to reference the ILM policy in your index template so every new index automatically gets the policy:

curl -s -X PUT "localhost:9200/_index_template/logs-template" \
  -H "Content-Type: application/json" \
  -d '{
  "index_patterns": ["logs-*"],
  "template": {
    "settings": {
      "number_of_shards": 2,
      "number_of_replicas": 1,
      "index.lifecycle.name": "logs-policy",
      "index.lifecycle.rollover_alias": "logs-write"
    },
    "aliases": {
      "logs-write": {
        "is_write_index": true
      }
    }
  },
  "priority": 200
}'
{
  "acknowledged": true
}

Bootstrap the First Index

For rollover to work with aliases, bootstrap the first index with a numeric suffix:

curl -s -X PUT "localhost:9200/logs-000001" \
  -H "Content-Type: application/json" \
  -d '{
  "aliases": {
    "logs-write": {
      "is_write_index": true
    }
  }
}'
{
  "acknowledged": true,
  "shards_acknowledged": true,
  "index": "logs-000001"
}

Monitoring ILM Status

Check ILM Status for an Index

curl -s -X GET "localhost:9200/logs-000001/_ilm/explain?pretty"
{
  "indices": {
    "logs-000001": {
      "index": "logs-000001",
      "managed": true,
      "policy": "logs-policy",
      "lifecycle_date_millis": 1711958400000,
      "age": "3d",
      "phase": "hot",
      "phase_time_millis": 1711958400000,
      "action": "complete",
      "action_time_millis": 1711958400000,
      "step": "complete",
      "step_time_millis": 1711958400000,
      "phase_execution": {
        "policy": "logs-policy",
        "phase_definition": {
          "min_age": "0ms",
          "actions": {
            "rollover": {
              "max_primary_shard_size": "50gb",
              "max_age": "7d"
            },
            "set_priority": {
              "priority": 100
            }
          }
        }
      }
    }
  }
}

Check Global ILM Operating Mode

curl -s -X GET "localhost:9200/_ilm/status?pretty"
{
  "operation_mode": "RUNNING"
}

Get a Specific ILM Policy

curl -s -X GET "localhost:9200/_ilm/policy/logs-policy?pretty"

Manual ILM Operations

Manually Move an Index to the Next Phase

If you need to force an index into a different phase immediately:

curl -s -X POST "localhost:9200/_ilm/move/logs-000001" \
  -H "Content-Type: application/json" \
  -d '{
  "current_step": {
    "phase": "hot",
    "action": "complete",
    "name": "complete"
  },
  "next_step": {
    "phase": "warm",
    "action": "shrink",
    "name": "shrink"
  }
}'
{
  "acknowledged": true
}

Retry a Failed ILM Step

When an ILM step fails (e.g., allocation timeout), retry it:

curl -s -X POST "localhost:9200/_ilm/retry/logs-000001"
{
  "acknowledged": true
}

Stop and Start ILM

# Stop ILM (useful during maintenance)
curl -s -X POST "localhost:9200/_ilm/stop"

# Start ILM
curl -s -X POST "localhost:9200/_ilm/start"

Practical Example: Application Logs

Here is a complete ILM setup for application logs that:

  1. Rolls over every 30 GB or 1 day.
  2. Shrinks to 1 shard and force-merges after 2 days.
  3. Moves to cold storage after 14 days.
  4. Deletes after 90 days.
curl -s -X PUT "localhost:9200/_ilm/policy/app-logs-policy" \
  -H "Content-Type: application/json" \
  -d '{
  "policy": {
    "phases": {
      "hot": {
        "min_age": "0ms",
        "actions": {
          "rollover": {
            "max_primary_shard_size": "30gb",
            "max_age": "1d"
          }
        }
      },
      "warm": {
        "min_age": "2d",
        "actions": {
          "shrink": { "number_of_shards": 1 },
          "forcemerge": { "max_num_segments": 1 }
        }
      },
      "cold": {
        "min_age": "14d",
        "actions": {
          "allocate": {
            "require": { "data": "cold" }
          }
        }
      },
      "delete": {
        "min_age": "90d",
        "actions": {
          "delete": {}
        }
      }
    }
  }
}'
{
  "acknowledged": true
}

Deleting an ILM Policy

curl -s -X DELETE "localhost:9200/_ilm/policy/logs-policy"
{
  "acknowledged": true
}

Note: You cannot delete a policy that is currently in use by any index. Remove the policy reference from all indices first.


Quick Reference

Task Command
Create / update ILM policy PUT _ilm/policy/<policy-name>
Get ILM policy GET _ilm/policy/<policy-name>
Delete ILM policy DELETE _ilm/policy/<policy-name>
Explain ILM for an index GET <index>/_ilm/explain
Check ILM operating mode GET _ilm/status
Manually move to next step POST _ilm/move/<index>
Retry a failed step POST _ilm/retry/<index>
Stop ILM POST _ilm/stop
Start ILM POST _ilm/start

Next → Aliases