How to create an Elasticsearch snapshot lifecycle policy

A snapshot lifecycle policy turns Elasticsearch backups into a repeatable operation, which matters before upgrades, index cleanup, and any other change that might need a fast restore path. Scheduled snapshots reduce the gap between recovery points and remove the risk of forgetting a manual backup before maintenance starts.

Snapshot lifecycle management (SLM) stores the policy in cluster state, creates snapshots from a registered repository on a schedule, and records execution metadata such as the last successful run and the next scheduled execution. The policy controls the repository, snapshot name pattern, included indices, and retention rules, while manual execute calls reuse the same policy definition for an immediate backup test.

The snapshot repository must already be registered and reachable by every node before the policy can succeed, and SLM itself must be running or scheduled snapshots will never fire. Retention cleanup is also separate from snapshot creation: expired snapshots are deleted by the cluster-level slm.retention_schedule task, so old snapshots can remain in the repository until that retention job runs. When cluster security is enabled, replace the local HTTP examples with HTTPS plus authentication and a trusted CA file.

Steps to create an Elasticsearch snapshot lifecycle policy:

  1. Confirm the target snapshot repository is already registered.
    $ curl -sS --fail "http://localhost:9200/_snapshot/local_fs?filter_path=*.type,*.settings.location&pretty"
    {
      "local_fs" : {
        "type" : "fs",
        "settings" : {
          "location" : "/usr/share/elasticsearch/snapshots"
        }
      }
    }

    The policy can only write to registered repositories. If local_fs is missing, create or verify the repository before continuing.

  2. Check that SLM is running before assigning a schedule.
    $ curl -sS --fail "http://localhost:9200/_slm/status?pretty"
    {
      "operation_mode" : "RUNNING"
    }

    If operation_mode returns STOPPED, start it with curl -sS --fail -X POST "http://localhost:9200/_slm/start?pretty" and re-run the status check.

  3. Create the snapshot lifecycle policy.
    $ curl -sS --fail -H "Content-Type: application/json" -X PUT "http://localhost:9200/_slm/policy/daily-snapshots?pretty" -d '{
      "schedule": "0 30 1 * * ?",
      "name": "<daily-snap-{now/d}>",
      "repository": "local_fs",
      "config": {
        "indices": ["logs-*"],
        "include_global_state": false
      },
      "retention": {
        "expire_after": "30d",
        "min_count": 7,
        "max_count": 90
      }
    }'
    {
      "acknowledged" : true
    }

    schedule accepts Quartz cron syntax or interval values such as 1h. The snapshot name supports date math, and Elasticsearch appends a unique suffix automatically so each run gets a distinct snapshot name.

  4. Run the policy once to verify it can create a snapshot immediately.
    $ curl -sS --fail -X POST "http://localhost:9200/_slm/policy/daily-snapshots/_execute?pretty"
    {
      "snapshot_name" : "daily-snap-2026.04.02-mmfhjvyts32baldzadffqq"
    }

    A manual execute call starts the snapshot in the background and does not change the configured schedule for future runs.

  5. Check the repository until the new snapshot reaches SUCCESS state.
    $ curl -sS --fail "http://localhost:9200/_cat/snapshots/local_fs?v&h=id,status,indices,successful_shards,failed_shards,total_shards&s=id"
    id                                            status indices successful_shards failed_shards total_shards
    daily-snap-2026.04.02-mmfhjvyts32baldzadffqq SUCCESS       1                 1             0            1

    If the snapshot still shows IN_PROGRESS or STARTED, wait a few seconds and run the same check again until the state changes.

  6. Inspect the policy metadata for the last successful run and the next scheduled execution.
    $ curl -sS --fail "http://localhost:9200/_slm/policy/daily-snapshots?human&filter_path=*.policy.schedule,*.policy.repository,*.last_success.snapshot_name,*.last_success.time_string,*.next_execution,*.stats.snapshots_taken,*.stats.snapshots_failed&pretty"
    {
      "daily-snapshots" : {
        "policy" : {
          "schedule" : "0 30 1 * * ?",
          "repository" : "local_fs"
        },
        "last_success" : {
          "snapshot_name" : "daily-snap-2026.04.02-mmfhjvyts32baldzadffqq",
          "time_string" : "2026-04-02T08:29:32.059Z"
        },
        "next_execution" : "2026-04-03T01:30:00.000Z",
        "stats" : {
          "snapshots_taken" : 1,
          "snapshots_failed" : 0
        }
      }
    }

    snapshots_taken counts manual execute runs too, while next_execution stays aligned with the configured schedule rather than the ad hoc test run.