Watch your servers for attacks with Wazuh and rootless Podman

Wazuh 4.14 on Debian 13, the open-source security monitoring system, with its manager, indexer and dashboard in rootless Podman under a user of its own behind Caddy, and an agent that reports logins, changes and attacks on your servers.

Tested on Wazuh 4.14.8 on Debian 13 (trixie) on a Melonslab server Updated October 14, 2026

Recommended server for this guide

SSD RAID 1 · ECC DDR4 · 10 Gbit

VC-P Titanium · 4 vCPU · 16 GB Memory · 120 GB Storage

Month to month, no lock-in 7-day money-back guarantee

€17.99/mo

Deploy now
On this page

What you will set up

Wazuh watches your servers for signs of attack and for weak settings. A small program on each server, the agent, reads its logs, notices changes to files and users, lists installed software, and checks the server's settings against the CIS benchmark, a public list of security recommendations. It sends everything to the Wazuh manager, which matches it against thousands of rules and raises an alert for failed logins, brute-force attempts, new users, changed system files and more. The alerts go into the Wazuh indexer, a search engine, and you read them in the Wazuh dashboard in your browser.

Wazuh is developed by Wazuh, Inc., a company in the United States, which sells support and a hosted version. It is open source: the manager and the agent under GPLv2, and the indexer and dashboard, which build on OpenSearch, under the Apache 2.0 licence.

Here the manager, indexer and dashboard run in a pod under a user of their own called siem, from Wazuh's official images, behind Caddy from the Podman guide. The agent runs as root on the server it watches, installed from Wazuh's package repository, because it needs to read the system's logs. In this guide the first agent goes on the same server, and the same commands enroll any other server.

Every step below was run on a fresh Melonslab VC-P Titanium (4 vCPU, 16 GB) with Debian 13:

  • Wazuh 4.14.7 started rootless, with its own passwords in place of the published defaults before it was put online. The default logins admin with SecretPassword and kibanaro with kibanaro were refused.
  • An agent on the server enrolled with a password, over IPv6, and showed as active with Debian GNU/Linux 13.
  • Failed SSH logins with an unknown user name gave the alert "sshd: Attempt to login using a non-existent user", and eight of them "sshd: brute force trying to get access to the system", level 10. Adding a user gave "New user added to the system". The CIS benchmark for Debian 13 ran by itself.
  • An email channel sent a test message over STARTTLS with a password, and a monitor emailed every alert of level 10 or higher within five minutes.
  • An update from Wazuh 4.14.7 to 4.14.8 kept the agent, the alerts and the email monitor. Wazuh was restored from a backup under a newly created user, the agent reconnected by itself, and everything came back by itself after a reboot.

The pod used about 2.2 GB of memory: 1.6 GB for the indexer, 400 MB for the manager and 300 MB for the dashboard. On disk, the images take 4.5 GB, and the vulnerability database the manager downloads takes another 10 GB.

Wazuh can also run rootful, as root, which is how its own Docker instructions run it. It works rootless here without changes to the images, so this guide keeps it rootless like the other apps.

Before you start

You need:

  • a server set up as in the Podman guide, with Caddy running, and at least 8 GB of memory;
  • an A record and an AAAA record for wazuh.example.com pointing at your server.

The examples use wazuh.example.com for Wazuh and web1 for the name of the server in Wazuh. Replace them throughout.

Agents connect to the server on ports 1514 and 1515. If you use ufw from the security guide, open them as root:

ufw allow 1514,1515/tcp

1. Create the user

As root:

apt install -y curl
useradd -m -s /bin/bash siem
loginctl enable-linger siem
machinectl shell siem@

Everything up to step 7 runs as siem. The user is not called wazuh, because the agent in step 9 creates a system user with that name, which should stay separate from the one running the containers.

2. Create the certificates

The indexer, the manager and the dashboard talk to each other over TLS, with certificates from a small certificate authority of your own. Wazuh's certificate tool makes them:

mkdir -p ~/wazuh/certs
cd ~/wazuh
cat > certs.yml <<'EOF'
nodes:
  indexer:
    - name: wazuh.indexer
      ip: wazuh.indexer
  server:
    - name: wazuh.manager
      ip: wazuh.manager
  dashboard:
    - name: wazuh.dashboard
      ip: wazuh.dashboard
EOF
podman run --rm -e CERT_TOOL_VERSION=4.14 -v ./certs.yml:/config/certs.yml -v ./certs:/certificates docker.io/wazuh/wazuh-certs-generator:0.0.4

The tool prints find: command not found once, which does no harm, and ends with Setting UID for wazuh manager and worker. The certificates in ~/wazuh/certs now belong to the users inside the containers, so ls works on them but cat does not. The certificate authority's key, root-ca.key, is there too; step 13 backs it up.

3. Create the passwords

openssl rand -hex 16 | tr -d '\n' | podman secret create wazuh-admin-password -
openssl rand -hex 16 | tr -d '\n' | podman secret create wazuh-dashboard-password -
printf '%sAa1.' "$(openssl rand -hex 16)" | podman secret create wazuh-api-password -
openssl rand -hex 16 | tr -d '\n' | podman secret create wazuh-enrollment-password -

They are, in turn: the password of admin, which you log in to the dashboard with; the dashboard's own password for the indexer; the password of wazuh-wui, the user the dashboard reaches the manager with, which must also contain a capital letter, a digit and a symbol, hence Aa1.; and the password agents need to enroll. Wazuh's own instructions ship fixed, published passwords for all of these; here every one is random and stored by Podman, not in the files below.

4. Write the configuration

Download the indexer's and the manager's configuration from Wazuh's repository, for the version you run:

mkdir -p ~/wazuh/config
cd ~/wazuh/config
base=https://raw.githubusercontent.com/wazuh/wazuh-docker/v4.14.8/single-node/config
curl -fsSLo wazuh.indexer.yml $base/wazuh_indexer/wazuh.indexer.yml
curl -fsSLo manager.conf $base/wazuh_cluster/wazuh_manager.conf
sed -i 's|<use_password>no</use_password>|<use_password>yes</use_password>|' manager.conf
sed -i 's|<port>151[45]</port>|&\n    <ipv6>yes</ipv6>|' manager.conf

The two sed lines change the manager in two ways:

  • use_password makes agents give the enrollment password from step 3. Without it, anyone who can reach port 1515 can enroll an agent.
  • ipv6 makes the manager listen on IPv6 as well as IPv4. Without it, agents that reach the server over IPv6, such as one on the server itself, fail with "Connection refused by the manager". With Wazuh's own rootful setup Docker hides this, as it passes IPv6 connections on over IPv4; rootless Podman passes them on as they are.

The indexer keeps its users in internal_users.yml, with each password stored as a hash. Make the hashes with the indexer's own tool, which reads the password from the secret, and write the file:

hash() {
  podman run --rm --secret $1,type=env,target=PW -e JAVA_HOME=/usr/share/wazuh-indexer/jdk \
    docker.io/wazuh/wazuh-indexer:4.14.8 bash plugins/opensearch-security/tools/hash.sh -env PW | tail -1
}
cat > internal_users.yml <<EOF
_meta:
  type: "internalusers"
  config_version: 2
admin:
  hash: "$(hash wazuh-admin-password)"
  reserved: true
  backend_roles:
  - "admin"
kibanaserver:
  hash: "$(hash wazuh-dashboard-password)"
  reserved: true
EOF

Wazuh's own file also has four demo users, among them kibanaro with the password kibanaro, who can read every alert. They are left out here.

Last, the dashboard's settings, opensearch_dashboards.yml:

cat > opensearch_dashboards.yml <<'EOF'
server.host: 0.0.0.0
server.port: 5601
opensearch.hosts: https://wazuh.indexer:9200
opensearch.ssl.verificationMode: certificate
opensearch.ssl.certificateAuthorities: ["/usr/share/wazuh-dashboard/certs/root-ca.pem"]
opensearch.requestHeadersAllowlist: ["securitytenant","Authorization"]
opensearch_security.multitenancy.enabled: false
opensearch_security.readonly_mode.roles: ["kibana_read_only"]
opensearch_security.cookie.secure: true
uiSettings.overrides.defaultRoute: /app/wz-home
EOF

The dashboard serves plain HTTP inside the pod, and Caddy gives it HTTPS. cookie.secure makes the browser send the login cookie over HTTPS only.

5. Define the pod

mkdir -p ~/.config/containers/systemd
cd ~/.config/containers/systemd

Create wazuh.pod:

[Pod]
PodName=wazuh
# Only Caddy, on this server, can reach the dashboard: the port is not open to the internet.
PublishPort=127.0.0.1:8130:5601
# Agents connect here: 1514 for their events, 1515 to enroll
PublishPort=1514:1514
PublishPort=1515:1515
# The names in the certificates all point at the pod itself
AddHost=wazuh.indexer:127.0.0.1
AddHost=wazuh.manager:127.0.0.1

[Install]
WantedBy=default.target

Create wazuh-indexer.container:

[Container]
ContainerName=wazuh-indexer
Image=docker.io/wazuh/wazuh-indexer:4.14.8
Pod=wazuh.pod
Environment="OPENSEARCH_JAVA_OPTS=-Xms1g -Xmx1g"
Ulimit=nofile=65536:65536
Volume=wazuh-indexer:/var/lib/wazuh-indexer
Volume=%h/wazuh/config/wazuh.indexer.yml:/usr/share/wazuh-indexer/config/opensearch.yml:ro
Volume=%h/wazuh/config/internal_users.yml:/usr/share/wazuh-indexer/config/opensearch-security/internal_users.yml:ro
Volume=%h/wazuh/certs/root-ca.pem:/usr/share/wazuh-indexer/config/certs/root-ca.pem:ro
Volume=%h/wazuh/certs/wazuh.indexer.pem:/usr/share/wazuh-indexer/config/certs/wazuh.indexer.pem:ro
Volume=%h/wazuh/certs/wazuh.indexer-key.pem:/usr/share/wazuh-indexer/config/certs/wazuh.indexer.key:ro
Volume=%h/wazuh/certs/admin.pem:/usr/share/wazuh-indexer/config/certs/admin.pem:ro
Volume=%h/wazuh/certs/admin-key.pem:/usr/share/wazuh-indexer/config/certs/admin-key.pem:ro
HealthCmd=curl -sk -o /dev/null https://127.0.0.1:9200
HealthInterval=10s
HealthStartPeriod=2m
Notify=healthy

[Service]
Restart=always
TimeoutStartSec=300

Create wazuh-manager.container:

[Unit]
Requires=wazuh-indexer.service
After=wazuh-indexer.service

[Container]
ContainerName=wazuh-manager
Image=docker.io/wazuh/wazuh-manager:4.14.8
Pod=wazuh.pod
Environment=INDEXER_URL=https://wazuh.indexer:9200 INDEXER_USERNAME=admin
Environment=FILEBEAT_SSL_VERIFICATION_MODE=full SSL_CERTIFICATE_AUTHORITIES=/etc/ssl/root-ca.pem
Environment=SSL_CERTIFICATE=/etc/ssl/filebeat.pem SSL_KEY=/etc/ssl/filebeat.key
Environment=API_USERNAME=wazuh-wui
Secret=wazuh-admin-password,type=env,target=INDEXER_PASSWORD
Secret=wazuh-api-password,type=env,target=API_PASSWORD
Secret=wazuh-enrollment-password,type=mount,target=/var/ossec/etc/authd.pass,uid=0,gid=999,mode=0640
Volume=wazuh-manager-etc:/var/ossec/etc
Volume=wazuh-manager-queue:/var/ossec/queue
Volume=wazuh-manager-logs:/var/ossec/logs
Volume=wazuh-manager-api:/var/ossec/api/configuration
Volume=wazuh-manager-multigroups:/var/ossec/var/multigroups
Volume=wazuh-manager-filebeat:/var/lib/filebeat
Volume=%h/wazuh/config/manager.conf:/wazuh-config-mount/etc/ossec.conf:ro
Volume=%h/wazuh/certs/root-ca-manager.pem:/etc/ssl/root-ca.pem:ro
Volume=%h/wazuh/certs/wazuh.manager.pem:/etc/ssl/filebeat.pem:ro
Volume=%h/wazuh/certs/wazuh.manager-key.pem:/etc/ssl/filebeat.key:ro

[Service]
Restart=always

And wazuh-dashboard.container:

[Unit]
Requires=wazuh-indexer.service
After=wazuh-indexer.service wazuh-manager.service

[Container]
ContainerName=wazuh-dashboard
Image=docker.io/wazuh/wazuh-dashboard:4.14.8
Pod=wazuh.pod
Environment=WAZUH_API_URL=https://wazuh.manager API_USERNAME=wazuh-wui DASHBOARD_USERNAME=kibanaserver
Secret=wazuh-api-password,type=env,target=API_PASSWORD
Secret=wazuh-dashboard-password,type=env,target=DASHBOARD_PASSWORD
Volume=wazuh-dashboard:/usr/share/wazuh-dashboard/data/wazuh/config
Volume=%h/wazuh/config/opensearch_dashboards.yml:/usr/share/wazuh-dashboard/config/opensearch_dashboards.yml:ro
Volume=%h/wazuh/certs/root-ca.pem:/usr/share/wazuh-dashboard/certs/root-ca.pem:ro

[Service]
Restart=always

What the settings do:

  • The three containers share the pod's network, so they reach each other on 127.0.0.1. The AddHost lines give that address the names in the certificates, which the manager checks when it sends alerts to the indexer.
  • Notify=healthy makes systemd wait until the indexer answers before it starts the manager and the dashboard.
  • The indexer gets 1 GB of memory for its Java heap, which suits a handful of agents. Raise both numbers for more.
  • The manager's settings, its agents' keys and its rules are in wazuh-manager-etc, and the alerts as files in wazuh-manager-logs. The manager copies manager.conf over its ossec.conf each time it starts, so change the file in ~/wazuh/config, not inside the container.
  • The enrollment password is mounted as the file the manager reads it from, readable only by its wazuh group, which is 999 in the image.
  • The version is pinned. The manager, indexer and dashboard must be the same version, and agents must not be newer than the manager, so you update them together (step 14) rather than with podman auto-update.

6. Start it

systemctl --user daemon-reload
systemctl --user start wazuh-pod

The first start downloads about 4.5 GB of images, and the dashboard is ready a minute or two after that. It is ready when this prints 200:

curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8130/app/login

In the first hour, the manager also downloads its vulnerability database, about 10 GB on disk once unpacked.

7. Put Caddy in front

Go back to root with exit, switch to machinectl shell caddy@, and add this block at the end of ~/Caddyfile:

wazuh.example.com {
    reverse_proxy 127.0.0.1:8130
}

Restart Caddy with systemctl --user restart caddy.

8. Log in

As siem, show the admin password:

podman secret inspect --showsecret --format '{{.SecretData}}' wazuh-admin-password; echo

Open https://wazuh.example.com and log in as admin with that password. Keep it in your password manager. The first page, Overview, shows the agents and the alerts of the last 24 hours, and links to each part of Wazuh: Configuration Assessment, File Integrity Monitoring, Threat Hunting, Vulnerability Detection and more.

One agent is there from the start, wazuh (server). That is the manager watching itself, inside its container. To watch the server, it needs an agent of its own.

The dashboard is in English. It has no Swedish translation.

9. Enroll an agent

As siem, show the enrollment password:

podman secret inspect --showsecret --format '{{.SecretData}}' wazuh-enrollment-password; echo

Then, as root on the server you want to watch, this one or another, add Wazuh's package repository and install the agent, with the password in place of ENROLLMENT_PASSWORD:

curl -fsSLo /usr/share/keyrings/wazuh.asc https://packages.wazuh.com/key/GPG-KEY-WAZUH
echo "deb [signed-by=/usr/share/keyrings/wazuh.asc] https://packages.wazuh.com/4.x/apt/ stable main" > /etc/apt/sources.list.d/wazuh.list
apt update
WAZUH_MANAGER=wazuh.example.com WAZUH_REGISTRATION_PASSWORD='ENROLLMENT_PASSWORD' WAZUH_AGENT_NAME=web1 apt install -y wazuh-agent=4.14.8-1
apt-mark hold wazuh-agent
systemctl daemon-reload
systemctl enable --now wazuh-agent

The variables go into the agent's configuration when the package installs: the manager's address, the password, which it keeps in /var/ossec/etc/authd.pass, and the name the server gets in Wazuh. apt-mark hold stops apt upgrade from installing an agent newer than your manager, which the manager would not accept.

The agent enrolls on port 1515 and then sends its events on port 1514, encrypted with the key it got. Within a minute, Agents management, Summary in the dashboard menu shows web1 as active, with its operating system. On the server, the agent's log says so too:

grep agentd /var/ossec/logs/ossec.log | tail -3

It ends with Connected to the server ([wazuh.example.com]:1514/tcp). For more servers, run the same commands on each, with a name of its own.

10. See the alerts

To see an alert arrive, try to log in over SSH to the server with a user that does not exist, from any computer:

ssh nosuchuser@wazuh.example.com

Each try gives the alert "sshd: Attempt to login using a non-existent user", level 5, and eight tries in a row give "sshd: brute force trying to get access to the system. Non existent user.", level 10. Wazuh's levels go from 0 to 15, and the dashboard counts 7 to 11 as medium, 12 to 14 as high and 15 as critical.

Open Threat Hunting and Events. Each alert shows the agent, the rule, its level and its ID, and opens with every detail, such as the address the logins came from. The search box takes queries such as rule.level >= 10.

In the first minutes, the agent also runs the CIS benchmark for Debian 13. Configuration Assessment lists each check that passed or failed, with the reason and how to fix it. A fresh Debian 13 scores around 45 %, mostly for logging rules it does not set up by default.

What else the agent watches by default:

  • logins, sudo, and new or deleted users and groups, from the system journal;
  • changes to files in /etc, /bin, /sbin, /boot, /usr/bin and /usr/sbin, checked every 12 hours, under File Integrity Monitoring;
  • installed packages, open ports and running processes, once an hour, which Vulnerability Detection compares with known security holes.

11. Email

Wazuh does not need email to work: without it you read the alerts in the dashboard. The manager's own email alerts cannot log in to a mail server, so they only work through a local mail relay, which this guide does not set up. Instead, the indexer's alerting can email you, and it logs in with a password over STARTTLS on port 587 or TLS on port 465.

As siem, store the SMTP password as a secret, and put it with the user name in the indexer's keystore, a file the indexer keeps secret settings in. mail is the name of the sender you create below, and alerts@example.com your account at your email provider:

printf %s 'the-smtp-password' | podman secret create wazuh-smtp-password -
podman run --rm --user 0 --secret wazuh-smtp-password,type=env,target=PW \
  -v ~/wazuh/config:/conf -e OPENSEARCH_PATH_CONF=/conf --entrypoint bash \
  docker.io/wazuh/wazuh-indexer:4.14.8 -c '
    bin/opensearch-keystore create
    echo alerts@example.com | bin/opensearch-keystore add --stdin opensearch.notifications.core.email.mail.username
    printf %s "$PW" | bin/opensearch-keystore add --stdin opensearch.notifications.core.email.mail.password
    chown 1000:1000 /conf/opensearch.keystore'

Add this line to wazuh-indexer.container, under the other Volume lines, and restart the pod:

Volume=%h/wazuh/config/opensearch.keystore:/usr/share/wazuh-indexer/config/opensearch.keystore
systemctl --user daemon-reload
systemctl --user restart wazuh-pod

Then, in the dashboard, open the menu, Explore, Notifications:

  1. Under Email senders, choose Create SMTP sender. Enter mail as Sender name, your address as Email address, your provider's SMTP server as Host, 587 as Port, and choose STARTTLS as Encryption method, or SSL/TLS for port 465. Choose Create.
  2. Under Channels, choose Create channel. Give it a Name, such as Security alerts, choose Email as Channel type, mail as SMTP sender, and your own address under Default recipients. Send test message sends a test email. Choose Create.

The channel does nothing until something uses it. To get an email for every alert of level 10 or higher, create a monitor that searches for them every five minutes. As siem, find the channel's ID, the config_id of the channel you named:

podman exec wazuh-indexer curl -sk --cert config/certs/admin.pem --key config/certs/admin-key.pem \
  'https://127.0.0.1:9200/_plugins/_notifications/configs?config_type=email'; echo

Save this as ~/wazuh/monitor.json, with the ID in place of CHANNEL_ID:

{
  "type": "monitor",
  "monitor_type": "query_level_monitor",
  "name": "Wazuh alerts of level 10 or higher",
  "enabled": true,
  "schedule": { "period": { "interval": 5, "unit": "MINUTES" } },
  "inputs": [{ "search": {
    "indices": ["wazuh-alerts-*"],
    "query": {
      "size": 10,
      "_source": ["agent.name", "rule.level", "rule.description"],
      "query": { "bool": { "filter": [
        { "range": { "rule.level": { "gte": 10 } } },
        { "range": { "timestamp": { "gte": "{{period_end}}||-5m", "lte": "{{period_end}}", "format": "epoch_millis" } } }
      ] } }
    }
  } }],
  "triggers": [{ "query_level_trigger": {
    "name": "Level 10 or higher",
    "severity": "1",
    "condition": { "script": { "source": "ctx.results[0].hits.total.value > 0", "lang": "painless" } },
    "actions": [{
      "name": "Email",
      "destination_id": "CHANNEL_ID",
      "subject_template": { "source": "Wazuh: {{ctx.results.0.hits.total.value}} alerts of level 10 or higher" },
      "message_template": { "source": "{{#ctx.results.0.hits.hits}}{{_source.agent.name}}: {{_source.rule.description}} (level {{_source.rule.level}})\n{{/ctx.results.0.hits.hits}}" }
    }]
  } }]
}

And create the monitor:

podman exec -i wazuh-indexer curl -sk --cert config/certs/admin.pem --key config/certs/admin-key.pem \
  -H 'Content-Type: application/json' -X POST https://127.0.0.1:9200/_plugins/_alerting/monitors --data-binary @- < ~/wazuh/monitor.json; echo

The admin certificate from step 2 lets these commands in without a password. The monitor shows under Explore, Alerting, where you can change it. After eight failed SSH logins, as in step 10, an email arrived within five minutes with the subject "Wazuh: 1 alerts of level 10 or higher" and the line web1: sshd: brute force trying to get access to the system. Non existent user. (level 10). We tested with a test mail server on the same server, not with an email provider.

12. Outside connections

In our tests, the server made two kinds of outside connections, both from the manager:

  • to cti.wazuh.com, Wazuh's threat intelligence service, for the vulnerability database and for news of new Wazuh versions, which the dashboard shows;
  • to cdn.amazonlinux.com, right after the vulnerability scanner started. The manager's image is built on Amazon Linux.

The agents connect only to your manager.

13. Back up

As siem:

umask 077
mkdir -p ~/backup
systemctl --user stop wazuh-pod
cd ~/.local/share/containers/storage/volumes
podman unshare tar -cf ~/backup/wazuh-volumes.tar --exclude='*/_data/vd' --exclude='*/_data/vd_updater' wazuh-*
systemctl --user start wazuh-pod
podman unshare tar -C ~ -cf ~/backup/wazuh-files.tar wazuh .config/containers/systemd
for s in $(podman secret ls --format '{{.Name}}' | grep '^wazuh-'); do
  echo "$s $(podman secret inspect --showsecret --format '{{.SecretData}}' $s)"
done > ~/backup/secrets.txt

That saves all volumes, which hold the agents' keys, the alerts, the settings and the email monitor; the certificates, the configuration and the unit files; and the passwords. The vulnerability database is left out, as the manager downloads it again. Wazuh is stopped for about a minute while the volumes are copied; agents keep their events meanwhile and send them when it is back. podman unshare lets you read files that belong to the users inside the containers, and tar lists the sockets it skips, which Wazuh makes again when it starts. Copy ~/backup to another machine, for example with restic, and keep it safe: it holds every password, the certificate authority's key and the history of your servers.

To restore, as siem, with the backup in ~/backup:

systemctl --user stop wazuh-pod
podman volume rm $(podman volume ls -q | grep '^wazuh-')
cd ~/.local/share/containers/storage/volumes
for v in $(tar -tf ~/backup/wazuh-volumes.tar | cut -d/ -f1 | sort -u); do podman volume create $v; done
podman unshare tar -xpf ~/backup/wazuh-volumes.tar
systemctl --user start wazuh-pod

On a new server, first create the user as in step 1, and, as siem, bring back the passwords and files:

while read -r name value; do printf %s "$value" | podman secret create "$name" -; done < ~/backup/secrets.txt
podman unshare tar -C ~ -xpf ~/backup/wazuh-files.tar
systemctl --user daemon-reload

Then run the restore commands above from the cd line on, as there are no volumes to stop or remove yet. The agents keep working with the restored manager, as their keys are in the backup.

14. Update

Wazuh announces new versions on its blog and in the dashboard. Read the release notes, back up as in step 13, and change the version in the three files, here from 4.14.8 to 4.14.9. As siem:

cd ~/.config/containers/systemd
sed -i 's/:4\.14\.8$/:4.14.9/' wazuh-indexer.container wazuh-manager.container wazuh-dashboard.container
systemctl --user daemon-reload
systemctl --user restart wazuh-pod

Then update the agent on each server, as root:

apt-mark unhold wazuh-agent
apt update
apt install -y wazuh-agent=4.14.9-1
apt-mark hold wazuh-agent

When a new major version such as 5.0 comes out, read its upgrade guide first: it can change the configuration files from step 4.

Troubleshooting

The agent's log says "Connection refused by the manager". The <ipv6>yes</ipv6> lines from step 4 are missing from manager.conf, and the agent reaches the server over IPv6. Add them and restart with systemctl --user restart wazuh-manager.

The agent's log says "Invalid password" or the enrollment fails. The password in WAZUH_REGISTRATION_PASSWORD differs from the secret. Put the right one in /var/ossec/etc/authd.pass on the agent and run systemctl restart wazuh-agent.

https://wazuh.example.com answers with an empty page and error 502. The dashboard is not running. The manager and the dashboard need the indexer, so they stop when it stops or fails. Check with systemctl --user status wazuh-indexer, read journalctl --user -u wazuh-indexer, fix the cause, and run systemctl --user reset-failed and systemctl --user restart wazuh-pod.

The indexer does not start after step 11, with AccessDeniedException on opensearch.keystore. The chown at the end of the keystore command is missing. Run podman unshare chown 1000:1000 ~/wazuh/config/opensearch.keystore.

cat or cp says "Permission denied" for the files in ~/wazuh/certs. They belong to the users inside the containers. Put podman unshare in front of the command.

Run it on your own server

SSD RAID 1 · ECC DDR4 · 10 Gbit

VC-P Titanium

€17.99/mo

vCPU
4
Memory
16 GB
Storage
120 GB
Transfer
15 TB
Performance
SSD · RAID 1
  • Full root access
  • Native /64 IPv6
  • RAID-protected storage
  • Malmö, Sweden
  • Month to month, no lock-in
  • 7-day money-back guarantee
All guides