Wazuh fix note · Wazuh 5.0 release candidate

Wazuh 5.0 events and alerts appear about 20 seconds late

What you see: a new event takes up to about 20 seconds to become searchable, and new events show up in batches about 20 seconds apart. Why: analysisd sends events to the indexer in bulk requests on a timer. The setting: the internal option analysisd.indexer_flush_interval, compiled default 20 seconds, accepted range 1 to 3600. Set it to 5 and the cycle became about 5 seconds; set it to 40 and it became about 40 seconds.

Dong Nguyen, ATK New Technology · 15 October 2026 · measured on Wazuh 5.0.0-rc1 (commit aff7a0b), our test containers · updated 15 October 2026 with load tests

5.0.0-rc1 is a release candidate, not the 5.0.0 GA release. The option name, its default and the behaviour described here may change in GA.

Moving custom rules to Wazuh 5.0? Email your 4.x rule and decoder files and your Wazuh version to support@atkvn.com. We reply within 24 hours with what we find. Free. Remove hostnames, IPs and usernames first. Rather not send them? Run the free Wazuh 5.0 migration check in your browser; nothing is uploaded.


The same note as a video · 1 min 47 sEnglish captionsmeasured on Wazuh 5.0.0-rc1

The video was recorded before the load tests. It does not cover them; see Under load.

How to check on your own lab

All three checks are read-only. The paths are the ones in the 5.0.0-rc1 manager container we used.

1. Is anything overriding the default?

grep -v '^#' /var/wazuh-manager/etc/wazuh-manager-internal-options.conf

If this prints nothing (or only blank lines), the compiled defaults apply. That is how the file ships in 5.0.0-rc1: it holds only comments, including “Defaults are compiled into the manager binaries; only add entries here when you need to override a specific value.” If the manager runs in a container, also look for WAZUH_INDEXER_FLUSH_INTERVAL in the container's environment. The binary contains that name; we did not test whether setting it changes the interval.

2. Does your analysisd binary have the option?

strings /var/wazuh-manager/bin/wazuh-manager-analysisd \
  | grep -E 'indexer_(flush_interval|bulk_max_bytes)|INDEXER_(FLUSH_INTERVAL|BULK_MAX_BYTES)'

Our copy of the 5.0.0-rc1 binary contains these strings:

analysisd.indexer_bulk_max_bytes must be between {} and {} (got {})
analysisd.indexer_flush_interval must be between 1 and {} (got {})
analysisd.indexer_flush_interval
analysisd.indexer_bulk_max_bytes
WAZUH_INDEXER_BULK_MAX_BYTES
WAZUH_INDEXER_FLUSH_INTERVAL

This shows that the option exists, not its default. We read the default of 20 seconds from the machine code next to the option name, not from documentation; the measured cycle matches it.

3. How long does one event take to become searchable?

Write one log line that an agent already reads, then poll the indexer until the line is there. This is the same method as our test script, cut down to one event. Run it on a test install.

# /var/log/lab-auth.log must already exist and be in the agent's <localfile> list
# (log_format syslog) before you write to it.
IP=203.0.113.200            # a test IP that is not in your data; use a new one for each try
IDX=http://localhost:9200   # your indexer. Ours had the security plugin off: plain HTTP, no login.
CURL="curl -s"              # normal install: CURL="curl -sk -u admin:YOUR_PASSWORD" and IDX=https://...
t0=$(date +%s.%N)
echo "$(date '+%b %e %H:%M:%S') $(hostname) sshd[4900]: Failed password for invalid user root from $IP port 55000 ssh2" >> /var/log/lab-auth.log
until $CURL -X POST "$IDX/wazuh-events-v5-*/_count" -H 'Content-Type: application/json' \
      -d "{\"query\":{\"term\":{\"source.ip\":\"$IP\"}}}" | grep -q '"count":[1-9]'; do sleep 0.3; done
awk -v a="$t0" -v b="$(date +%s.%N)" 'BEGIN{printf "searchable after %.1f s\n", b-a}'

One try gives one number, anywhere from a couple of seconds to a little over the interval, depending on where the event lands relative to the timer. Repeat it a few times with a new IP. With the default, our longest wait was 20.4 seconds in the one-at-a-time test and 21.8 seconds in the load tests. If the loop never returns, the event was not indexed: in our run, a line written before the agent had opened the file was never indexed.

Why it happens

In 5.0.0-rc1, analysisd sends events to the indexer through its indexer connector. It does not send each event as it arrives. It collects them and sends them in a bulk request when a timer fires. The timer is analysisd.indexer_flush_interval, in seconds. It also sends early when the batch reaches the size limit analysisd.indexer_bulk_max_bytes; in our tests that happened at 500 events per second, not at 200 or below (see Under load). Until the bulk request is sent, the event cannot be found in the index.

So the delay is not constant. An event that arrives just before a flush becomes searchable soon after it (our shortest wait was 2.2 seconds); an event that arrives just after one waits a full interval. With the default, new events appear about every 20 seconds, in batches.

internal optionenvironment variable in the binarydefault (read from the binary)accepted rangetested
analysisd.indexer_flush_intervalWAZUH_INDEXER_FLUSH_INTERVAL20 seconds1 to 3600yes: 5, 40, default, 0
analysisd.indexer_bulk_max_bytesWAZUH_INDEXER_BULK_MAX_BYTES8,388,608 bytes (8 MiB)65,536 to 104,857,600 bytes (read from the binary)under load only: 65,536, one run each at 200 and 500 events/s

The range 1 to 3600 is also what analysisd says when the value is out of range. We set the interval to 0 and restarted. analysisd refused to start and logged:

wazuh-manager-analysisd: ERROR: An error occurred while initializing the modules: Could not initialize Indexer Connector: analysisd.indexer_flush_interval must be between 1 and 3600 (got 0).

In the same restart, the status command reported wazuh-manager-remoted, wazuh-manager-modulesd and wazuh-manager-clusterd as not running as well. A bad value does not fall back to the default; it stops the manager from processing events. Check the status after every change.

How to change it

Add one line to the internal options file and restart the manager. As root, on the manager. Remove any earlier line for the same option first, so the file holds only one:

F=/var/wazuh-manager/etc/wazuh-manager-internal-options.conf
sed -i '/^analysisd\.indexer_flush_interval/d' $F
echo 'analysisd.indexer_flush_interval=5' >> $F
/var/wazuh-manager/bin/wazuh-manager-control restart
/var/wazuh-manager/bin/wazuh-manager-control status

After a good restart, status lists wazuh-manager-analysisd is running..., and the manager log has these two lines:

grep -E "Indexer Connector initialized|Raw Event Indexer initialized" /var/wazuh-manager/logs/wazuh-manager.log | tail -2

The file's own header says it “will not be overwritten during upgrades”. To go back to the default, delete the line and restart; in our test the cycle returned to about 20 seconds. wazuh-manager-control restart is the command we used inside the container. We did not test other ways of restarting the service.

What we measured

Wazuh 5.0.0-rc1 (commit aff7a0b), our test containers, 15 October 2026. Two paths into the manager:

Each run sent 12 events, one at a time. For each event we timed from sending it until a _count query on wazuh-events-v5-* first found it, polling every 0.3 seconds. After each event became searchable, the script waited 3.7 × (n mod 4) seconds before sending the next. Times are in seconds.

pathflush intervalrunshortest waitlongest waitcycle
socketdefault (no override)18.820.3~20 s (events became searchable 19.8 to 20.3 s apart)
socket512.26.6~5 s
socket522.66.3~5 s
socket532.56.3~5 s
socket40112.739.9~40 s (39.8 to 40.3 s apart)
socketoverride removed (default again)18.620.3~20 s (19.7 to 20.3 s apart)
agent 4.14.7default (no override)12.320.4~20 s (19.7 to 20.4 s apart)
agent 4.14.7512.47.1~5 s

With 5 seconds, the gaps between events were not all 5 seconds, because our script waited different times between events. The cycle shows in where the events landed: in every 5-second run, each event became searchable at nearly the same point of the 5-second cycle, all within about 1.4 seconds of each other.

The agent path behaved like the socket path. At this load (one log line at a time, an idle agent) the 4.14.7 agent added no delay we could see on top of the flush cycle.

The shortest waits are not a typical value. They depend on where each event landed relative to the timer, and our sending rhythm locked onto the cycle. Read the table for the cycle and the longest wait, not for an average.

Under load (added 15 October 2026, after the video)

Wazuh 5.0.0-rc1, our test containers, 15 October 2026: one manager and one indexer node (OpenSearch 3.6.0, 3 GB heap, security plugin off). These tests answer three questions the first runs left open: how often the events index refreshes, what a 5-second flush costs when events arrive steadily, and whether analysisd.indexer_bulk_max_bytes sends a bulk before the timer.

How we loaded it. A generator sent sshd “Failed password” lines straight into the manager's local ingest socket, one batch every 0.1 seconds for 115 seconds, at 50, 200 or 500 events per second (it reached 50.0, 200.2 and 500.4). It did not go through agents. While it ran, a second script sent 16 marked events 1.9 seconds apart, without waiting for each to appear, and searched for them every 0.25 seconds. Over a 75-second window in each run we counted the bulk requests the indexer received, the bytes the manager container sent on its network interface, the CPU time of analysisd, and docker stats for the containers. Each load rate at each flush setting ran twice; the runs without load and the bulk-size runs ran once. Because the probe rhythm differs from the table above, compare longest waits and cycles with it, not shortest waits.

A. How often the events index refreshes

Our test event landed in the data stream wazuh-events-v5-system-activity. Its backing index has refresh_interval set to 2s, explicitly, from the index template wazuh-events-v5-system-activity-template; it is not left to the OpenSearch default. All eight wazuh-events-v5-* and all eight wazuh-findings-v5-* backing indices had 2s; wazuh-events-raw-v5, wazuh-active-responses and the three wazuh-metrics-* streams had 5s. So after a bulk arrives, an event can take up to about 2 more seconds to become searchable. Because the value is set explicitly, OpenSearch's documented search-idle pause (index.search.idle.after, 30s by default here) should not hold back refreshes on a quiet index. We did not test that.

B. What a 5-second flush costs under load

Two runs per row, shown as first / second. Bulk requests per second are what the indexer received over the window. Bytes per bulk are what the manager container sent on its network interface, divided by the number of bulks; we only recorded them from the sixth run on.

events/sflushbulk requests/sevents per bulkbytes per bulkanalysisd CPU (% of one core)longest wait (s)indexed / sent
5020 s (default)0.053 / 0.0531,004 / 1,002not recorded5.2 / 5.120.7 / 21.85,755 / 5,755, both runs
505 s0.196 / 0.198252 / 252302,246 / 302,7706.4 / 5.26.6 / 7.05,755 / 5,755, both runs
20020 s (default)0.053 / 0.0523,030* / 4,045not recorded / 4,790,71313.3 / 14.719.4 / 21.823,020 / 23,020, both runs
2005 s0.196 / 0.1971,016 / 9491,211,998 / 1,232,93213.1 / 11.46.8 / 6.623,020 / 23,020, both runs
50020 s (default)0.066 / 0.0667,098 / 7,098not recorded / 8,410,76926.1 / 23.714.1 / 14.557,550 / 57,550, both runs
5005 s0.197 / 0.1852,577 / 2,7613,075,976 / 3,104,38623.4 / 22.06.3 / 6.857,550 / 57,550, both runs

* The 75-second window cut across the 20-second cycle in this run, so it undercounts; all 23,020 events were indexed in the end. At 20 seconds the window holds only 4 or 5 bulks, so those bulk rates can be off by one bulk. Without load (one run each), the default sent 2 bulk requests in the window and the longest wait was 20.9 s; the 5-second setting sent 7, each 5,828 bytes, and the longest wait was 6.3 s.

Indexer write blocks. During the session the indexer's Security Analytics plugin logged 12 times “Events ingestion backpressure: Applying write block on 'wazuh-events-v5-*' (correlation backlog 10000/10000)”, and lifted each block 0.4 to 1.2 seconds later (11 blocks; the 12th fell between runs, so we have only its start). Around several of them it also logged that it was dropping correlation and enrichment for findings. Blocks came at both flush settings and at 50, 200 and 500 events/s, always 16 to 21 seconds after an even-numbered minute, not in step with the flush timer; we did not establish what triggers them. Our load was one event type that matches a detection rule, which may push the correlation queue harder than a mixed real stream. At 5 and 20 seconds, the manager logged no indexer warnings and every event was indexed.

C. Does analysisd.indexer_bulk_max_bytes send early? Yes (one run per rate)

We set analysisd.indexer_bulk_max_bytes=65536, the minimum, and left the flush interval at the default 20 seconds. analysisd started normally.

events/sbulk requests/sevents per bulkbytes per bulkanalysisd CPUmedian / longest wait (s)indexed / sent
2003.58 (270 in the window)5667,06112.2%1.1 / 2.323,020 / 23,020
5008.99 (684 in the window)5766,96925.8%1.5 / 2.257,438 / 57,550

Limits of the load tests. The runs without load and both bulk-size runs ran once; each load rate at 5 and 20 seconds ran twice. One event type, one simulated agent, sent through the local ingest socket rather than through agents and remoted; a real mix has different document sizes and a different share of events that match rules. We stopped at 500 events per second and did not try a 1-second flush. Bytes per bulk come from the manager container's whole network interface, so they include a little other traffic. CPU from docker stats is sampled about every 2 seconds and is noisy, and the indexer ran its own periodic work. All containers ran on one machine.

Trade-offs

Limits of what we measured

The one-at-a-time tests ran on Wazuh 5.0.0-rc1 (commit aff7a0b) in our test containers; the load tests ran on Wazuh 5.0.0-rc1, our test containers (images tagged 5.0.0-rc1); their notes do not record the commit. Both used one manager and one indexer node, the indexer with a 3 GB heap and the security plugin off. 5.0.0-rc1 is a release candidate, not GA; behaviour may change in GA. The default of 20 seconds and the 8 MiB default of analysisd.indexer_bulk_max_bytes were read from the binary, not from documentation. In the one-at-a-time tests our small events never came near 8 MiB. Under load we set analysisd.indexer_bulk_max_bytes only to its minimum, 65,536, once at 200 and once at 500 events per second; we did not try values in between. That the default bulks at 500 events per second were cut at 8 MiB is an inference from bulk sizes, event counts and timing, not from a log line. We did not test setting either option through its environment variable. The 40-second setting and each agent setting ran once; the 5-second socket setting ran three times. The waits come from a fixed sending rhythm that locks onto the cycle, so they are not a real-world average delay. In most runs the longest wait was a little above the interval (by 0.3 to 0.4 seconds at 20, by 1.3 to 2.1 seconds at 5); we did not separate where that extra time goes. The load tests later showed that the events indices refresh every 2 seconds and that, with bulks sent many times a second, the longest wait was 2.2 to 2.3 seconds. That fits refresh plus our polling making up most of the extra time, but we did not measure it directly. The agent ran at one event at a time; we did not try the agent's own batching settings, and we do not know how the agent path behaves under heavy load: the load tests sent events through the local socket, not through agents. During the first runs the manager was still downloading its initial vulnerability feed; the default runs before and after that gave the same cycle. We did not measure clusters or more than one indexer node. Indexer load was measured only roughly, under load: container CPU sampled about every 2 seconds and heap read three times per run. In the load tests, the runs without load and the bulk-size runs ran once; see the limits at the end of Under load.

Free check

Check your whole manager for silent rules: Rule Doctor Lite (free, read-only, one Python file).

Lite sorts each custom rule that did not fire into DROPPED-AT-LOAD, SHADOW-CANDIDATE, NEVER-REACHES-MANAGER or NO-MATCH. We have not run Lite on 5.0.0-rc1; for rule files you plan to move to 5.0, use the Wazuh 5.0 migration check.

curl -sLO https://github.com/xuxu298/rule-doctor-lite/releases/latest/download/rule-doctor-lite.py
sudo python3 rule-doctor-lite.py