Running securityadmin.sh has no effect

Versions (relevant - OpenSearch/Dashboard/Server OS/Browser):

Opensearch/OpensearchDashboards v3

Describe the issue:

Starting Opensearch via docker compose. Cluster started but I see error message:

opensearch-node2       | [2026-01-06T20:07:05,302][ERROR][o.o.s.a.BackendRegistry  ] [opensearch-node2] OpenSearch Security not initialized. (you may need to run securityadmin)
opensearch-dashboards  | {"type":"log","@timestamp":"2026-01-06T20:07:05Z","tags":["error","opensearch","data"],"pid":1,"message":"[ResponseError]: Response Error"}
opensearch-node3       | [2026-01-06T20:07:07,801][ERROR][o.o.s.a.BackendRegistry  ] [opensearch-node3] OpenSearch Security not initialized. (you may need to run securityadmin)
opensearch-node3       | [2026-01-06T20:07:07,801][ERROR][o.o.s.a.BackendRegistry  ] [opensearch-node3] OpenSearch Security not initialized. (you may need to run securityadmin)
opensearch-node1       | [2026-01-06T20:07:07,803][ERROR][o.o.s.a.BackendRegistry  ] [opensearch-node1] OpenSearch Security not initialized. (you may need to run securityadmin)
opensearch-node1       | [2026-01-06T20:07:07,803][ERROR][o.o.s.a.BackendRegistry  ] [opensearch-node1] OpenSearch Security not initialized. (you may need to run securityadmin)
opensearch-node2       | [2026-01-06T20:07:07,804][ERROR][o.o.s.a.BackendRegistry  ] [opensearch-node2] OpenSearch Security not initialized. (you may need to run securityadmin)
opensearch-node2       | [2026-01-06T20:07:07,804][ERROR][o.o.s.a.BackendRegistry  ] [opensearch-node2] OpenSearch Security not initialized. (you may need to run securityadmin)
opensearch-node3       | [2026-01-06T20:07:07,805][ERROR][o.o.s.a.BackendRegistry  ] [opensearch-node3] OpenSearch Security not initialized. (you may need to run securityadmin)
opensearch-node3       | [2026-01-06T20:07:07,805][ERROR][o.o.s.a.BackendRegistry  ] [opensearch-node3] OpenSearch Security not initialized. (you may need to run securityadmin)
...

If I’m running securityadmin.sh like this:

docker compose exec opensearch-node1 bash -c "chmod +x plugins/opensearch-security/tools/securityadmin.sh && bash plugins/opensearch-security/tools/securityadmin.sh -cd config/opensearch-security -icl -nhnv -cacert config/certificates/ca/ca.pem -cert config/certificates/ca/admin.pem -key config/certificates/ca/admin.key -h localhost"

I’m getting result:

Security Admin v7
Will connect to localhost:9200 ... done
Connected as "CN=ADMIN,O=**,L=**,ST=**,C=**"
OpenSearch Version: 3.4.0
Contacting opensearch cluster 'opensearch' and wait for YELLOW clusterstate ...
Clustername: opensearch-cluster
Clusterstate: GREEN
Number of nodes: 3
Number of data nodes: 3
.opendistro_security index already exists, so we do not need to create one.
Populate config from /usr/share/opensearch/config/opensearch-security
Will update '/config' with config/opensearch-security/config.yml
   SUCC: Configuration for 'config' created or updated
Will update '/roles' with config/opensearch-security/roles.yml
   SUCC: Configuration for 'roles' created or updated
Will update '/rolesmapping' with config/opensearch-security/roles_mapping.yml
   SUCC: Configuration for 'rolesmapping' created or updated
Will update '/internalusers' with config/opensearch-security/internal_users.yml
   SUCC: Configuration for 'internalusers' created or updated
Will update '/actiongroups' with config/opensearch-security/action_groups.yml
   SUCC: Configuration for 'actiongroups' created or updated
Will update '/tenants' with config/opensearch-security/tenants.yml
   SUCC: Configuration for 'tenants' created or updated
Will update '/nodesdn' with config/opensearch-security/nodes_dn.yml
   SUCC: Configuration for 'nodesdn' created or updated
Will update '/audit' with config/opensearch-security/audit.yml
   SUCC: Configuration for 'audit' created or updated
Will update '/allowlist' with config/opensearch-security/allowlist.yml
   SUCC: Configuration for 'allowlist' created or updated
SUCC: Expected 9 config types for node {"updated_config_types":["allowlist","tenants","rolesmapping","nodesdn","audit","roles","actiongroups","config","internalusers"],"updated_config_size":9,"message":null} is 9 (["allowlist","tenants","rolesmapping","nodesdn","audit","roles","actiongroups","config","internalusers"]) due to: null
SUCC: Expected 9 config types for node {"updated_config_types":["allowlist","tenants","rolesmapping","nodesdn","audit","roles","actiongroups","config","internalusers"],"updated_config_size":9,"message":null} is 9 (["allowlist","tenants","rolesmapping","nodesdn","audit","roles","actiongroups","config","internalusers"]) due to: null
SUCC: Expected 9 config types for node {"updated_config_types":["allowlist","tenants","rolesmapping","nodesdn","audit","roles","actiongroups","config","internalusers"],"updated_config_size":9,"message":null} is 9 (["allowlist","tenants","rolesmapping","nodesdn","audit","roles","actiongroups","config","internalusers"]) due to: null
Done with success

Anyway this has no effect, Opensearch Logs say:

OpenSearch Security not initialized. (you may need to run securityadmin)

Restart did not help, remove everything and start again did not help

In a browser I see:

OpenSearch Dashboards server is not ready yet

Configuration:

-- opensearch.yml

cluster.name: opensearch-cluster
network.host: 0.0.0.0
node.roles: [ cluster_manager, data ]

bootstrap.memory_lock: "true"

cluster.routing.allocation.disk.threshold_enabled: true
cluster.routing.allocation.disk.watermark.low: 93%
cluster.routing.allocation.disk.watermark.high: 95%

plugins.security.allow_unsafe_democertificates: false

plugins.security.disabled: false
plugins.security.allow_default_init_securityindex: true
plugins.security.restapi.roles_enabled: ["all_access"]

# HTTP/REST TLS + mTLS
plugins.security.ssl.http.enabled: true
#plugins.security.ssl.http.pemcert_filepath:  -> docker-compose.yml
#plugins.security.ssl.http.pemkey_filepath:   -> docker-compose.yml
plugins.security.ssl.http.pemtrustedcas_filepath: certificates/ca/ca.pem
plugins.security.ssl.http.clientauth_mode: OPTIONAL

# Transport TLS betw Nodes
plugins.security.ssl.transport.enabled: true
#plugins.security.ssl.transport.pemcert_filepath: -> docker-compose.yml
#plugins.security.ssl.transport.pemkey_filepath:  -> docker-compose.yml
plugins.security.ssl.transport.pemtrustedcas_filepath: certificates/ca/ca.pem
plugins.security.ssl.transport.enforce_hostname_verification: false

plugins.security.authcz.admin_dn:
  - 'CN=ADMIN,O=**,L=**,ST=**,C=**'
plugins.security.nodes_dn:
  - 'CN=opensearch-node1,O=**,L=**,ST=**,C=**'
  - 'CN=opensearch-node2,O=**,L=**,ST=**,C=**'
  - 'CN=opensearch-node3,O=**,L=**,ST=**,C=**'
-- opensearch-dashboards.yml

server.name: os_dashboards
server.host: "0.0.0.0"

opensearch.username: "admin"
opensearch.password: "admin"


opensearch.ssl.certificate: "/usr/share/opensearch-dashboards/config/certificates/os-dashboards/os-dashboards-client.pem"
opensearch.ssl.key: "/usr/share/opensearch-dashboards/config/certificates/os-dashboards/os-dashboards-client.key"
opensearch.ssl.certificateAuthorities: ["/usr/share/opensearch-dashboards/config/certificates/ca/ca.pem"]
opensearch.ssl.verificationMode: full

#
# Browser <-> Opensearch-Dashboards
# using nginx and let'sencrypt
#
server.ssl.enabled: false

# OpenSearch Dashboards 3.x new features
data_source.enabled: true
workspace.enabled: true
explore.enabled: true
-- docker-compose.yml

---
services:
  opensearch-node1:
    image: opensearchproject/opensearch:3
    container_name: opensearch-node1
    environment:
      - DISABLE_INSTALL_DEMO_CONFIG=true
      - node.name=opensearch-node1
      - discovery.seed_hosts=opensearch-node1,opensearch-node2,opensearch-node3
      - cluster.initial_cluster_manager_nodes=opensearch-node1,opensearch-node2,opensearch-node3
      - bootstrap.memory_lock=true
      - OPENSEARCH_JAVA_OPTS=-Xms2048m -Xmx2048m
      - plugins.security.ssl.transport.pemkey_filepath=certificates/opensearch-node1/transport.key
      - plugins.security.ssl.transport.pemcert_filepath=certificates/opensearch-node1/transport.pem
      - plugins.security.ssl.http.pemkey_filepath=certificates/opensearch-node1/http.key
      - plugins.security.ssl.http.pemcert_filepath=certificates/opensearch-node1/http.pem
    ulimits:
      memlock:
        soft: -1
        hard: -1
      nofile:
        soft: 65536  # maximum number of open files for the OpenSearch user, set to at least 65536 on modern systems
        hard: 65536
    volumes:
      - ./configs/opensearch.yml:/usr/share/opensearch/config/opensearch.yml
      - ./data/opensearch-data1:/usr/share/opensearch/data
      - ./configs/certs:/usr/share/opensearch/config/certificates:ro
      - ./configs/security/config.yml:/usr/share/opensearch/config/opensearch-security/config.yml:ro
      - ./configs/security/internal_users.yml:/usr/share/opensearch/config/opensearch-security/internal_users.yml:ro
      - ./configs/security/roles.yml:/usr/share/opensearch/config/opensearch-security/roles.yml:ro
      - ./configs/security/roles_mapping.yml:/usr/share/opensearch/config/opensearch-security/roles_mapping.yml:ro
      - ./configs/security/action_groups.yml:/usr/share/opensearch/config/opensearch-security/action_groups.yml:ro
#      - ./configs/security/tenants.yml:/usr/share/opensearch/config/opensearch-security/tenants.yml:ro

    ports:
      - "9200:9200"
      - "127.0.0.1:9600:9600"  # required for Performance Analyzer
    networks:
      - opensearch-net

  opensearch-node2:
    image: opensearchproject/opensearch:3
    container_name: opensearch-node2
    environment:
      - DISABLE_INSTALL_DEMO_CONFIG=true
      - node.name=opensearch-node2
      - discovery.seed_hosts=opensearch-node1,opensearch-node2,opensearch-node3
      - cluster.initial_cluster_manager_nodes=opensearch-node1,opensearch-node2,opensearch-node3
      - bootstrap.memory_lock=true
      - OPENSEARCH_JAVA_OPTS=-Xms2048m -Xmx2048m
      - plugins.security.ssl.transport.pemkey_filepath=certificates/opensearch-node2/transport.key
      - plugins.security.ssl.transport.pemcert_filepath=certificates/opensearch-node2/transport.pem
      - plugins.security.ssl.http.pemkey_filepath=certificates/opensearch-node2/http.key
      - plugins.security.ssl.http.pemcert_filepath=certificates/opensearch-node2/http.pem
    ulimits:
      memlock:
        soft: -1
        hard: -1
      nofile:
        soft: 65536
        hard: 65536
    volumes:
      - ./configs/opensearch.yml:/usr/share/opensearch/config/opensearch.yml
      - ./data/opensearch-data2:/usr/share/opensearch/data
      - ./configs/certs:/usr/share/opensearch/config/certificates:ro
      - ./configs/security/config.yml:/usr/share/opensearch/config/opensearch-security/config.yml:ro
      - ./configs/security/internal_users.yml:/usr/share/opensearch/config/opensearch-security/internal_users.yml:ro
      - ./configs/security/roles.yml:/usr/share/opensearch/config/opensearch-security/roles.yml:ro
      - ./configs/security/roles_mapping.yml:/usr/share/opensearch/config/opensearch-security/roles_mapping.yml:ro
      - ./configs/security/action_groups.yml:/usr/share/opensearch/config/opensearch-security/action_groups.yml:ro
    networks:
      - opensearch-net

  opensearch-node3:
    image: opensearchproject/opensearch:3
    container_name: opensearch-node3
    environment:
      - DISABLE_INSTALL_DEMO_CONFIG=true
      - node.name=opensearch-node3
      - discovery.seed_hosts=opensearch-node1,opensearch-node2,opensearch-node3
      - cluster.initial_cluster_manager_nodes=opensearch-node1,opensearch-node2,opensearch-node3
      - bootstrap.memory_lock=true
      - OPENSEARCH_JAVA_OPTS=-Xms2048m -Xmx2048m
      - plugins.security.ssl.transport.pemkey_filepath=certificates/opensearch-node3/transport.key
      - plugins.security.ssl.transport.pemcert_filepath=certificates/opensearch-node3/transport.pem
      - plugins.security.ssl.http.pemkey_filepath=certificates/opensearch-node3/http.key
      - plugins.security.ssl.http.pemcert_filepath=certificates/opensearch-node3/http.pem
    ulimits:
      memlock:
        soft: -1
        hard: -1
      nofile:
        soft: 65536
        hard: 65536
    volumes:
      - ./configs/opensearch.yml:/usr/share/opensearch/config/opensearch.yml
      - ./data/opensearch-data3:/usr/share/opensearch/data
      - ./configs/certs:/usr/share/opensearch/config/certificates:ro
      - ./configs/security/config.yml:/usr/share/opensearch/config/opensearch-security/config.yml:ro
      - ./configs/security/internal_users.yml:/usr/share/opensearch/config/opensearch-security/internal_users.yml:ro
      - ./configs/security/roles.yml:/usr/share/opensearch/config/opensearch-security/roles.yml:ro
      - ./configs/security/roles_mapping.yml:/usr/share/opensearch/config/opensearch-security/roles_mapping.yml:ro
      - ./configs/security/action_groups.yml:/usr/share/opensearch/config/opensearch-security/action_groups.yml:ro
    networks:
      - opensearch-net

  opensearch-dashboards:
    image: opensearchproject/opensearch-dashboards:3
    container_name: opensearch-dashboards
    ports:
      - "127.0.0.1:5601:5601"
    environment:
      OPENSEARCH_HOSTS: '["https://opensearch-node1:9200","https://opensearch-node2:9200", "https://opensearch-node3:9200"]'
      DISABLE_INSTALL_DEMO_CONFIG: "true"
      SERVER_BASEPATH: '/opensearch'
      SERVER_REWRITEBASEPATH: 'true'
    volumes:
      - ./configs/certs:/usr/share/opensearch-dashboards/config/certificates:ro
      - ./configs/opensearch-dashboards.yml:/usr/share/opensearch-dashboards/config/opensearch_dashboards.yml
      - ./data/opensearch-dashboards:/usr/share/opensearch-dashboards/data
    networks:
      - opensearch-net

networks:
  opensearch-net:

I hope you have an idea what’s wrong with this setup.

Tried today: run securityadmin + security plugin health test

ser1@server1-ops:~/.opensearch$ docker exec -it opensearch-node1 bash -c "/usr/share/opensearch/plugins/opensearch-security/tools/securityadmin.sh \

  -cacert '/usr/share/opensearch/config/certificates/ca/ca.pem' \
  -cert  '/usr/share/opensearch/config/certificates/ca/admin.pem' \
  -key   '/usr/share/opensearch/config/certificates/ca/admin.key' \
  -cd    '/usr/share/opensearch/config/opensearch-security' \
  -h     'opensearch-node1' \
  -p     9200 \
  -icl \
  -nhnv"

Security Admin v7
Will connect to opensearch-node1:9200 ... done
Connected as "CN=ADMIN,****"
OpenSearch Version: 3.4.0

Contacting opensearch cluster 'opensearch' and wait for YELLOW clusterstate ...
Clustername: opensearch-cluster
Clusterstate: GREEN
Number of nodes: 3
Number of data nodes: 3
.opendistro_security index already exists, so we do not need to create one.
Populate config from /usr/share/opensearch/config/opensearch-security/
Will update '/config' with /usr/share/opensearch/config/opensearch-security/config.yml
   SUCC: Configuration for 'config' created or updated
Will update '/roles' with /usr/share/opensearch/config/opensearch-security/roles.yml
   SUCC: Configuration for 'roles' created or updated
Will update '/rolesmapping' with /usr/share/opensearch/config/opensearch-security/roles_mapping.yml
   SUCC: Configuration for 'rolesmapping' created or updated
Will update '/internalusers' with /usr/share/opensearch/config/opensearch-security/internal_users.yml
   SUCC: Configuration for 'internalusers' created or updated
Will update '/actiongroups' with /usr/share/opensearch/config/opensearch-security/action_groups.yml
   SUCC: Configuration for 'actiongroups' created or updated
Will update '/tenants' with /usr/share/opensearch/config/opensearch-security/tenants.yml
   SUCC: Configuration for 'tenants' created or updated
Will update '/nodesdn' with /usr/share/opensearch/config/opensearch-security/nodes_dn.yml
   SUCC: Configuration for 'nodesdn' created or updated
Will update '/audit' with /usr/share/opensearch/config/opensearch-security/audit.yml
   SUCC: Configuration for 'audit' created or updated
Will update '/allowlist' with /usr/share/opensearch/config/opensearch-security/allowlist.yml
   SUCC: Configuration for 'allowlist' created or updated
SUCC: Expected 9 config types for node {"updated_config_types":["allowlist","tenants","rolesmapping","nodesdn","audit","roles","actiongroups","config","internalusers"],"updated_config_size":9,"message":null} is 9 (["allowlist","tenants","rolesmapping","nodesdn","audit","roles","actiongroups","config","internalusers"]) due to: null
SUCC: Expected 9 config types for node {"updated_config_types":["allowlist","tenants","rolesmapping","nodesdn","audit","roles","actiongroups","config","internalusers"],"updated_config_size":9,"message":null} is 9 (["allowlist","tenants","rolesmapping","nodesdn","audit","roles","actiongroups","config","internalusers"]) due to: null
SUCC: Expected 9 config types for node {"updated_config_types":["allowlist","tenants","rolesmapping","nodesdn","audit","roles","actiongroups","config","internalusers"],"updated_config_size":9,"message":null} is 9 (["allowlist","tenants","rolesmapping","nodesdn","audit","roles","actiongroups","config","internalusers"]) due to: null
Done with success
ser1@server1-ops:~/.opensearch$ curl --cacert 'configs/certs/ca/ca.pem' \
     --cert  'configs/certs/ca/admin.pem' \
     --key   'configs/certs/ca/admin.key' \
     'https://localhost:9200/_plugins/_security/health?pretty'
{
  "message" : "Not initialized",
  "mode" : "strict",
  "status" : "DOWN",
  "settings" : {
    "plugins.security.cache.ttl_minutes" : 60
  }
}

Here is opendistro_security Document

curl --cacert 'configs/certs/ca/ca.pem' \
     --cert  'configs/certs/ca/admin.pem' \
     --key   'configs/certs/ca/admin.key' \
     -X GET 'https://localhost:9200/.opendistro_security?pretty'

{
  ".opendistro_security" : {
    "aliases" : { },
    "mappings" : {
      "properties" : {
        "actiongroups" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "allowlist" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "audit" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "config" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "internalusers" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "nodesdn" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "roles" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "rolesmapping" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        },
        "tenants" : {
          "type" : "text",
          "fields" : {
            "keyword" : {
              "type" : "keyword",
              "ignore_above" : 256
            }
          }
        }
      }
    },
    "settings" : {
      "index" : {
        "replication" : {
          "type" : "DOCUMENT"
        },
        "number_of_shards" : "1",
        "auto_expand_replicas" : "0-all",
        "provided_name" : ".opendistro_security",
        "creation_date" : "1767737443550",
        "number_of_replicas" : "2",
        "uuid" : "W_74iU0tSPWTBNyZIf6Cvw",
        "version" : {
          "created" : "137257827"
        }
      }
    }
  }
}

@clearuf Could you explain why these options are outside of opensearch.yml file?

      - plugins.security.ssl.transport.pemkey_filepath=certificates/opensearch-node1/transport.key
      - plugins.security.ssl.transport.pemcert_filepath=certificates/opensearch-node1/transport.pem
      - plugins.security.ssl.http.pemkey_filepath=certificates/opensearch-node1/http.key
      - plugins.security.ssl.http.pemcert_filepath=certificates/opensearch-node1/http.pem

@clearuf I’ve tested all your configs and I had to add only the initial admin password. It’s required since 2.12.0

      - "OPENSEARCH_INITIAL_ADMIN_PASSWORD=Eliatra123"

After adding that line and updating password in opensearch_dashboards.yml, I was able to fully start the cluster.

curl --insecure -u admin:Eliatra123 https://localhost:9200/_plugins/_security/health?pretty
{
  "message" : null,
  "mode" : "strict",
  "status" : "UP",
  "settings" : {
    "plugins.security.cache.ttl_minutes" : 60
  }
}

The reported errors are expected at the start but should disappear once the security plugin is fully initiated.