# Wazuh 4.x to 5.x migration checklist

Companion to the lesson: https://maheshtata.com/wazuh/wazuh-4-to-5-migration/

Tested against: Wazuh 5.0 (beta5 documentation, September 2026). Wazuh 5.0 had not
reached general availability at the time of writing, so re-verify every step against the
GA migration guide before using this on a production estate.

Sources for every step below:

- https://documentation.wazuh.com/5.0-beta/migration-to-5x/index.html
- https://documentation.wazuh.com/5.0-beta/migration-to-5x/wazuh-indexer.html
- https://documentation.wazuh.com/5.0-beta/migration-to-5x/wazuh-manager.html
- https://documentation.wazuh.com/5.0-beta/migration-to-5x/wazuh-dashboard.html
- https://documentation.wazuh.com/5.0-beta/migration-to-5x/wazuh-agents.html
- https://github.com/wazuh/wazuh/tree/main/docs/guide/migration

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

---

## Phase 0 — Decisions

- [ ] Source deployment confirmed at Wazuh **4.4.0 or later** (required for index snapshot migration)
- [ ] Target OS confirmed as one of: Amazon Linux 2023, Ubuntu 24.04, Ubuntu 26.04, RHEL 9, RHEL 10 (x86_64 or ARM64)
- [ ] Decision recorded: **new host** or **same host** (parallel)
- [ ] If same host: port map agreed
  - [ ] 1517/TCP free for the 5.x agent channel (no clash with 4.x 1514/1515)
  - [ ] 9200 (indexer), 443 (dashboard), 55000 (manager API) remapped on the host
  - [ ] 1514/1515 deliberately **not** published from the 5.x containers
  - [ ] Memory headroom checked; indexer JVM heap capped
- [ ] Decision recorded: carry historical index data across, or keep the 4.x indexer read-only for its retention window
- [ ] Migration owner named, and a rollback owner named
- [ ] Validation window length agreed (how long 4.x and 5.x run side by side)

## Phase 1 — Inventory and back up 4.x

- [ ] `/var/ossec/etc/ossec.conf` backed up off-host
- [ ] `/var/ossec/etc/internal_options.conf` and `local_internal_options.conf` backed up
- [ ] `/var/ossec/api/configuration/api.yaml` backed up
- [ ] `/var/ossec/etc/rules/`, `/decoders/`, `/lists/` archived
- [ ] Agent groups archived, excluding `merged.mg`:
      `cd /var/ossec/etc && tar -cvzf /tmp/wazuh_groups_backup.tar.gz --exclude='*/merged.mg' shared/*/`
- [ ] `client.keys` copied to a directory readable only by administrators
- [ ] Indexer **active** security configuration exported with the documented backup procedure
      (the files on disk may not match the effective configuration, which lives in the security index)
- [ ] Saved objects that analysts actually use identified
- [ ] External consumers of Wazuh data listed (ticketing, reporting, automation, custom queries)
- [ ] Custom content inventory completed and sized:

| Asset | Count | Owner | 5.0 destination |
|---|---|---|---|
| Custom XML rules | | | Sigma YAML rules, Custom space (re-author by hand) |
| Custom XML decoders | | | YAML decoders (re-author by hand) |
| CDB lists | | | KVDBs inside an integration (re-author by hand) |
| `<integration>` blocks | | | Alerting monitor + Notifications channel |
| `<email_alerts>` / `<syslog_output>` | | | Notifications channels |
| Manager syslog listener (port 514) | | | rsyslog collection host with a Wazuh agent |
| Manager-side `<syscheck>` / `<localfile>` / `<sca>` | | | Agent config or group shared `agent.conf` |
| Custom dashboards and visualisations | | | Saved-object export/import plus field rewrites |

- [ ] Full backup of the 4.x deployment taken before anything is changed

## Phase 2 — Deploy the 5.x environment

- [ ] Install order followed: **indexer, then manager, then dashboard**
- [ ] Deployment method recorded (installation assistant, step by step, Docker, Kubernetes, Ansible)
- [ ] Beta only: `-d pre-release` used with the staging repository (drops at GA)
- [ ] Default credentials changed
- [ ] Indexer node visible: `curl -k -u <USER>:<PASS> https://<INDEXER_IP>:9200/_cat/nodes?v`
- [ ] Manager starts with no configuration errors in `/var/wazuh-manager/logs/wazuh-manager.log`
- [ ] Dashboard health check passes against the manager API
- [ ] A handful of test endpoints attached and event volume observed for several days

## Phase 3 — Recreate configuration and security

Manager:

- [ ] `wazuh-manager.conf` rebuilt **by hand** under the `<wazuh_config>` root (no 4.x file copied in)
- [ ] Only supported sections present: `global`, `logging`, `remote`, `auth`, `indexer`,
      `vulnerability-detection`, `agent-upgrade`, `task-manager`, `wdb`, `cluster`
- [ ] `<alerts>`, `<command>`, `<ruleset>` removed (these cause a startup error)
- [ ] `<syscheck>`, `<rootcheck>`, `<localfile>`, `<wodle name="syscollector">`, `<wodle name="open-scap">`
      removed (these are silently accepted and do nothing, which is worse)
- [ ] `<auth><ssl_auto_negotiate>` removed; `<auth><ciphers>` is a TLS 1.3 ciphersuite list or absent
- [ ] `<vulnerability-detection><index-status>` removed
- [ ] `<indexer><enabled>` removed; certificate paths point at the manager's own certificates
- [ ] Manager-side `monitord.*` internal options renamed into the `wazuh_modules` namespace.
      Note: the migration guide says the `monitord` daemon itself is going, but a 5.0.0-beta5
      manager still ships and starts `wazuh-manager-monitord`, for eight running daemons in
      total. Check `wazuh-manager-control status` on your own build before writing service
      monitoring against a seven-daemon list.
- [ ] `/var/wazuh-manager/bin/wazuh-manager-conf validate` returns clean
- [ ] `/var/wazuh-manager/bin/wazuh-manager-conf dump` reviewed for surprises from schema defaults

Indexer:

- [ ] `/etc/wazuh-indexer/opensearch.yml` reviewed and recreated
- [ ] `/etc/wazuh-indexer/jvm.options` heap set for this host
- [ ] `/etc/wazuh-indexer/log4j2.properties` reviewed
- [ ] Certificates in place under `/etc/wazuh-indexer/certs/`
- [ ] Security files recreated under `/etc/wazuh-indexer/opensearch-security/`:
      `config.yml`, `internal_users.yml`, `roles.yml`, `roles_mapping.yml`, `action_groups.yml`,
      `tenants.yml`, `nodes_dn.yml`, `allowlist.yml`, `audit.yml`
- [ ] Files on disk confirmed to be the configuration you want **before** loading
      (`indexer-security-init.sh` can overwrite settings made through the dashboard or security API)
- [ ] `/usr/share/wazuh-indexer/bin/indexer-security-init.sh` run
- [ ] A recreated non-admin user logs in and sees only what its role permits

Dashboard:

- [ ] Manager API connection recreated under `wazuh_core.hosts` in
      `/etc/wazuh-dashboard/opensearch_dashboards.yml` (`wazuh.yml` no longer exists)
- [ ] Health checks migrated from `checks.*` to `healthcheck.checks_enabled`
- [ ] `cron.*` and `wazuh.monitoring.*` settings dropped (removed in 5.x)
- [ ] Branding recreated under `opensearchDashboards.branding.*`
- [ ] Noted: report logo, header and footer customisation is **not supported** in 5.x.
      If branded PDF reports are a deliverable, a separate plan exists for them.
- [ ] Advanced Settings recreated per tenant (`timeout`, `enrollment.dns`, `reports.csv.maxRows`,
      `wazuh.updates.disabled`)

## Phase 4 — Re-author detection content

- [ ] Severity mapping agreed and written down (4.x levels 0–16 to `informational` / `low` /
      `medium` / `high` / `critical`)
- [ ] Rules rebuilt in the Draft space
- [ ] Validated in the Test space with the log test tool
- [ ] Promoted to the Custom space
- [ ] Custom content attached to a detector, with the detector schedule set
- [ ] Chained rules (`if_sid`, `if_group`, `if_level`, `if_matched_sid`) redesigned; the destination
      for each is recorded (decoder, bucket-level monitor, or automation platform)
- [ ] Correlation rules (`frequency`, `timeframe`, `same_*`, `different_*`) redesigned the same way
- [ ] CDB lookups moved into decoders as KVDB lookups
- [ ] A known-bad sample per source type produces the expected normalised fields and the expected finding

## Phase 5 — Historical data (optional)

- [ ] Shared snapshot directory created and exported (for example NFS at `/mnt/snapshots`)
- [ ] Directory mounted at the same path on every indexer node, both clusters
- [ ] Ownership set: `chown wazuh-indexer:wazuh-indexer /mnt/snapshots`
- [ ] `path.repo` set in `opensearch.yml` on every node, service restarted
- [ ] Repository registered on 4.x (type: shared file system, location `/mnt/snapshots`)
- [ ] Snapshot taken of Wazuh **data** indices only
- [ ] "Include global state" **not** selected; system indices excluded
- [ ] Read-only repository registered on 5.x (`"readonly": true`)
- [ ] Snapshot restored with a `restored_` prefix
- [ ] System indices and global cluster state **not** restored
- [ ] Index pattern created (for example `restored_wazuh*`) with `timestamp` as the time field
- [ ] Historical data verified in Discover
- [ ] Analysts told which pattern holds pre-cutover data

## Phase 6 — Groups first, then agents

Groups (must complete before any agent connects):

- [ ] Group archive restored: `tar -xvzf /tmp/wazuh_groups_backup.tar.gz -C /var/wazuh-manager/etc/`
- [ ] Ownership fixed: `chown -R wazuh-manager:wazuh-manager /var/wazuh-manager/etc/shared/`
- [ ] Every restored `agent.conf` reviewed against the 5.0 reference; unsupported settings removed
- [ ] Manager restarted and the shared configuration compiles
- [ ] Group list verified: `ls /var/wazuh-manager/etc/shared/`

Registrations:

- [ ] `client.keys` copied to the 5.x manager, readable only by administrators
- [ ] API token obtained from `POST /security/user/authenticate?raw=true`
- [ ] Registrations imported through `POST /agents/insert` (see `import-client-keys.sh`)
- [ ] Large fleets imported in batches with a fresh token per batch
- [ ] Any ID collisions resolved by deleting the existing registration before retrying
- [ ] Agent count on the 5.x manager matches the source

Cutover, per batch:

- [ ] Batch defined (one group, or one site, or one maintenance window)
- [ ] Enrolment password distributed if agents will re-enrol (`/var/wazuh-manager/etc/authd.pass`)
- [ ] Manager CA placed at the default path on endpoints if the certificate is not publicly trusted
      (`/var/ossec/etc/certs/root-ca.pem` on Linux and macOS, `<installdir>\certs\root-ca.pem` on Windows)
- [ ] `<agent><manager><endpoint>` repointed at the 5.x manager. An endpoint without a port works:
      the agent logs `No port in <agent><manager><endpoint>. Using the default port 1517.` and
      carries on. The manager serves its routes under `<global_prefix>`, `/wazuh-manager/` by
      default, so anything probing the root path sees `{"error":"not_found"}` from a healthy manager.
- [ ] Agents restarted (`systemctl restart wazuh-agent`, `/Library/Ossec/bin/wazuh-control restart`,
      `Restart-Service -Name WazuhSvc`)
- [ ] `status` in `/var/ossec/var/run/wazuh-agentd.state` reads `connected`, not `pending`
- [ ] Agents show as active on the 5.x dashboard
- [ ] Group assignment verified: `GET /agents?agents_list=<agent-id>&select=group`
- [ ] Events landing in the expected `wazuh-events-v5-<category>` stream, not piling up in `unclassified`
- [ ] Agent package upgrade scheduled: 4.x to latest 4.14.x, then 4.14.x to 5.0.0
      (a direct jump is blocked by pre-install validation)
- [ ] Noted: until an agent is upgraded, FIM, SCA, inventory, active response and vulnerability
      detection are not fully supported on that endpoint

## Phase 7 — Dashboards and side-by-side validation

- [ ] Custom saved objects exported from 4.x with **Include related objects** enabled
- [ ] Imported into 5.x with **Check for existing objects** (not "overwrite all conflicts")
- [ ] 4.x **default** dashboards and visualisations deliberately **not** imported
- [ ] `wazuh-alerts-*` references replaced with a 5.x pattern (`wazuh-events*` / `wazuh-findings*`)
- [ ] Field references rewritten to WCS names:
      `rule.level` → `wazuh.rule.level` (keyword, not integer),
      `rule.description` → `wazuh.rule.title`,
      `rule.id` → `wazuh.rule.id` (UUID),
      `agent.name` → `wazuh.agent.name`,
      `agent.id` → `wazuh.agent.id`
- [ ] If 4.x had multi-tenancy: objects exported and imported per tenant
- [ ] Every migrated dashboard opens and every panel loads
- [ ] A known source produces a 5.x finding where 4.x produced an alert
- [ ] One representative detection triggered per integration category
- [ ] Notifications arrive through the Alerting monitor and Notifications channel, including escalation
- [ ] Reporting and ticketing consumers read the new field names
- [ ] Indexer disk watched for the whole validation window (5.0 indexes all events, not only matches)

## Phase 8 — Retire 4.x, or roll back

- [ ] Validation window completed with no open gaps
- [ ] 4.x manager stopped
- [ ] 4.x indexer kept until the retention obligation is satisfied or the restored snapshot covers it
- [ ] `<remote><legacy>` disabled on the 5.x manager so only 5.x agents can connect
- [ ] Runbooks, monitoring checks and automation updated for the new paths, ports and field names
- [ ] Rollback path confirmed still available until the 4.x environment is decommissioned

Rollback note: the official guide documents no rollback procedure. Rollback is the 4.x
environment still running and still receiving events. To roll a batch back, repoint those
agents at the 4.x manager and restart them; their 4.x registration was never removed.
