Creating dedicated Elasticsearch users and roles keeps API clients, ingest components, and human operators off the built-in elastic superuser and makes least-privilege access practical for daily cluster work.

Native users and roles are stored in the .security index and are managed through the _security/user and _security/role APIs. Roles bundle cluster privileges such as monitor with index privileges such as read or view_index_metadata, and a user receives the combined privileges from every assigned role.

Creating or updating either object requires an authenticated account with the manage_security cluster privilege. The built-in elastic user is best treated as a bootstrap account for creating narrower native users, and self-managed clusters that use a private HTTP CA may require curl to trust that CA or use --cacert /path/to/http_ca.crt before the API calls succeed.

Steps to create users and roles in Elasticsearch:

  1. Authenticate as an administrative user to confirm access to the security APIs.
    $ curl --silent --show-error --fail --user elastic "https://localhost:9200/_security/_authenticate?pretty"
    Enter host password for user 'elastic':
    {
      "username" : "elastic",
      "roles" : [
        "superuser"
      ],
      "full_name" : null,
      "email" : null,
      "metadata" : {
        "_reserved" : true
      },
      "enabled" : true,
      "authentication_realm" : {
        "name" : "reserved",
        "type" : "reserved"
      },
      "lookup_realm" : {
        "name" : "reserved",
        "type" : "reserved"
      },
      "authentication_type" : "realm"
    }

    Use any account that already has manage_security, and keep elastic for bootstrap-only administration where possible.

  2. Create a role with only the required cluster and index privileges.
    $ curl --silent --show-error --fail --user elastic -H "Content-Type: application/json" -X PUT "https://localhost:9200/_security/role/logs_reader?pretty" -d '{
      "cluster" : ["monitor"],
      "indices" : [
        {
          "names" : ["logs-*"],
          "privileges" : ["read", "view_index_metadata"]
        }
      ]
    }'
    Enter host password for user 'elastic':
    {
      "role" : {
        "created" : true
      }
    }

    Reuse the same role name with PUT to update the stored privilege set, and keep index patterns and privileges as narrow as the workload allows.

  3. Review the stored role definition before assigning it to a user.
    $ curl --silent --show-error --fail --user elastic "https://localhost:9200/_security/role/logs_reader?pretty"
    Enter host password for user 'elastic':
    {
      "logs_reader" : {
        "cluster" : [
          "monitor"
        ],
        "indices" : [
          {
            "names" : [
              "logs-*"
            ],
            "privileges" : [
              "read",
              "view_index_metadata"
            ],
            "allow_restricted_indices" : false
          }
        ],
        "applications" : [ ],
        "run_as" : [ ],
        "metadata" : { },
        "transient_metadata" : {
          "enabled" : true
        }
      }
    }

    Checking the saved role first makes it easier to catch an over-broad wildcard or a missing cluster privilege before the user starts authenticating with it.

  4. Create a native user and assign the role to it.
    $ curl --silent --show-error --fail --user elastic -H "Content-Type: application/json" -X PUT "https://localhost:9200/_security/user/logs-viewer?pretty" -d '{
      "password" : "ChangeMe-LogsViewer-92!",
      "roles" : ["logs_reader"],
      "full_name" : "Logs Viewer"
    }'
    Enter host password for user 'elastic':
    {
      "created" : true
    }

    Replace the sample password before running the request, and avoid saving reusable credentials in shell history, scripts, or ticket notes.

  5. Authenticate as the new user to confirm the account resolves through the native realm.
    $ curl --silent --show-error --fail --user logs-viewer "https://localhost:9200/_security/_authenticate?pretty"
    Enter host password for user 'logs-viewer':
    {
      "username" : "logs-viewer",
      "roles" : [
        "logs_reader"
      ],
      "full_name" : "Logs Viewer",
      "email" : null,
      "metadata" : { },
      "enabled" : true,
      "authentication_realm" : {
        "name" : "default_native",
        "type" : "native"
      },
      "lookup_realm" : {
        "name" : "default_native",
        "type" : "native"
      },
      "authentication_type" : "realm"
    }

    The default_native realm in the response confirms that the account is being served from the native realm rather than from the reserved built-in user set.

  6. Confirm the effective privileges for the new user.
    $ curl --silent --show-error --fail --user logs-viewer "https://localhost:9200/_security/user/_privileges?pretty"
    Enter host password for user 'logs-viewer':
    {
      "cluster" : [
        "monitor"
      ],
      "global" : [ ],
      "indices" : [
        {
          "names" : [
            "logs-*"
          ],
          "privileges" : [
            "read",
            "view_index_metadata"
          ],
          "allow_restricted_indices" : false
        }
      ],
      "applications" : [ ],
      "run_as" : [ ]
    }

    This API only reports the privileges of the currently authenticated user, which makes it a direct least-privilege check after the role assignment.