Never Lose Your Wazuh Alert Data Again: Automated Snapshots with MinIO

Mulayam Yadav
By Mulayam YadavAug 23, 202610 Min Read

Introduction

Wazuh index data contains valuable security information, including alerts, vulnerability findings, and compliance results. Protecting this data is essential to ensure it can be recovered following accidental deletion, index corruption, or infrastructure failure.

The Wazuh indexer supports snapshot management through its underlying OpenSearch capabilities, allowing snapshots to be stored in external repositories. While the Wazuh documentation demonstrates using an NFS shared filesystem as a snapshot repository, organizations may prefer an S3-compatible object storage solution such as MinIO for environments where shared storage is unavailable or undesirable.

In this blog post, we demonstrate how to configure MinIO as a self-hosted S3-compatible snapshot repository for the Wazuh indexer. We also show how to automate snapshot creation and retention using Snapshot Management and how to restore index data from a snapshot.

NFS or MinIO - What Works for You

Wazuh documents NFS as a snapshot repository and it does the job well. If NFS is already part of your infrastructure, there is no reason to move away from it. Both approaches support automated snapshot policies through the same Snapshot Management interface in the Wazuh Dashboard — the scheduler, retention rules, and restore workflow are identical regardless of which repository type you use.

MinIO is simply another option that some environments fit better. It uses the S3 API over HTTP instead of a shared filesystem mount, which means no path.repo change in opensearch.yml, no per-node mount configuration, and no dependency on a network filesystem staying healthy. If the MinIO endpoint is unreachable, the snapshot fails with a visible connection error rather than silently writing nothing. And because it speaks S3, the same repository configuration works against AWS S3 or any other S3-compatible storage by changing the endpoint — useful if your environment might move to cloud storage later.

That said, if NFS works for you, keep using it. This guide just uses MinIO as the repository because it fits the two-server lab setup cleanly and demonstrates an approach that is not yet covered in the official Wazuh documentation.

One thing worth knowing before deploying MinIO in production: the open-source community edition was archived in April 2026 and is no longer actively maintained. For a long-term production setup, Garage or SeaweedFS are solid S3-compatible alternatives actively maintained, fully open-source, and the Wazuh repository configuration works identically with both.

Lab Architecture

This setup requires two servers both can be Ubuntu 22.04 LTS virtual machines or cloud instances.

Server

Role

Minimum Specs

Server 1 — 192.168.1.10

Wazuh All-in-One (Manager + Indexer + Dashboard)

4 vCPU · 8 GB RAM · 50 GB disk · Ubuntu 22.04

Server 2 — 192.168.1.20

MinIO S3-compatible object storage

2 vCPU · 4 GB RAM · 100 GB disk · Ubuntu 22.04

Note: Both servers must be able to reach each other on port 9000 (MinIO S3 API). Verify connectivity before starting: nc -zv 192.168.1.20 9000 

Part 1 Install and Configure MinIO on Server 2

Step 1 Create a dedicated system user

MinIO should never run as root. Create a system user with no login shell:

sudo useradd -r minio-user -s /sbin/nologin

Step 2 Create the data directory

sudo mkdir -p /mnt/minio/data
sudo chown -R minio-user:minio-user /mnt/minio
sudo chmod -R 750 /mnt/minio

Step 3 Download and install the MinIO binary

curl -o minio https://dl.min.io/server/minio/release/linux-amd64/minio
chmod +x minio
sudo mv minio /usr/local/bin/
minio --version

Step 4 Create the MinIO environment configuration file

sudo tee /etc/default/minio > /dev/null <<EOF
 MINIO_VOLUMES="/mnt/minio/data"
 MINIO_ROOT_USER="minioadmin"
 MINIO_ROOT_PASSWORD="ChangeThisPassword123!"
 MINIO_OPTS="--address 192.168.1.20:9000 --console-address 192.168.1.20:9001"
 EOF

Note: Replace ChangeThisPassword123! with a strong password. These credentials will be stored in the Wazuh Indexer keystore in Part 3.

Step 5 Create the MinIO systemd service

sudo tee /etc/systemd/system/minio.service > /dev/null <<EOF
[Unit]
Description=MinIO Object Storage
Wants=network-online.target
After=network-online.target
AssertFileIsExecutable=/usr/local/bin/minio

[Service]
WorkingDirectory=/mnt/minio/data
User=minio-user
Group=minio-user
EnvironmentFile=/etc/default/minio
ExecStart=/usr/local/bin/minio server $MINIO_OPTS $MINIO_VOLUMES
Restart=always
LimitNOFILE=65536
TimeoutStopSec=infinity
SendSIGKILL=no

[Install]
WantedBy=multi-user.target
EOF

Step 6 Start and enable MinIO

sudo systemctl daemon-reload
sudo systemctl enable minio
sudo systemctl start minio
sudo systemctl status minio
1.png


Step 7 Create the snapshot bucket

Open the MinIO console at http://192.168.1.20:9001. Log in with your credentials, navigate to Buckets → Create Bucket, name it wazuh-snapshots, and click Create Bucket.

Alternatively, use the MinIO client (mc) from the command line:

# Install mc on Server 2
curl -o mc https://dl.min.io/client/mc/release/linux-amd64/mc
chmod +x mc
sudo mv mc /usr/local/bin/

# Configure and create bucket
mc alias set local http://192.168.1.20:9000 minioadmin ChangeThisPassword123!
mc mb local/wazuh-snapshots
mc ls local/
2.png

Part 2 Install the repository-s3 Plugin on the Wazuh Indexer

Step 1  Install the repository-s3 plugin

The repository-s3 plugin allows the Wazuh Indexer to communicate with any S3-compatible storage including MinIO.
/usr/share/wazuh-indexer/bin/opensearch-plugin install repository-s3
When prompted with the permissions warning, type y and press Enter. Verify the plugin installed:
/usr/share/wazuh-indexer/bin/opensearch-plugin list | grep repository-s3

Note: Use the Wazuh Indexer plugin path /usr/share/wazuh-indexer/bin/opensearch-plugin — not the standard OpenSearch path. These are different binaries.

Step 2 Store MinIO credentials in the Wazuh Indexer keystore

The Wazuh Indexer uses a secure keystore to store S3 credentials. These commands prompt for values without echoing them on screen.
without echoing them on screen.
# Add the MinIO access key
echo "minioadmin" | sudo /usr/share/wazuh-indexer/bin/opensearch-keystore add --stdin s3.client.default.access_key

# Add the MinIO secret key
echo "ChangeThisPassword123!" | sudo /usr/share/wazuh-indexer/bin/opensearch-keystore add --stdin s3.client.default.secret_key

Verify the keystore permissions:
ls -la /etc/wazuh-indexer/opensearch.keystore

Step 3 Restart the Wazuh Indexer

sudo systemctl restart wazuh-indexer

# Wait 30 seconds then verify cluster health
curl -sk -u admin:<YOUR_ADMIN_PASSWORD> \
 https://192.168.1.10:9200/_cluster/health?pretty

Note: The status field should show green or yellow after restart. Red means unassigned shards — investigate before proceeding.

Part 3 Register the MinIO Repository

Step 1 Register the repository via the API

curl -sk -u admin:<YOUR_ADMIN_PASSWORD> \
  -X PUT "https://192.168.1.10:9200/_snapshot/wazuh-minio-repo?pretty" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "s3",
    "settings": {
      "bucket": "wazuh-snapshots",
      "endpoint": "192.168.1.20:9000",
      "protocol": "http",
     "path_style_access": "true",
      "client": "default"
    }
  }'
A successful response returns: { "acknowledged": true }

Note: path_style_access: true is required for MinIO. Without it, the indexer tries virtual-hosted-style URLs that do not work with self-hosted S3-compatible storage.

Step 2 Take a manual test snapshot

curl -sk -u admin:<YOUR_ADMIN_PASSWORD> \
  -X PUT "https://192.168.1.10:9200/_snapshot/wazuh-minio-repo/test-snapshot-01?wait_for_completion=true&pretty" \
  -H "Content-Type: application/json" \
  -d '{
    "indices": "wazuh-alerts-*",
   "ignore_unavailable": true,
   "include_global_state": false
  }'
A successful response includes "state": "SUCCESS" and lists all snapshotted indices. Verify the files landed in MinIO:
mc ls local/wazuh-snapshots/
3.png

Part 4 Create a Snapshot Management Policy in the Wazuh Dashboard

Instead of manually triggering snapshots, a Snapshot Management (SM) policy runs on a schedule and automatically manages retention. Everything in this part is done through the Wazuh Dashboard no command line required.

Step 1 Navigate to Snapshot Management

In the Wazuh Dashboard: ☰ (Menu) → Indexer management → Snapshot Management

4.png

Step 2 Create a new policy

Click Create policy and fill in:

  • Policy name: wazuh-daily-snapshot
  • Description: Daily automated snapshot of Wazuh alert indices to MinIO
5.png

Step 3 Configure the snapshot source

Under Source, set Indices to: wazuh-alerts-*,wazuh-archives-*

This pattern captures all daily alert indices and archive indices. Add any custom Wazuh indices here separated by commas.

6.png

Step 4 Configure the snapshot destination

Under Destination:

  • Repository: wazuh-minio-repo

  • Snapshot name prefix: wazuh-daily-

The policy appends the date and sequence number automatically, producing names like wazuh-daily-2026-07-03-1.

Step 5 Set the schedule

Under Schedule, toggle to Custom cron expression and enter:

0 2 * * *

This fires every day at 2:00 AM. Select your local timezone from the dropdown.

7.png

Step 6 Configure retention

Under Retention:

  • Max count: 30 (keep 30 days of daily backups)

  • Min count: 7 (never delete below 7 snapshots)

  • Max age: 30d

8.png

Step 7 Review and create

Review the full policy configuration and click Create. The policy list will show wazuh-daily-snapshot with status Enabled and the next scheduled run time.

9.png

Part 5 Fixing Snapshot Restore Permissions (Security Plugin)

When attempting a restore, you may encounter a 403 security_exception even when using the admin user. This is a known behavior in the Wazuh Indexer security plugin — the admin backend role maps to all_access but does not automatically inherit snapshot restore permissions from the reserved manage_snapshots role.

The fix is to create a custom role with the required cluster permissions and map the admin user to it.

Step 1 Create a custom snapshot restore role

curl -sk -u admin:<YOUR_ADMIN_PASSWORD> \
   -X PUT "https://localhost:9200/_plugins/_security/api/roles/custom_snapshot_restore?pretty" \
   -H "Content-Type: application/json" \
   -d '{
            "cluster_permissions": [
      "cluster:admin/snapshot/restore",
      "cluster:admin/snapshot/get",
      "cluster:admin/snapshot/create",
      "cluster:admin/snapshot/status",
      "cluster:admin/repository/get",
      "cluster:admin/repository/put"
            ],
            "index_permissions": [
            {
           "index_patterns": ["*"],
           "allowed_actions": [
          "indices:admin/create",
          "indices:data/write/index",
          "indices:admin/mapping/put"
           ]
            }
            ]
   }'
Expected response: { "status": "CREATED", "message": "'custom_snapshot_restore' created." }

Step 2 Map admin user to the custom role

curl -sk -u admin:<YOUR_ADMIN_PASSWORD> \
  -X PUT "https://localhost:9200/_plugins/_security/api/rolesmapping/custom_snapshot_restore?pretty" \
  -H "Content-Type: application/json" \
  -d '{
    "backend_roles": ["admin"],
    "hosts": [],
    "users": ["admin"]
  }'
Expected response: { "status": "CREATED", "message": "'custom_snapshot_restore' created." }

Note: The manage_snapshots role is reserved in Wazuh Indexer and cannot be modified via PUT or PATCH. Creating a custom role is the correct approach — do not attempt to modify reserved roles.

Part 6 Restoring From a Snapshot

Knowing how to restore is as important as knowing how to create backups. This section covers both full restore and targeted single-index recovery both tested in this lab.

Restore a specific index from a snapshot

curl -sk -u admin:<YOUR_ADMIN_PASSWORD> \
  -X POST "https://localhost:9200/_snapshot/wazuh-minio-repo/test-snapshot-01/_restore?pretty" \
  -H "Content-Type: application/json" \
  -d '{
    "indices": "wazuh-alerts-4.x-2026.07.02",
   "ignore_unavailable": true,
   "include_global_state": false,
    "rename_pattern": "(.+)",
   "rename_replacement": "restored-$1"
  }'

Expected response: { "accepted": true }

Note: The rename_pattern and rename_replacement fields are required. They prefix the restored index with restored- to avoid conflicts with any existing index of the same name. An index cannot be restored into a name already in use.

Check restore progress

curl -sk -u admin:<YOUR_ADMIN_PASSWORD> \
 "https://localhost:9200/_cat/recovery/restored-wazuh-alerts-4.x-2026.07.02?v&pretty"

Verify the restored index

curl -sk -u admin:<YOUR_ADMIN_PASSWORD> \
 "https://localhost:9200/_cat/indices/restored-wazuh-alerts-4.x-2026.07.02?v&pretty"

Making restored data visible in Threat Hunting

The restored index is named restored-wazuh-alerts-4.x-2026.07.02 — it does not match the wazuh-alerts-* dashboard pattern. To make it searchable in Threat Hunting, reindex the restored data back into the original index name:

curl -sk -u admin:<YOUR_ADMIN_PASSWORD> \
  -X POST "https://localhost:9200/_reindex?pretty" \
  -H "Content-Type: application/json" \
  -d '{
   "source": {
     "index": "restored-wazuh-alerts-4.x-2026.07.02"
   },
   "dest": {
     "index": "wazuh-alerts-4.x-2026.07.02"
    }
  }'

After reindexing completes, open Threat Hunting in the Wazuh Dashboard and filter by date the alerts from that date will appear exactly as before.

10.png

Conclusion

The question that started all of this "If the Wazuh Indexer disk dies tonight, how much alert history do we lose?" now has a real answer: nothing, as long as the last snapshot ran.

That shift matters more than it sounds. Most teams spend enormous energy making Wazuh collect the right data and catch the right threats. Very few think about what happens to that data when something goes wrong at the infrastructure level. A corrupted index, an accidental deletion, a disk failure during an upgrade these are not edge cases. 

Mulayam Yadav
Mulayam YadavSOC Analyst L3
linkedin

SOC Analyst with expertise in incident monitoring, deploying SIEM & SOAR solutions for customers. Holds a Bachelor’s degree in Computer Science from Dr. A. P. J. Abdul Kalam Technical University (AKTU). Currently performing R&D on integrating AI into SOC eco-system.

Share

Share to Microsoft Teams

Related security services

Running this in production?

Certbar's SOC team deploys, tunes, and monitors Wazuh and your full detection stack 24/7 — so your engineers ship while we watch the alerts.