# Wazuh 5.0 hardening checklist

Companion to <https://maheshtata.com/wazuh/wazuh-security-hardening/>

Every item is drawn from the Wazuh 5.0-beta documentation (beta5, September 2026), with the
defaults marked "lab-verified" read off a real 5.0.0-beta5 stack on 23 September 2026. Wazuh 5.0 is
still in beta: re-verify defaults at general availability, and re-verify anything marked
**UNVERIFIED** before you rely on it.

How to use this: copy it into your change record, work top to bottom, and record who signed off
and when. Items are ordered so that nothing later depends on something earlier being skipped.

Legend: `[ ]` to do, `[x]` done, `[n/a]` not applicable with a reason.

---

## 1. Credentials

- [ ] **Rotate `admin`** (indexer, and the dashboard login). Default password is `admin`.
- [ ] **Rotate `kibanaserver`** (dashboard to indexer).
- [ ] **Rotate the manager's indexer service account** (manager to indexer). The 5.0-beta docs name it two ways: `wazuh-server` on the indexer security page, `wazuh-manager` on the indexer-connector page and in the official Docker compose (`INDEXER_USERNAME`). Check `internal_users.yml` for which one your install created, then rotate that one. Documented default password equals the username.
- [ ] **Rotate `wazuh-dashboard`** (dashboard to indexer). Documented default password equals the username.
- [ ] **Rotate `wazuh`** (manager API administrator).
- [ ] **Rotate `wazuh-wui`** (dashboard to manager API).
- [ ] **Remove unused OpenSearch demo accounts** from `internal_users.yml`:
      `anomalyadmin`, `kibanaro`, `logstash`, `readall`, `snapshotrestore`. Their bcrypt hashes are
      published in the documentation.
- [ ] **Count the internal users on your own install and account for every one.** A 5.0.0-beta5 lab
      stack shipped eleven, including `wazuh-demo`, `wazuh-admin`, `wazuh-readonly` and
      `wazuh-manager` alongside the accounts named above. The lab also confirmed `admin`/`admin` on
      the dashboard and indexer and `wazuh`/`wazuh` on the manager API, out of the box.
- [ ] After rotating `kibanaserver`, update the dashboard keystore:
      `echo <PASSWORD> | /usr/share/wazuh-dashboard/bin/opensearch-dashboards-keystore --allow-root add -f --stdin opensearch.password`
- [ ] After rotating `wazuh-wui`, update `wazuh_core.hosts` in
      `/etc/wazuh-dashboard/opensearch_dashboards.yml` and restart the dashboard.
- [ ] **Restrict `/etc/wazuh-dashboard/opensearch_dashboards.yml`** — it holds an API password in
      plain text.
- [ ] **Move indexer credentials into the manager keystore** rather than `wazuh-manager.conf`:
      `wazuh-manager-keystore -f indexer -k username -v <USER>` and `-k password -v <PASSWORD>`.
- [ ] **Treat the cluster `<key>` as a credential.** 32 characters, generated at install. Keep it
      out of scripts, source control, documentation and shell history.
- [ ] **Password policy applied:** 8 to 64 characters, at least one uppercase, one lowercase, one
      digit and one of `.*+?-` (enforced by the passwords tool).
- [ ] Credentials stored in a vault, not in a runbook.

Tool: `/usr/share/wazuh-indexer/plugins/opensearch-security/tools/wazuh-passwords-tool.sh`
Indexer users: run on any indexer node. Manager API users: run on the manager master node.

## 2. Network exposure

- [ ] **443/TCP (dashboard)** reachable only from where users are, ideally via an identity-aware
      proxy or VPN.
- [ ] **9200/TCP (indexer)** firewalled to the manager, dashboard and named administrators.
- [ ] **55000/TCP (manager API)** firewalled. It binds `0.0.0.0` by default.
- [ ] **1516/TCP (manager cluster)** restricted to cluster nodes. The reference gives a default bind
      of `127.0.0.1`; the 5.0.0-beta5 Docker image ships the node's own hostname instead. Read the
      value on your manager.
- [ ] **1517/TCP (agent HTTPS)** reachable from the agent estate only. Lab-verified on beta5: the
      shipped default bind is `0.0.0.0`, not `127.0.0.1` as the reference page states.
- [ ] **1514 legacy listener removed** once no 4.x agents remain. Lab-verified on beta5: the shipped
      configuration contains `<remote><legacy><enabled>yes</enabled>` on port 1514, so this is a
      removal, not a non-decision. Deleting the `<legacy>` block is equivalent to disabling it.
- [ ] **1515 legacy enrolment retired** with `<auth><legacy_enrollment>no</legacy_enrollment>`,
      which keeps the HTTPS `POST /enroll` path available. `<remote_enrollment>no</remote_enrollment>`
      turns off both; the local socket used by the API is unaffected either way.
- [ ] Port inventory recorded before any of the above, so a closed port is a decision and not an
      outage. For reference, a stock 5.0.0-beta5 manager was observed listening on
      `1514 1515 1516 1517 55000` with nothing enabled by hand.

## 3. Certificates

- [ ] **`wazuh-certificates.tar` deleted** from every node. It contains every private key in the
      deployment.
- [ ] **Certificate directory `chmod 500`, contents `chmod 400`**, owned by the component service
      user, on the indexer, manager and dashboard.
- [ ] **`admin.pem` and `admin-key.pem` moved off the node** into a vault. This is a superuser
      credential; retrieve it for the operation and remove it afterwards.
- [ ] **`plugins.security.nodes_dn` accurate** in `/etc/wazuh-indexer/opensearch.yml`, and
      `nodes_dn.yml` consistent with it. This is what stops an unexpected node joining the cluster.
- [ ] **Expiry dates recorded** for every issued certificate. **UNVERIFIED:** the beta documents no
      rotation procedure and no default validity period.
- [ ] **Renewal rehearsed** on a copy of the deployment: regenerate with the same node names and
      addresses, redeploy to the same paths and ownership, restart in the order indexer, manager,
      dashboard, then run `/usr/share/wazuh-indexer/bin/indexer-security-init.sh` once.
- [ ] **Dashboard presented with a certificate users' browsers already trust**, or `root-ca.pem`
      distributed deliberately.

## 4. Agent-facing surface

- [ ] **Decision recorded on mutual TLS.** Defaults: `<remote><https><verification_mode>` is `none`
      (client certificate not verified) and `<auth><ssl_agent_ca>` is unset (agent certificate
      verification disabled).
- [ ] If mutual TLS is in scope: `ssl_agent_ca` set, `verification_mode` set to `certificate` or
      `full`, and `ssl_verify_host` considered.
- [ ] **Agents verify the manager** with `<agent><ssl><certificate_authorities>`.
- [ ] If a custom manager certificate is issued, `<auth><ssl_manager_cert>` and
      `<ssl_manager_key>` set. Without these the manager keeps using `etc/certs/remoted.pem`.
- [ ] **Enrolment password in use** (`<auth><use_password>yes`). The installer sets this; the
      reference default is `no`.
- [ ] **`/var/wazuh-manager/etc/authd.pass` rotation scheduled.** Delete the file on the master and
      restart `wazuh-manager-authd`; a new password is generated and distributed to workers. Do not
      blank the file: an empty or invalid file stops the daemon starting.
- [ ] **`<force><enabled>` reviewed.** Default `yes` allows a new enrolment to overwrite an existing
      agent entry.
- [ ] **`<purge>` reviewed.** Default `no` keeps a `!`-prefixed placeholder in `client.keys`
      recording removed identities.
- [ ] **Anti-tampering enabled per Linux group** in
      `/var/wazuh-manager/etc/shared/<GROUP>/agent.conf`:
      `<agent_config os="linux"><anti_tampering><package_uninstallation>yes</package_uninstallation></anti_tampering></agent_config>`
- [ ] **Separate uninstall control planned for Windows and macOS.** Anti-tampering is Linux only.
- [ ] **`uninstall_validation.env` handled safely:** prefer the temporary token form over stored API
      credentials, use a scoped API user, delete the file after the uninstall, and keep it out of
      golden images. The documented example sets `VALIDATION_SSL_VERIFY="false"`.

## 5. Manager API

- [ ] **`auth_token_exp_timeout` lowered** from the 900-second default via
      `PUT /security/config`. Any security configuration change revokes every issued token.
- [ ] **`max_login_attempts` lowered** from the documented default of 50.
- [ ] `block_time` (default 300), `max_request_per_minute` (default 300) and
      `max_unauthenticated_request_per_minute` (default 10) reviewed.
- [ ] **CORS left disabled** unless a specific origin needs it.
- [ ] **One API user per integration**, each with an RBAC role scoped to the endpoints it calls.
      No integration uses `wazuh`.
- [ ] **Tokens held in memory** in scripts, never written to a file or an environment file.
- [ ] `run_as` with an authorization context used where the caller has its own identity. The context
      body is capped at a fixed 8192 bytes that `max_upload_size` does not lift.

## 6. Indexer and dashboard access

- [ ] **`plugins.alerting.filter_by_backend_roles: true`** (default `false`).
- [ ] **`plugins.security_analytics.filter_by_backend_roles: true`** (default `false`).
- [ ] **`opensearch.notifications.general.filter_by_backend_roles: true`** (default `false`).
- [ ] **`opensearch.notifications.core.http.host_deny_list` populated.** The Notifications module
      makes outbound HTTP requests to user-supplied URLs, which is a server-side request forgery
      primitive against internal networks. Default is empty.
- [ ] **Roles duplicated, not modified.** Default roles and users cannot be changed.
- [ ] **SSO configured** for interactive users.
- [ ] **One break-glass local administrator**, long unique password, held offline, use recorded.
- [ ] **Document-level security tested on your own data.** Still undocumented by Wazuh as of
      September 2026, but lab-verified on 5.0.0-beta5: a role with a `term` query on
      `wazuh.agent.groups` restricted a user to one group's documents across the events, findings
      and state indices, and an index the role did not name returned HTTP 403. Re-test after every
      upgrade; a role that stops filtering looks exactly like one that works.

## 7. Monitoring the platform itself

- [ ] `/var/wazuh-manager/logs/wazuh-manager.log` collected and alerted on.
- [ ] `/var/log/wazuh-indexer/wazuh-indexer.log` collected and alerted on.
- [ ] Authentication failures against the dashboard, the indexer and the manager API alert
      somewhere a human reads.
- [ ] Certificate expiry alerting in place.
- [ ] CTI synchronisation state monitored: compare `local_offset` and `remote_offset` in the
      `.wazuh-cti-consumers` index.
- [ ] Agent disconnection alerting in place, so an agent removed by an attacker is noticed.

---

## Sign-off

| Section | Completed by | Date | Notes |
| --- | --- | --- | --- |
| 1. Credentials | | | |
| 2. Network exposure | | | |
| 3. Certificates | | | |
| 4. Agent-facing surface | | | |
| 5. Manager API | | | |
| 6. Indexer and dashboard access | | | |
| 7. Monitoring | | | |
