Configure Elasticsearch 8 index lifecycle management with hot-warm-cold architecture for automated data tiering

Advanced 45 min Apr 25, 2026
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Set up Elasticsearch 8 with hot-warm-cold node architecture and automated index lifecycle management policies to optimize storage costs and query performance. Configure ILM policies that automatically move data through different tiers based on age and usage patterns.

Prerequisites

  • Elasticsearch 8.x cluster with at least 3 nodes
  • Different storage types for each tier
  • 8GB RAM minimum per node
  • Root or sudo access

What this solves

Elasticsearch clusters accumulate massive amounts of data over time, making storage expensive and queries slower. Index Lifecycle Management (ILM) with hot-warm-cold architecture automatically moves older data to cheaper storage tiers while keeping recent data on fast nodes. This reduces costs by 60-80% while maintaining query performance for active data.

Prerequisites

  • At least 3 servers with 8GB RAM each for a proper cluster
  • Different storage types: fast SSD for hot nodes, standard SSD for warm, spinning disks for cold
  • Elasticsearch 8.x cluster already running (see our Elasticsearch 8 installation guide)

Step-by-step configuration

Configure hot node settings

Hot nodes handle active indexing and recent data queries. They need fast CPUs and SSDs.

# Hot node configuration
node.name: es-hot-01
node.roles: [ master, data_hot, data_content, ingest ]
node.attr.data_tier: hot
node.attr.box_type: hot

Performance settings for hot nodes

path.data: /var/lib/elasticsearch bootstrap.memory_lock: true cluster.name: production-cluster network.host: 0.0.0.0 http.port: 9200 transport.port: 9300

Discovery settings

discovery.seed_hosts: ["10.0.1.10", "10.0.1.11", "10.0.1.12"] cluster.initial_master_nodes: ["es-hot-01", "es-warm-01", "es-cold-01"]

Security

xpack.security.enabled: true xpack.security.transport.ssl.enabled: true xpack.security.http.ssl.enabled: true

Configure warm node settings

Warm nodes store less frequently accessed data and handle occasional queries.

# Warm node configuration
node.name: es-warm-01
node.roles: [ master, data_warm ]
node.attr.data_tier: warm
node.attr.box_type: warm

Warm nodes can have less memory allocated

path.data: /var/lib/elasticsearch bootstrap.memory_lock: true cluster.name: production-cluster network.host: 0.0.0.0 http.port: 9200 transport.port: 9300

Discovery settings

discovery.seed_hosts: ["10.0.1.10", "10.0.1.11", "10.0.1.12"] cluster.initial_master_nodes: ["es-hot-01", "es-warm-01", "es-cold-01"]

Security

xpack.security.enabled: true xpack.security.transport.ssl.enabled: true xpack.security.http.ssl.enabled: true

Configure cold node settings

Cold nodes store archived data with slower access times but much lower storage costs.

# Cold node configuration
node.name: es-cold-01
node.roles: [ master, data_cold ]
node.attr.data_tier: cold
node.attr.box_type: cold

Cold storage optimization

path.data: /var/lib/elasticsearch bootstrap.memory_lock: true cluster.name: production-cluster network.host: 0.0.0.0 http.port: 9200 transport.port: 9300

Less aggressive caching for cold nodes

indices.queries.cache.size: 5% indices.fielddata.cache.size: 10%

Discovery settings

discovery.seed_hosts: ["10.0.1.10", "10.0.1.11", "10.0.1.12"] cluster.initial_master_nodes: ["es-hot-01", "es-warm-01", "es-cold-01"]

Security

xpack.security.enabled: true xpack.security.transport.ssl.enabled: true xpack.security.http.ssl.enabled: true

Set JVM heap sizes per node type

Configure appropriate heap sizes based on node roles and available memory.

# Hot nodes - 50% of available RAM, max 31GB
-Xms4g
-Xmx4g
# Warm/cold nodes - can use less heap
-Xms2g
-Xmx2g
# Hot nodes - 50% of available RAM, max 31GB
-Xms4g
-Xmx4g
# Warm/cold nodes - can use less heap
-Xms2g
-Xmx2g

Restart Elasticsearch on all nodes

Apply the configuration changes by restarting each node one at a time.

# Restart hot nodes first
sudo systemctl restart elasticsearch
sudo systemctl status elasticsearch

Wait 30 seconds, then restart warm nodes

sleep 30 sudo systemctl restart elasticsearch

Wait 30 seconds, then restart cold nodes

sleep 30 sudo systemctl restart elasticsearch

Create ILM policy for log data

Define a lifecycle policy that moves data through hot-warm-cold phases automatically.

curl -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": "10gb",
            "max_age": "7d"
          },
          "set_priority": {
            "priority": 100
          }
        }
      },
      "warm": {
        "min_age": "7d",
        "actions": {
          "migrate": {
            "enabled": true
          },
          "forcemerge": {
            "max_num_segments": 1
          },
          "shrink": {
            "number_of_shards": 1
          },
          "set_priority": {
            "priority": 50
          }
        }
      },
      "cold": {
        "min_age": "30d",
        "actions": {
          "migrate": {
            "enabled": true
          },
          "set_priority": {
            "priority": 0
          }
        }
      },
      "delete": {
        "min_age": "365d",
        "actions": {
          "delete": {}
        }
      }
    }
  }
}'

Create index template with ILM integration

Set up an index template that automatically applies the ILM policy to new indices.

curl -X PUT "localhost:9200/_index_template/logs-template" -H 'Content-Type: application/json' -d'
{
  "index_patterns": ["logs-*"],
  "data_stream": {
    "timestamp_field": {
      "name": "@timestamp"
    }
  },
  "template": {
    "settings": {
      "index": {
        "lifecycle": {
          "name": "logs-policy",
          "rollover_alias": "logs"
        },
        "number_of_shards": 2,
        "number_of_replicas": 1,
        "refresh_interval": "30s"
      }
    },
    "mappings": {
      "properties": {
        "@timestamp": {
          "type": "date"
        },
        "level": {
          "type": "keyword"
        },
        "message": {
          "type": "text",
          "fields": {
            "keyword": {
              "type": "keyword",
              "ignore_above": 256
            }
          }
        },
        "host": {
          "type": "keyword"
        }
      }
    }
  },
  "priority": 200
}'

Create metrics ILM policy

Configure a separate policy for metrics data with different retention periods.

curl -X PUT "localhost:9200/_ilm/policy/metrics-policy" -H 'Content-Type: application/json' -d'
{
  "policy": {
    "phases": {
      "hot": {
        "min_age": "0ms",
        "actions": {
          "rollover": {
            "max_primary_shard_size": "5gb",
            "max_age": "1d"
          },
          "set_priority": {
            "priority": 100
          }
        }
      },
      "warm": {
        "min_age": "3d",
        "actions": {
          "migrate": {
            "enabled": true
          },
          "forcemerge": {
            "max_num_segments": 1
          },
          "set_priority": {
            "priority": 50
          }
        }
      },
      "cold": {
        "min_age": "15d",
        "actions": {
          "migrate": {
            "enabled": true
          },
          "set_priority": {
            "priority": 0
          }
        }
      },
      "delete": {
        "min_age": "90d",
        "actions": {
          "delete": {}
        }
      }
    }
  }
}'

Create data stream for testing

Create a data stream to test the ILM policy functionality.

# Create the data stream
curl -X PUT "localhost:9200/_data_stream/logs-application"

Add some test data

curl -X POST "localhost:9200/logs-application/_doc" -H 'Content-Type: application/json' -d' { "@timestamp": "2024-01-15T10:30:00Z", "level": "INFO", "message": "Application started successfully", "host": "web-01" }' curl -X POST "localhost:9200/logs-application/_doc" -H 'Content-Type: application/json' -d' { "@timestamp": "2024-01-15T10:31:00Z", "level": "ERROR", "message": "Database connection failed", "host": "web-02" }'

Configure allocation awareness

Set up shard allocation awareness to ensure proper distribution across node types.

curl -X PUT "localhost:9200/_cluster/settings" -H 'Content-Type: application/json' -d'
{
  "persistent": {
    "cluster.routing.allocation.awareness.attributes": "data_tier,box_type",
    "cluster.routing.allocation.awareness.force.data_tier.values": "hot,warm,cold",
    "cluster.routing.allocation.balance.shard": 0.45,
    "cluster.routing.allocation.balance.index": 0.55
  }
}'

Install Kibana for monitoring

Install Kibana to visualize ILM policy execution and cluster health.

wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo gpg --dearmor -o /usr/share/keyrings/elasticsearch-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/elasticsearch-keyring.gpg] https://artifacts.elastic.co/packages/8.x/apt stable main" | sudo tee /etc/apt/sources.list.d/elastic-8.x.list
sudo apt update
sudo apt install -y kibana
sudo rpm --import https://artifacts.elastic.co/GPG-KEY-elasticsearch
echo '[elasticsearch]
name=Elasticsearch repository for 8.x packages
baseurl=https://artifacts.elastic.co/packages/8.x/yum
gpgcheck=1
gpgkey=https://artifacts.elastic.co/GPG-KEY-elasticsearch
enabled=0
autorefresh=1
type=rpm-md' | sudo tee /etc/yum.repos.d/elasticsearch.repo
sudo dnf install --enablerepo=elasticsearch -y kibana

Configure Kibana for ILM monitoring

Set up Kibana to connect to your Elasticsearch cluster and monitor ILM policies.

# Kibana configuration
server.port: 5601
server.host: "0.0.0.0"
server.name: "kibana-server"

Elasticsearch configuration

elasticsearch.hosts: ["https://localhost:9200"] elasticsearch.username: "kibana_system" elasticsearch.password: "your-kibana-password"

Security settings

elasticsearch.ssl.certificateAuthorities: ["/etc/elasticsearch/certs/http_ca.crt"] server.ssl.enabled: true server.ssl.certificate: "/etc/kibana/certs/kibana.crt" server.ssl.key: "/etc/kibana/certs/kibana.key"

Monitoring settings

monitoring.ui.ccs.enabled: false monitoring.enabled: true

Start and enable Kibana

Enable Kibana to start automatically and launch the service.

sudo systemctl daemon-reload
sudo systemctl enable kibana
sudo systemctl start kibana
sudo systemctl status kibana

Verify your setup

Check that your hot-warm-cold architecture and ILM policies are working correctly.

# Check cluster health and node roles
curl -X GET "localhost:9200/_cluster/health?pretty"
curl -X GET "localhost:9200/_cat/nodes?v&h=name,node.role,heap.percent,disk.used_percent"

Verify ILM policies

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

Check data stream and index allocation

curl -X GET "localhost:9200/_cat/indices?v&h=index,health,pri,rep,store.size,pri.store.size"

View ILM policy status

curl -X GET "localhost:9200/logs-application/_ilm/explain?pretty"

Check shard allocation across tiers

curl -X GET "localhost:9200/_cat/allocation?v"
Kibana Access: Open your browser to https://your-server-ip:5601 to access the Kibana interface. Use the elastic superuser credentials to log in and navigate to Stack Management > Index Lifecycle Policies to monitor your ILM policies.

Monitor ILM performance with Kibana dashboards

Create ILM monitoring dashboard

Set up Kibana visualizations to track ILM policy execution and data tier usage.

# Create index pattern for monitoring
PUT _template/ilm-monitoring
{
  "index_patterns": [".monitoring-*"],
  "settings": {
    "index.lifecycle.name": "logs-policy"
  }
}

Query to check phase transitions

GET _cat/indices?v&h=index,pri,rep,health,status,pri.store.size&s=pri.store.size:desc

Monitor ILM execution status

GET _ilm/status

Check specific index ILM explain

GET logs-application/_ilm/explain

Set up Watcher alerts for ILM issues

Configure automatic alerts when ILM policies fail or indices get stuck.

curl -X PUT "localhost:9200/_watcher/watch/ilm-failure-alert" -H 'Content-Type: application/json' -d'
{
  "trigger": {
    "schedule": {
      "interval": "1h"
    }
  },
  "input": {
    "search": {
      "request": {
        "search_type": "query_then_fetch",
        "indices": [".ds-*"],
        "body": {
          "query": {
            "bool": {
              "must": [
                {
                  "range": {
                    "@timestamp": {
                      "gte": "now-1h"
                    }
                  }
                }
              ],
              "filter": [
                {
                  "term": {
                    "ilm.phase": "ERROR"
                  }
                }
              ]
            }
          }
        }
      }
    }
  },
  "condition": {
    "compare": {
      "ctx.payload.hits.total": {
        "gt": 0
      }
    }
  },
  "actions": {
    "send_email": {
      "email": {
        "profile": "standard",
        "to": ["admin@example.com"],
        "subject": "ILM Policy Failure Alert",
        "body": "ILM policies have failed for {{ctx.payload.hits.total}} indices. Please check the cluster."
      }
    }
  }
}'

Common issues

SymptomCauseFix
Indices stuck in hot phaseInsufficient warm/cold nodesAdd more warm/cold nodes or adjust allocation settings
ILM policy not executingILM disabled or insufficient permissionsRun PUT _ilm/start and check user permissions
Shards not moving to warm tierNode attribute mismatchVerify node.attr.data_tier settings match policy
High disk usage on hot nodesRollover conditions not metAdjust max_primary_shard_size or max_age in policy
Search performance degradedToo many segments in warm/coldEnable forcemerge action in warm phase
Node allocation warningsAwareness attributes not setConfigure cluster.routing.allocation.awareness.attributes
Storage Planning: Cold nodes typically use 70% less expensive storage but have 3-5x slower query performance. Plan your hot-warm-cold ratios based on query patterns, not just age. Active dashboards may need data kept in warm tier longer than expected.

Next steps

Running this in production?

Managing Elasticsearch complexity? Running this at scale adds a second layer of work: capacity planning, failover drills, cost control, and on-call. See how we run infrastructure like this for European teams.

Automated install script

Run this to automate the entire setup

Need help?

Don't want to manage this yourself?

We handle high availability infrastructure for businesses that depend on uptime. From initial setup to ongoing operations.