Migrating from Curator to ILM Policies
Retire a running elasticsearch-curator rotation and hand the same live indices to an ILM policy so that neither tool ever deletes the same data twice and no index is left unmanaged in between.
The risk in this migration is not the policy authoring — that part is mechanical. It is the cutover ordering. Curator runs from cron with no knowledge of ILM, and ILM has no knowledge of the cron; for a brief window both could hold a claim on the same syslog-* indices. Do it in the wrong order and either nothing rotates (both disabled) or both rotate (both live), and overlapping delete steps race. This procedure sequences the four operations — create the policy, attach a template, adopt existing indices, disable the cron — so there is exactly one controller acting on every index at every instant. It is the detailed runbook behind the higher-level comparison in ILM rollover vs Curator-based rotation, and it plugs into the broader Index Lifecycle Management (ILM) control plane.
Prerequisites
- Elasticsearch 8.x with data-tier roles assigned, and
elasticsearch-pyv8.0+ for the automation step. - The existing Curator
action_file.ymland its cron/timer entry, plus shell access to the host that runs it. manage_ilmandmanage_index_templatescluster privileges, andmanageon thesyslog-*pattern.- A staging deployment that mirrors the production index naming, so the cutover is rehearsed before it touches live retention.
Implementation
The safe cutover is a strict sequence. Each step narrows who controls the indices until only ILM remains, and the Curator cron is not disabled until ILM demonstrably owns every index.
1. Author the ILM policy equivalent to the Curator actions
Translate each Curator action into a phase. Rollover conditions move into the hot phase unchanged; a forcemerge filtered by age becomes a warm-phase action with a min_age; the delete_indices age filter becomes the delete phase. Reproduce the retention intent, not the literal day count, because ILM measures phase age from rollover rather than creation.
PUT _ilm/policy/syslog-ilm
{
"policy": {
"phases": {
"hot": {
"min_age": "0ms",
"actions": {
"rollover": { "max_age": "1d", "max_primary_shard_size": "50gb" },
"set_priority": { "priority": 100 }
}
},
"warm": {
"min_age": "2d",
"actions": { "forcemerge": { "max_num_segments": 1 } }
},
"delete": {
"min_age": "90d",
"actions": { "delete": {} }
}
}
}
}Creating the policy is inert: it manages nothing until an index references it, so this step is completely safe while the Curator cron is still live.
2. Attach the policy via a composable template
Bind the policy so every new backing index is managed from creation. The template also names the write alias so ILM knows which alias to advance on rollover. Building this template pair in full is covered in bootstrapping index templates and data streams.
PUT _index_template/syslog-template
{
"index_patterns": ["syslog-*"],
"template": {
"settings": {
"index.lifecycle.name": "syslog-ilm",
"index.lifecycle.rollover_alias": "syslog-write"
}
}
}A template only affects indices created after it is applied. Every syslog-* index that already exists is untouched by this step — which is exactly why step 3 exists.
3. Attach the policy to existing indices
Bring the already-created indices under management explicitly. For the current write index, set both the policy and the rollover alias; for older backing indices that only need to progress through warm and delete, the policy name alone is enough.
PUT syslog-000006/_settings
{
"index.lifecycle.name": "syslog-ilm"
}At this point every index is managed by ILM, but the Curator cron is still running — both systems can act. That overlap is tolerable only because you have not yet reached a delete boundary under ILM; you must close it in step 4 before ILM’s delete phase can fire.
4. Disable the Curator cron
With every index confirmed managed, remove Curator from the loop so ILM is the sole controller. Disable the schedule, do not merely stop a running invocation:
# Remove the syslog rotation entry, or disable the timer unit.
crontab -e # delete the curator line
systemctl disable --now curator-syslog.timerOnly now is the migration complete. ILM owns rotation end to end, and no cron will fire a second delete against an index ILM is relocating or has already removed.
5. Automate steps 1 and 3 with the Python v8 client
For a repeatable, auditable cutover, drive policy creation and existing-index adoption through the v8 client in one idempotent pass. This applies the policy, then attaches it to every existing index that is not yet managed:
import logging
from elasticsearch import Elasticsearch, ApiError
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)
def migrate_to_ilm(es: Elasticsearch, policy_name: str, pattern: str, policy: dict) -> None:
# 1. Apply the policy — idempotent, and inert until an index references it.
es.ilm.put_lifecycle(name=policy_name, policy=policy)
logger.info("Policy '%s' applied.", policy_name)
# 2. Adopt existing indices that are not yet managed by any policy.
explained = es.ilm.explain_lifecycle(index=pattern)
for index, meta in explained["indices"].items():
if meta.get("managed"):
logger.info("Skipping %s — already managed by %s.", index, meta.get("policy"))
continue
# Attach the policy in place; the template only covers indices created later.
es.indices.put_settings(
index=index,
settings={"index.lifecycle.name": policy_name},
)
logger.info("Attached '%s' to %s.", policy_name, index)
if __name__ == "__main__":
es = Elasticsearch(
"https://es-cluster-01:9200",
api_key="YOUR_BASE64_ENCODED_API_KEY",
verify_certs=True,
)
syslog_policy = {
"phases": {
"hot": {"min_age": "0ms", "actions": {
"rollover": {"max_age": "1d", "max_primary_shard_size": "50gb"}}},
"warm": {"min_age": "2d", "actions": {"forcemerge": {"max_num_segments": 1}}},
"delete": {"min_age": "90d", "actions": {"delete": {}}},
}
}
try:
migrate_to_ilm(es, "syslog-ilm", "syslog-*", syslog_policy)
except ApiError as exc:
logger.error("Migration step failed (%s): %s", exc.status_code, exc.info)
raiseRun this script before disabling the cron. It is safe to re-run: put_lifecycle replaces the policy in place, and indices already managed are skipped rather than re-attached.
Verification
Confirm ILM owns every index before you trust the cron is safe to remove. First, check that no syslog-* index is left unmanaged:
GET syslog-*/_ilm/explain?filter_path=indices.*.managed,indices.*.policy{
"indices": {
"syslog-000006": { "managed": true, "policy": "syslog-ilm" },
"syslog-000007": { "managed": true, "policy": "syslog-ilm" }
}
}Every entry must show "managed": true with "policy": "syslog-ilm". A single "managed": false means an index would be orphaned the moment the cron stops. Second, confirm the Curator schedule is actually gone from the host — a stopped invocation is not a disabled schedule:
crontab -l | grep -i curator # expect no output
systemctl list-timers | grep -i curator # expect no outputBoth commands returning nothing confirms ILM is the sole controller and the cutover is complete.
Gotchas and edge cases
- Stop Curator before ILM can delete the same indices — order is everything. The dangerous window is when both hold a delete claim. Disable the cron only after adoption is verified, but before any index reaches its ILM delete
min_age. If you disable the cron too early, rotation stops entirely until ILM adopts the indices; too late, and overlapping deletes race and can drop an index mid-relocation. - Existing indices need an explicit attach — the template is not retroactive. A composable template only manages indices created after it is applied. Every backing index that predates the template stays unmanaged until you
PUT <index>/_settingswith the policy name, which is what step 3 and the Python loop handle. min_ageis measured from rollover, not creation. Curator age filters typically count fromcreation_date; ILM counts phase age from the rollover event. An index that filled slowly before rolling will hit its ILM delete boundary later than the old Curator rule would have. Do not assume “older than 90 days” carries over numerically — verify the effective retention in_ilm/explain.- Rehearse in staging first. A mistimed cutover on a live pipeline can either stop rotation (disks fill) or double-delete (retention breach). Run the full sequence against a staging deployment with the same index naming, watch one rollover and one phase transition happen under ILM, then repeat in production.
FAQ
What if I disable the Curator cron before ILM adopts the old indices?
Then those pre-existing indices are managed by nothing: Curator has stopped and ILM never adopted them, so they will not force-merge, migrate, or delete. They sit and accumulate until a disk watermark blocks writes. Always verify "managed": true on every syslog-* index before removing the cron, and run the adoption step for any that report false.
Do I need to re-index or restart anything to attach the policy?
No. Attaching a policy to an existing index is a live settings update via PUT <index>/_settings — no re-index, no restart, no downtime. ILM picks the index up at its next poll and begins evaluating it from its current age. The data and its shards are untouched; only the index metadata gains the index.lifecycle.name setting.
Can I keep Curator running for cleanup tasks ILM does not handle?
Yes, provided it acts only on indices ILM does not manage. Narrow the Curator delete filter to a pattern that cannot overlap syslog-* (for example, unmanaged legacy names), so the two never contend for the same index. Using Curator for genuine one-off cleanup of unmanaged indices is fine; the migration only removes it from the recurring rotation of managed data.
Related
- ILM Rollover vs Curator-Based Rotation — the capability-by-capability comparison this runbook implements.
- Bootstrapping Index Templates and Data Streams — building the composable template and write alias that attach the policy to new indices.
- Index Lifecycle Management (ILM) — the control plane that manages the indices once Curator is retired.
← Back to ILM Rollover vs Curator · ILM Architecture & Fundamentals