Applying an Elasticsearch index template to Logstash indices keeps new indices predictable before the first event lands. A template pins field mappings and shard settings up front, which helps prevent dynamic mapping drift, unexpected text-versus-date conflicts, and inconsistent shard layouts across daily Logstash indices.
Current Elasticsearch releases use composable index templates through /_index_template. The template matches new indices by index_patterns, resolves conflicts with priority, and applies the resulting settings and mappings when the index is created. On the Logstash side, the elasticsearch output decides the final index name, so the template pattern must match the exact value produced by index ⇒.
This workflow targets classic indices rather than data streams. Templates affect only new indices, not existing ones, and current Logstash output behavior matters: leaving manage_template enabled lets the plugin install its own default logstash-* template, while leaving ILM enabled can replace a custom index ⇒ name with a rollover alias. Use an application-specific pattern such as logstash-app-*, set manage_template to false, and disable ILM in this workflow unless the pipeline is intentionally using the separate ILM pattern. On secured clusters, switch the examples to https and add credentials or an API key.
$ curl --silent --show-error --fail \
--header "Content-Type: application/json" \
--request PUT "http://elasticsearch.example.net:9200/_index_template/logstash-app?pretty" \
--data '{
"index_patterns": ["logstash-app-*"],
"priority": 300,
"template": {
"settings": {
"number_of_shards": 1,
"number_of_replicas": 0
},
"mappings": {
"properties": {
"@timestamp": { "type": "date" },
"message": { "type": "text" }
}
}
}
}'
{
"acknowledged" : true
}
Use an application-specific pattern instead of a broad prefix such as logs-* unless you deliberately intend to overlap Elastic built-in or Fleet-managed templates. Creating or updating index templates also requires an Elasticsearch cluster privilege such as manage_index_templates.
A single-node validation cluster commonly uses number_of_replicas set to 0 so the new index can become green immediately. Raise the replica count on multi-node clusters.
output {
elasticsearch {
hosts => ["http://elasticsearch.example.net:9200"]
index => "logstash-app-%{+yyyy.MM.dd}"
manage_template => false
ilm_enabled => false
}
}
manage_template => false keeps the plugin from installing its default logstash-* template. ilm_enabled => false keeps the explicit daily index ⇒ pattern in effect instead of letting an ILM rollover alias take precedence.
On secured clusters, switch the host to https and add the current TLS and authentication settings such as ssl_enabled, ssl_certificate_authorities, user with password, or api_key.
$ sudo -u logstash /usr/share/logstash/bin/logstash --path.settings /etc/logstash --path.data /tmp/logstash-template-configtest --config.test_and_exit Using bundled JDK: /usr/share/logstash/jdk Configuration OK [2026-04-07T14:21:08,214][INFO ][logstash.runner ] Using config.test_and_exit mode. Config Validation Result: OK. Exiting Logstash
The temporary --path.data directory must be writable by the logstash user. This check validates pipeline syntax and plugin options only; it does not prove Elasticsearch connectivity or privileges.
Current Logstash 9.x packages block superuser runs by default, so test the pipeline as the logstash service account unless allow_superuser is intentionally enabled.
$ sudo systemctl restart logstash
Restarting Logstash briefly pauses ingestion while the service stops and starts the pipeline workers again.
$ sudo journalctl --unit logstash --since "5 minutes ago" --no-pager
Apr 07 14:24:11 host logstash[21457]: [2026-04-07T14:24:11,351][INFO ][logstash.outputs.elasticsearch][main] Elasticsearch pool URLs updated {:changes=>{:removed=>[], :added=>[http://elasticsearch.example.net:9200/]}}
Apr 07 14:24:11 host logstash[21457]: [2026-04-07T14:24:11,884][INFO ][logstash.javapipeline ][main] Pipeline started {"pipeline.id"=>"main"}
##### snipped #####
400 mapping errors, template conflicts, and 403 privilege failures usually show up here before they are obvious in the index listing.
$ curl --silent --show-error --fail \
"http://elasticsearch.example.net:9200/_index_template/logstash-app?pretty&filter_path=index_templates.name,index_templates.index_template.index_patterns,index_templates.index_template.priority,index_templates.index_template.template.settings.index.number_of_shards,index_templates.index_template.template.settings.index.number_of_replicas,index_templates.index_template.template.mappings.properties"
{
"index_templates" : [
{
"name" : "logstash-app",
"index_template" : {
"index_patterns" : [
"logstash-app-*"
],
"template" : {
"settings" : {
"index" : {
"number_of_shards" : "1",
"number_of_replicas" : "0"
}
},
"mappings" : {
"properties" : {
"@timestamp" : {
"type" : "date"
},
"message" : {
"type" : "text"
}
}
}
},
"priority" : 300
}
}
]
}
This confirms the stored name, matching pattern, and the exact settings and mappings that Elasticsearch will try to apply to future matching indices.
$ curl --silent --show-error --fail \
--request POST "http://elasticsearch.example.net:9200/_index_template/_simulate_index/logstash-app-2026.04.07?pretty&filter_path=template.settings.index.number_of_shards,template.settings.index.number_of_replicas,template.mappings.properties,overlapping"
{
"template" : {
"settings" : {
"index" : {
"number_of_shards" : "1",
"number_of_replicas" : "0"
}
},
"mappings" : {
"properties" : {
"@timestamp" : {
"type" : "date"
},
"message" : {
"type" : "text"
}
}
}
},
"overlapping" : [ ]
}
Simulation shows the resolved result without waiting for a new index to be created. A non-empty overlapping array means another matching template is still in play.
$ curl --silent --show-error --fail \ "http://elasticsearch.example.net:9200/_cat/indices/logstash-app-*?h=health,status,index,docs.count,store.size&v&allow_no_indices=true" health status index docs.count store.size green open logstash-app-2026.04.07 1 4.8kb
If no matching index exists yet, send a fresh event through the pipeline and run the command again. A yellow index on a single-node cluster usually means the template requested replicas that cannot be assigned yet.
$ curl --silent --show-error --fail \
"http://elasticsearch.example.net:9200/logstash-app-2026.04.07/_settings?pretty&filter_path=*.settings.index.number_of_shards,*.settings.index.number_of_replicas"
{
"logstash-app-2026.04.07" : {
"settings" : {
"index" : {
"number_of_shards" : "1",
"number_of_replicas" : "0"
}
}
}
}
Replace the date-based index name with the current index returned by the previous step when Logstash is rotating indices daily.
$ curl --silent --show-error --fail \
"http://elasticsearch.example.net:9200/logstash-app-2026.04.07/_mapping?pretty&filter_path=*.mappings.properties.@timestamp,*.mappings.properties.message"
{
"logstash-app-2026.04.07" : {
"mappings" : {
"properties" : {
"@timestamp" : {
"type" : "date"
},
"message" : {
"type" : "text"
}
}
}
}
}
If the mapping does not match the template, the index was usually created before the template existed or a higher-priority overlapping template won the match.