Securing a cluster

This section documents the current Elasticsearch 9.x line (with 8.19 as the final 8.x release) as published at the Elasticsearch documentation, which is the reference these pages are written and verified against. No specific patch version is pinned. Some capabilities (Kibana-only UIs, the ML/NLP model-management workflow, cross-cluster replication, and parts of the paid / serverless-only surface) are linked, not documented in depth.

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production, as Elasticsearch iterates quickly.

This section’s bibliography lists the reference material consulted while preparing these pages.

Elasticsearch security has three layers: TLS on every connection, an authentication step that resolves a request to a user, and a role-based authorization step that decides what that user may do. Security is enabled by default — a fresh node configures TLS and creates its own certificates on first start. This page covers that first-start flow, the authentication realms, and how roles, users, and role mappings grant access down to the field and document level. See Secure the Elastic Stack for the full checklist.

Transport & HTTP TLS and the first-start enrollment flow

Security on by default

Starting a node from a stock distribution auto-configures the xpack.security. settings: TLS is required on the *transport layer (node-to-node, port 9300) and on the HTTP layer (client REST, port 9200), a CA and node certificates are generated into config/certs/, and the built-in users are created. The node prints the elastic user’s password and a Kibana enrollment token once to the terminal on that first start. See Start the Elastic Stack with security enabled.

# config/elasticsearch.yml as written by auto-configuration (abridged).
xpack.security.enabled: true
xpack.security.transport.ssl:
  enabled: true
  verification_mode: certificate
  keystore.path: certs/transport.p12
  truststore.path: certs/transport.p12
xpack.security.http.ssl:
  enabled: true
  keystore.path: certs/http.p12
# https://www.elastic.co/guide/en/elasticsearch/reference/current/security-basic-setup.html

The transport layer uses verification_mode: certificate so any node presenting a cert from the cluster CA can join; set it to full to also check hostnames. To secure a cluster that was started with security off, run bin/elasticsearch-certutil to build a CA and certs and set the same keys by hand — see Set up basic security and …​with HTTPS.

Enrollment tokens and password reset

A new node joins an existing secured cluster with an enrollment token: a base64 blob carrying the CA fingerprint and a one-time key. Generate it on a running node and pass it to the joining node (or to Kibana), which then pulls the CA and gets its own certificates automatically.

# On a running node: mint a token for another Elasticsearch node, or for Kibana.
bin/elasticsearch-create-enrollment-token -s node
bin/elasticsearch-create-enrollment-token -s kibana

# On the joining node, first start only:
bin/elasticsearch --enrollment-token <PASTE_NODE_TOKEN_HERE>

# Lost the elastic password, or need a bootstrap password for kibana_system:
bin/elasticsearch-reset-password -u elastic
bin/elasticsearch-reset-password -u kibana_system --auto   # print a random one
# https://www.elastic.co/guide/en/elasticsearch/reference/current/reset-password.html
flowchart TD A[First start of node 1] --> B[Auto-config: generate CA + transport/http certs, enable TLS] B --> C[Print elastic password and Kibana enrollment token once] C --> D[Start Kibana with the enrollment token: it fetches the CA over HTTPS] D --> E[bin/elasticsearch-create-enrollment-token -s node] E --> F[Start node 2 with --enrollment-token: it joins over transport TLS]

Built-in users

The reserved users live in a special index and back the stack’s own components. Their privileges are fixed; you only set their passwords.

User Purpose

elastic

Superuser for bootstrap. Set a strong password and stop using it for day-to-day work.

kibana_system

The account Kibana uses to talk to Elasticsearch (not for logging in to Kibana).

logstash_system

Logstash monitoring and the Elasticsearch output’s management calls.

beats_system, apm_system

Monitoring data from Beats and APM Server.

remote_monitoring_user

Used by Metricbeat to collect monitoring data from the cluster.

# Set kibana_system's password non-interactively via the change-password API.
curl -sk -u elastic:<ELASTIC_PASSWORD> -X POST \
  "https://localhost:9200/_security/user/kibana_system/_password" \
  -H 'Content-Type: application/json' \
  -d '{ "password": "<GENERATED_PASSWORD>" }'
# https://www.elastic.co/guide/en/elasticsearch/reference/current/built-in-users.html

Authentication realms

A realm authenticates a credential and produces a user with a set of group/role names. Realms are chained by an order value and consulted in turn until one authenticates the request. The native and file realms are enabled implicitly; the rest are added under xpack.security.authc.realms.<type>.<name> in elasticsearch.yml. See Set up authentication and Realms.

native and file

The native realm stores users and their hashed passwords in Elasticsearch itself and is managed entirely through the _security API — this is the default for human users without an external directory. The file realm reads config/users and config/users_roles on each node; it is meant for a handful of break-glass accounts that must work even if the cluster is unhealthy.

# native realm: create a user (see the Authorization section for the role).
POST /_security/user/ada
{
  "password": "<GENERATED_PASSWORD>",
  "roles": [ "app_reader" ],
  "full_name": "Ada Lovelace",
  "email": "ada@example.com"
}
# file realm: add a break-glass admin on one node.
bin/elasticsearch-users useradd breakglass -p <GENERATED_PASSWORD> -r superuser
# https://www.elastic.co/guide/en/elasticsearch/reference/current/file-realm.html

LDAP and Active Directory

The ldap realm authenticates a bind and then runs a group search; the active_directory realm is the same with AD’s defaults baked in. Groups come back as distinguished names that a role mapping (below) turns into roles. Store the bind password in the keystore, not the YAML.

xpack.security.authc.realms.ldap.ldap1:
  order: 2
  url: "ldaps://ldap.example.com:636"
  bind_dn: "cn=es,ou=services,dc=example,dc=com"
  user_search.base_dn: "ou=people,dc=example,dc=com"
  group_search.base_dn: "ou=groups,dc=example,dc=com"
  ssl.certificate_authorities: [ "certs/ldap-ca.crt" ]
# bin/elasticsearch-keystore add xpack.security.authc.realms.ldap.ldap1.secure_bind_password
# https://www.elastic.co/guide/en/elasticsearch/reference/current/ldap-realm.html

SAML and OIDC

For browser SSO through Kibana, use the saml or oidc realm: Elasticsearch is the service provider / relying party and the identity provider asserts the user and their groups. Both realms are driven by dedicated _security APIs that Kibana calls during the login handshake.

xpack.security.authc.realms.saml.saml1:
  order: 3
  idp.metadata.path: "saml/idp-metadata.xml"
  idp.entity_id: "https://idp.example.com/"
  sp.entity_id: "https://kibana.example.com/"
  sp.acs: "https://kibana.example.com/api/security/saml/callback"
  attributes.principal: "nameid"
  attributes.groups: "http://schemas.xmlsoap.org/claims/Group"
# https://www.elastic.co/guide/en/elasticsearch/reference/current/saml-guide-stack.html

PKI

The pki realm authenticates a client X.509 certificate presented on the TLS handshake — no password. The certificate’s DN is the principal; a role mapping grants it roles. HTTP client-cert auth must be turned on for the realm to see the certificate.

xpack.security.authc.realms.pki.pki1:
  order: 1
xpack.security.http.ssl.client_authentication: optional
# https://www.elastic.co/guide/en/elasticsearch/reference/current/pki-realm.html

Kerberos

The kerberos realm authenticates a SPNEGO/GSSAPI ticket against a keytab, for environments that already run an MIT Kerberos or AD KDC. See Kerberos realm.

xpack.security.authc.realms.kerberos.kerb1:
  order: 4
  keytab.path: "es.keytab"

API keys — the service-to-service default

For application and service accounts, do not hand out user passwords — issue an API key. A key is owned by the user who created it, can never exceed that user’s own privileges, may carry its own scoped role_descriptors, and can be given an expiration. Clients send it in the Authorization: ApiKey <base64> header.

POST /_security/api_key
{
  "name": "billing-service",
  "expiration": "90d",
  "role_descriptors": {
    "billing-writer": {
      "cluster": [ "monitor" ],
      "indices": [
        { "names": [ "billing-*" ], "privileges": [ "create_doc", "auto_configure" ] }
      ]
    }
  },
  "metadata": { "team": "payments", "env": "prod" }
}
// Response returns "id" and "api_key" once; the client uses base64(id:api_key).
// https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-create-api-key.html
GET  /_security/_authenticate                        // who am I, which realm
GET  /_security/api_key?owner=true                   // list my keys
DELETE /_security/api_key
{ "ids": [ "<API_KEY_ID>" ] }                         // invalidate

Manage and invalidate keys with the related APIs at Get API key information and Invalidate API key.

Authorization: role-based access control

Every authenticated request is checked against the union of the user’s roles. A role grants cluster privileges (node-wide actions) and index privileges (per-index-pattern actions), and may allow run_as impersonation. Unknown actions are denied. See Authorization.

Roles: cluster and index privileges, run_as

PUT /_security/role/app_reader
{
  "cluster": [ "monitor" ],
  "indices": [
    {
      "names": [ "app-*" ],
      "privileges": [ "read", "view_index_metadata" ]
    }
  ],
  "run_as": [ "report_service" ],
  "metadata": { "managed_by": "platform-team" }
}
// Named privileges (read, write, create_doc, index, delete, manage, all, ...) and
// cluster privileges (monitor, manage, manage_security, ...) are listed at
// https://www.elastic.co/guide/en/elasticsearch/reference/current/security-privileges.html

run_as lets a trusted middle-tier authenticate once and then submit each request as the end user by adding an es-security-runas-user: <username> header, so document-level rules apply per person. Built-in roles such as superuser, kibana_admin, and viewer cover common cases — see Built-in roles.

Users and role mappings

Native-realm users get their roles from the roles array on the user document (see the Authentication section). Users from an external realm (LDAP, SAML, OIDC, PKI, Kerberos) have no such document — a role mapping assigns roles by matching realm name, username, groups, or metadata.

PUT /_security/role_mapping/sso_app_readers
{
  "roles": [ "app_reader" ],
  "enabled": true,
  "rules": {
    "all": [
      { "field": { "realm.name": "saml1" } },
      { "field": { "groups": "cn=app-readers,ou=groups,dc=example,dc=com" } }
    ]
  }
}
// https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-put-role-mapping.html

Field- and document-level security

An index privilege can restrict which fields a role can see (field_security.grant / except) and which documents it can see (query, a Query DSL filter evaluated per request). Together they let one physical index serve many tenants. FLS and DLS require a platform-license tier. See Field and document level security.

PUT /_security/role/acme_support
{
  "indices": [
    {
      "names": [ "tickets" ],
      "privileges": [ "read" ],
      "field_security": {
        "grant":  [ "subject", "body", "status", "@timestamp", "tenant" ],
        "except": [ "body.internal_notes" ]
      },
      "query": { "term": { "tenant": "acme" } }
    }
  ]
}
// A user with this role searching "tickets" only ever sees acme documents,
// and never the body.internal_notes field.

DLS and run_as compose: a middle tier with a per-user query on owner shows each impersonated user only their own rows. Note that a DLS query cannot use has_child / has_parent on the join field.

The audit log

Auditing records authentication and authorization events to a separate JSON log (<cluster>_audit.json) and is enabled per node in elasticsearch.yml. Filter down to the events that matter to keep volume sane.

xpack.security.audit.enabled: true
xpack.security.audit.logfile.events.include: [ access_denied, authentication_failed, run_as_denied, tampered_request ]
xpack.security.audit.logfile.events.emit_request_body: false
# https://www.elastic.co/guide/en/elasticsearch/reference/current/enable-audit-logging.html

See Audit events for the full event list and the structured fields each one carries.

Cross-cluster security

For cross-cluster search and cross-cluster replication over the API-key-based remote-cluster model, issue a dedicated cross-cluster API key on the remote cluster and configure it on the local one — see Create cross-cluster API key.