Plan a Wazuh 5 migration as a parallel deployment. There is no in-place upgrade from 4.x to 5.x, so the documented path is: stand up a new 5.x environment, recreate configuration and security settings by hand, restore agent groups before anything connects, import agent registrations through the API so IDs and keys survive, then cut agents over in batches while 4.x keeps running as the rollback. This runbook puts the official migration guide in that order, with the verification step for each phase.

What you need

  • A Wazuh 4.x deployment running 4.4.0 or later. Index data migration by snapshot requires it.
  • Target hosts meeting the 5.0 requirements: Amazon Linux 2023, Ubuntu 24.04 or 26.04, or RHEL 9 or 10, on x86_64 or ARM64.
  • A clear read of what changes in Wazuh 5. Do not start this runbook without it. Several steps only make sense if you know what was removed.
  • An inventory of your custom rules, decoders, CDB lists and manager integrations. This number drives the schedule more than anything else.
  • A shared file system reachable by both indexer clusters if you intend to carry historical data across.

The companion files for this lesson include a printable checklist and the registration import script.

Phase 0: decide the shape of the migration

Two decisions come before any package is installed.

New box or same box. A separate 5.x environment is the clean option and the one the documentation assumes. The same-box option is legitimate when hardware or a cloud budget will not stretch, and it works because the 5.x agent channel is port 1517, which does not collide with the 4.x agent ports 1514 and 1515.

The collisions are elsewhere: the indexer wants 9200, the dashboard wants 443 and the manager API wants 55000, and your 4.x stack already holds all three. Running the 5.x stack in containers and remapping those host ports is the tidy way through. The official single-node Docker deployment publishes 1517, 1514, 1515, 514/udp, 55000, 9200 and 443, so on a shared box you edit that port list down to 1517 plus remapped management ports, and leave 1514 and 1515 unpublished so the 4.x manager keeps them.

How much history to carry. Restored 4.x indices keep their 4.x mappings, stay read-only, and 5.x never writes to them. They are a historical archive you query through a separate index pattern, not a continuation. Decide whether you need that archive inside the new deployment at all, or whether keeping the 4.x indexer alive in read-only mode for its retention window is simpler. Skipping the snapshot phase removes the shared file system requirement entirely.

Phase 1: inventory and back up the 4.x deployment

Do this while 4.x is healthy, not on cutover night.

Back up the manager configuration and the custom content you will have to re-author:

mkdir -p /tmp/wazuh-4x-backup
cp /var/ossec/etc/ossec.conf /tmp/wazuh-4x-backup/
cp /var/ossec/etc/internal_options.conf /tmp/wazuh-4x-backup/
cp /var/ossec/etc/local_internal_options.conf /tmp/wazuh-4x-backup/
cp /var/ossec/api/configuration/api.yaml /tmp/wazuh-4x-backup/
tar -czf /tmp/wazuh-4x-backup/custom-ruleset.tar.gz \
    /var/ossec/etc/rules/ /var/ossec/etc/decoders/ /var/ossec/etc/lists/

Archive the agent group folders, excluding the compiled merged.mg files, which the manager regenerates:

cd /var/ossec/etc && tar -cvzf /tmp/wazuh_groups_backup.tar.gz --exclude='*/merged.mg' shared/*/

Copy client.keys somewhere only administrators can read. It contains agent authentication keys.

Also capture what is not in a config file: the indexer’s active security configuration, exported through the backup procedure rather than read off disk, since the effective configuration lives in the security index; the saved objects your analysts actually use; and every external query that reads Wazuh data. Move all of it off the host. On a same-box migration these files must survive the 4.x removal.

Inventory the work you cannot copy

Write down, per item, who owns it and whether it is still needed:

4.x asset 5.0 destination Effort
Custom XML rules Sigma YAML rules in the Custom space Re-author by hand, no tool
Custom XML decoders YAML decoders in the normalization engine Re-author by hand, no tool
CDB lists KVDBs inside an integration, lookups moved into the decoder Re-author by hand, no tool
<integration> blocks Alerting monitor plus Notifications channel Rebuild in the dashboard
<email_alerts>, <syslog_output> Notifications channels Rebuild in the dashboard
Manager syslog listener on 514 rsyslog on a collection host with an agent reading it New host or reuse one
Manager-side <syscheck>, <localfile>, <sca> Agent config or a group’s shared agent.conf Move, then re-test
Custom dashboards and visualisations Saved-object export and import, plus field rewrites Export now, rewrite later

This table is the migration schedule. The install is a day; this is the rest.

Phase 2: deploy the 5.x environment

Install order is indexer, then manager, then dashboard. On a lab or small deployment the installation assistant does all three.

During the beta the assistant is pulled from the pre-release repository:

wget https://packages-staging.xdrsiem.wazuh.info/pre-release/5.x/installation-assistant/wazuh-install-5.0.0-beta5.sh && sudo bash ./wazuh-install-5.0.0-beta5.sh -a -id -d pre-release

The -d pre-release flag and the staging URL exist only while 5.0 is in beta. At GA the assistant comes from the normal package host and the flag goes away. For a distributed deployment, follow the step-by-step installation instead so you control certificates and node names.

Verification for this phase. The indexer node reports itself in the cluster, the manager starts with no configuration errors in /var/wazuh-manager/logs/wazuh-manager.log, and the dashboard health check under Dashboard Management passes against the manager API.

curl -k -u <USERNAME>:<PASSWORD> https://<WAZUH_INDEXER_IP>:9200/_cat/nodes?v

Phase 3: recreate configuration and security settings

Recreate, do not copy. Copying a 4.x file into a 5.x installation is the single most common way to end up with a manager that will not start.

On the manager, read /tmp/wazuh-4x-backup/ossec.conf and rebuild only the supported settings in /var/wazuh-manager/etc/wazuh-manager.conf, under the new <wazuh_config> root. Remember the section list shrank to ten and that <alerts>, <command> and <ruleset> now cause a startup error while <syscheck>, <rootcheck>, <localfile> and the syscollector wodle are accepted and silently ignored. Validate before restarting:

/var/wazuh-manager/bin/wazuh-manager-conf validate

On the indexer, the files to review live under /etc/wazuh-indexer/: opensearch.yml, jvm.options, log4j2.properties, certs/ and opensearch-security/. Recreate internal users, roles, role mappings and external auth providers in the security files, then load them:

/usr/share/wazuh-indexer/bin/indexer-security-init.sh

On the dashboard, wazuh.yml is gone. Manager API connections move into /etc/wazuh-dashboard/opensearch_dashboards.yml under wazuh_core.hosts, health checks move from individual checks.* settings to healthcheck.checks_enabled, and the cron.* and wazuh.monitoring.* settings are removed outright. Report logo, header and footer customisation is not supported in 5.x, so if branded PDF reports are a contractual deliverable, that gap needs its own plan.

Verification for this phase. Log in to the dashboard as a recreated non-admin user and confirm they see what their role should allow and nothing more. Do this before any real data arrives, while a mistake is cheap. The detail is in Wazuh SSO and RBAC.

Phase 4: re-author detection content

This phase runs in parallel with everything else and usually finishes last.

Work through the inventory from phase 1 in the content management system, using the space lifecycle: create in Draft, load into Test to validate with the log test tool, promote to Custom when it behaves, then attach the Custom content to a detector so it runs.

Three things to settle early:

  • Severity mapping. 4.x levels 0 to 16 become five keywords. Decide the mapping once, write it down, and apply it consistently, because your escalation rules depend on it.
  • Chained rules. Anything built on if_sid or frequency has no direct translation. Decide per rule whether the logic belongs in the decoder, in a bucket-level Alerting monitor, or in your automation platform.
  • CDB lookups. These move out of rules and into decoders as KVDB lookups. That often means merging several 4.x rules into one decoder plus one rule.

The authoring detail is in writing a custom decoder in YAML and writing Sigma YAML detection rules.

Verification for this phase. Feed a known-bad sample of each source type through the log test tool and confirm both that the decoder normalises the fields you expect and that the rule produces a finding.

Phase 5: carry historical data across (optional)

Skip this if you decided in phase 0 to keep the 4.x indexer for its retention window.

The snapshot route needs a repository directory that both indexer clusters can reach, typically an NFS export, mounted at the same path on every node, owned by wazuh-indexer, and declared in path.repo in opensearch.yml on each node.

On 4.x, register the repository and take a snapshot of the Wazuh data indices only. Do not include system indices, and make sure Include global state is not selected: you are moving data, not the 4.x configuration.

On 5.x, register the same location as a read-only repository:

curl -sk -X PUT --cert /etc/wazuh-indexer/certs/admin.pem --key /etc/wazuh-indexer/certs/admin-key.pem "https://<WAZUH_INDEXER_IP>:9200/_snapshot/Wazuh-4x-migration" -H "Content-Type: application/json" -d '{"type":"fs","settings":{"location":"/mnt/snapshots","readonly":true}}'

Restore from the dashboard under Indexer management, renaming the restored indices with a restored_ prefix so they never get confused with live 5.x streams. Do not restore system indices or the global cluster state.

Verification for this phase. Create an index pattern such as restored_wazuh* with timestamp as the time field, open Discover, and confirm the historical data is queryable. Tell your analysts that this pattern is where anything older than the cutover lives, because the 5.x default pattern will not show it.

Phase 6: groups first, then agents

Order matters here more than anywhere else in the migration, because getting it wrong locks agents out.

Restore the groups before anything connects

tar -xvzf /tmp/wazuh_groups_backup.tar.gz -C /var/wazuh-manager/etc/
chown -R wazuh-manager:wazuh-manager /var/wazuh-manager/etc/shared/

Review each restored agent.conf against the 5.0 reference before restarting the manager. A deprecated tag in a shared configuration stops that group’s configuration compiling.

An agent declares its groups at enrolment and the manager validates them. If a declared group does not exist, the enrolment is rejected outright, and one unknown group in the list is enough to reject the whole request. The checksum-based group guessing of 4.x is removed, so an agent that declares nothing lands in default and stays there until you move it.

Import the agent registrations

Copy client.keys from the 4.x manager, get an API token, and import each entry. The 5.x API preserves the ID, name, IP and key:

TOKEN=$(curl -sk -u <USERNAME>:<PASSWORD> -X POST "https://<WAZUH_MANAGER_IP>:55000/security/user/authenticate?raw=true")
while read -r id name ip key; do
  [ "${id#!}" = "$id" ] || continue
  curl -sk -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
    -X POST "https://<WAZUH_MANAGER_IP>:55000/agents/insert" \
    -d "{\"id\":\"$id\",\"name\":\"$name\",\"ip\":\"$ip\",\"key\":\"$key\"}"
  echo
done < <WORKING_DIRECTORY>/client.keys

The token expires, so for a large fleet import in batches and request a fresh token before each one. If an ID already exists on the 5.x manager the import fails for that entry, and you delete the existing registration before retrying.

The repository for this lesson ships this loop as a script with the batching and a dry-run mode added.

Cut agents over, one group at a time

Repoint the agent at the new manager:

<agent>
  <manager>
    <endpoint><WAZUH_MANAGER_IP></endpoint>
  </manager>
</agent>

An endpoint written without a port works, and says so in the agent log at every start:

2026/09/23 02:38:49 wazuh-agentd: INFO: No port in <agent><manager><endpoint>. Using the default port 1517.

That line is informational, not a fault, and it appears twice per start. Write the port in anyway if your managers sit behind anything that remaps it. The path prefix is the other half: a beta5 manager serves its routes under <global_prefix>, which defaults to /wazuh-manager/, and the official agent container image sets its endpoint as host:1517/wazuh-manager/ accordingly. A health check or a proxy rule written against the root path gets {"error":"not_found"} from a healthy manager.

Then restart the agent: systemctl restart wazuh-agent on Linux, /Library/Ossec/bin/wazuh-control restart on macOS, Restart-Service -Name WazuhSvc on Windows. Only the Linux path was exercised in the lab behind this page.

A 4.x agent can connect to a 5.x manager over the legacy channel, but FIM, SCA, inventory, active response and vulnerability detection are not fully supported until the agent is upgraded. Treat the repoint as the cutover and the package upgrade as a second pass.

For the package upgrade, the supported path is 4.x to the latest 4.14.x, then 4.14.x to 5.0.0. A direct jump is blocked by pre-install validation. Two checks run before a remote WPK upgrade installs anything: the manager must answer HTTPS on the agent’s target port and endpoint, and the upgraded agent must be able to verify the manager’s certificate. If neither the system trust store nor a CA at /var/ossec/etc/certs/root-ca.pem verifies it, the upgrade aborts and the agent stays on 4.14.x.

Verification for this phase. For each batch: the agent count on the 5.x dashboard matches what you cut over; group assignment is correct, which you can check with GET /agents?agents_list=<agent-id>&select=group; and events from those agents are landing in the expected wazuh-events-v5-<category> stream, not piling up in unclassified.

Add one more check on the agent itself. /var/ossec/var/run/wazuh-agentd.state carries a status line, and it reads pending while enrolment is failing and connected once a key has been accepted. A batch that looks quiet on the dashboard and reads pending on the endpoints is an enrolment problem, not a network one.

Phase 7: dashboards, then side-by-side validation

Export your custom saved objects from 4.x with Include related objects enabled, and import them into 5.x choosing Check for existing objects so the provisioned 5.x defaults survive. Do not import the 4.x default dashboards. They carry outdated index patterns and unsupported field references and will overwrite the new ones.

Then rewrite what the import cannot: replace wazuh-alerts-* references with a 5.x pattern such as wazuh-events* or wazuh-findings*, and update every field reference to its Wazuh Common Schema name, rule.level to wazuh.rule.level, rule.description to wazuh.rule.title, agent.name to wazuh.agent.name and so on. If 4.x had multi-tenancy enabled, export and import per tenant, because saved objects are tenant-scoped.

Now run both environments in parallel for a defined window and compare deliberately. Take a source you know well and confirm the same activity produces a finding on 5.x where it produced an alert on 4.x. Trigger one representative detection per integration category. Confirm notifications arrive through the new monitor and channel, including the escalation path and not only the test message. Confirm reporting and ticketing consumers read the new field names. And watch indexer disk for the whole window, because that is where the all-events model shows up.

Phase 8: retire 4.x, or roll back

Retire only after the validation window has passed with no open gaps. Stop the 4.x manager first and leave its indexer up until your retention obligation is satisfied or the snapshot restore covers it. Once the last agent is on 5.x, turn off the <legacy> block inside <remote> in wazuh-manager.conf so only 5.x agents can connect. That is a cleaner cutover proof than reading a dashboard.

Rollback is not a procedure, it is a state: the 4.x environment still running and still receiving events. The official guide documents no rollback path from 5.x, which is why the parallel deployment and the per-group cutover matter. To roll back a batch, point those agents at the 4.x manager and restart them. Their 4.x registration was never removed.

Common errors

ERROR: Invalid group: <group>. Unable to add agent (from manager)

Cause: the agent declared a group in <enrollment><groups> that does not exist on the 5.x manager. One unknown group rejects the entire enrolment, and the valid groups in the list are not applied either.

Fix: restore or create the group first, from the dashboard under Agents management or with POST /groups, then restart the agent so it re-enrols.

ERROR: (1244): Invalid configuration at '/auth/ssl_auto_negotiate': unknown option (does not satisfy 'additionalProperties') [schema /properties/auth]

Cause: a 4.x option was carried into wazuh-manager.conf. The 5.0 schema rejects unknown options and the manager refuses to start.

Fix: remove the option named in the JSON pointer, then run bin/wazuh-manager-conf validate to catch the remaining ones in a single pass.

UPGRADE BLOCKED: Incompatible version detected

Cause: an agent package upgrade was attempted from a version earlier than 4.14.0 directly to 5.0.0.

Fix: upgrade to the latest 4.14.x, confirm the agent starts cleanly, then upgrade to 5.0.0. On macOS the same block surfaces as a generic installer failure, so check the agent version before blaming the package.

ERROR: Invalid TLS 1.3 cipher suite list: '<value>' followed by ERROR: Could not set up SSL connection! Check certification configuration.

Cause: the agent’s <enrollment><ssl_cipher> still holds a 4.x OpenSSL cipher string. Unlike the manager, the agent accepts it at startup and only fails when it tries to enrol.

Fix: set a TLS 1.3 ciphersuite list or remove the option to take the default. <enrollment><auto_method> was removed outright and is accepted but ignored.

The import fails with the agent ID already in use

Cause: POST /agents/insert was run twice, or the ID exists from a test enrolment against the new manager.

Fix: delete the existing registration on the 5.x manager, then rerun the import for that entry. The import script in the companion files for this lesson skips IDs that already exist so a rerun is safe.

Primary sources

Work from the Wazuh 5.x migration guide and its per-component pages for the indexer, the manager, the dashboard and the agents. The migration guides in the Wazuh repository go further on configuration options, rule conversion and the agent upgrade path than the documentation site does, and they carry the exact log strings.

For what happens after the cutover, see upgrade, backup and index retention.

Next: What Wazuh Really Costs: Build vs Buy for a SOC.

Files for this lesson

Every file this lesson refers to, exactly as used. Open a file to read it in the browser, or download the set as a zip. Download wazuh-4-to-5-migration.zip. The full set for all lessons is at wazuh-tutorial-files.zip.

Frequently asked questions

How do I upgrade Wazuh 4.x to 5.x?

You do not upgrade the central components, you migrate. Deploy a new 5.x environment beside the running 4.x one, recreate configuration and security settings by hand, import agent registrations through the manager API, then cut agents over group by group. Only the agent packages support an in-place upgrade, and only from 4.14.0 or later.

Can I run Wazuh 4.x and 5.x on the same server?

Yes, if you keep the port map clean. The 5.x agent channel is 1517, which does not clash with 1514 or 1515, but the indexer on 9200, the dashboard on 443 and the manager API on 55000 all do. Running 5.x in containers and remapping those three host ports is the practical way to do it on one box.

Will my Wazuh agents keep their IDs after migration?

Yes. The documented procedure copies client.keys from the 4.x manager and imports each entry through POST /agents/insert on the 5.x API, which preserves the agent ID, name, IP and key. Agents then repoint at the new manager and reconnect without re-enrolling.

How long does a Wazuh 5 migration take?

The install is the short part. The schedule is set by how much custom content you have, because XML rules, decoders and CDB lists must be re-authored by hand with no conversion tool, and by how many agents you can cut over per maintenance window. Inventory both before you pick a date.

What is the rollback plan for a Wazuh 5 migration?

The official guide documents no rollback procedure. The rollback is the 4.x environment, which you keep running and receiving events until the 5.x deployment has been validated. That is the main reason the deployment has to be parallel rather than in place.