June 24, 2026
Wazuh Ansible Series Part 10: Automating Wazuh Indexer Backups to Google Cloud Storage
How I backed up Wazuh index data to Google Cloud Storage, kept snapshots for 9 days, and trimmed live index data to 7 days with three smallβ¦

By Rio Wiraldhani
5 min read
How I backed up Wazuh index data to Google Cloud Storage, kept snapshots for 9 days, and trimmed live index data to 7 days with three small Ansible roles.
In this lab, the Wazuh all-in-one node runs on Google Compute Engine using an ARM instance, with daily operations handled from Google Cloud Console.
Because the deployment already sits on GCE, Google Cloud Storage becomes the natural long-term backup target for the indexer layer. It keeps retained snapshot data outside the live Wazuh disk while still being easy to inspect and manage operationally.
The model in this repo is intentionally small because this lab only uses 100 GB of storage:
- Keep live Wazuh data queryable for 7 days
- Keep GCS snapshots for 9 days
- Manage the repository, snapshot policy, and retention policy from one playbook
This gives a 2-day recovery buffer outside the live cluster without turning the indexer into long-term storage or overloading the small lab disk.
The current implementation uses GCS, but the repository role is not cloud-locked. The same pattern can be switched to S3 later if the environment changes or a secondary object-storage target is needed.
Files in This Post
playbooks/
βββ wazuh-indexer-backup.yml
roles/
βββ wazuh_snapshot_repository/
βββ wazuh_snapshot_policy/
βββ wazuh_index_retention/
inventories/lab/files/wazuh/config/indexer/
βββ snapshot_repository.yml
βββ snapshot_policy.yml
βββ index_retention.yml
inventories/lab/files/wazuh/secrets/
βββ creds-backup.jsonplaybooks/
βββ wazuh-indexer-backup.yml
roles/
βββ wazuh_snapshot_repository/
βββ wazuh_snapshot_policy/
βββ wazuh_index_retention/
inventories/lab/files/wazuh/config/indexer/
βββ snapshot_repository.yml
βββ snapshot_policy.yml
βββ index_retention.yml
inventories/lab/files/wazuh/secrets/
βββ creds-backup.jsonDepends on: Part 1. The indexer must already exist.
One Playbook, Three Steps
The deployment flow is intentionally linear:
# playbooks/wazuh-indexer-backup.yml
- name: Configure Wazuh indexer backup and retention
hosts: aio:wazuh_indexer
become: true
gather_facts: true
roles:
- role: wazuh_snapshot_repository
- role: wazuh_snapshot_policy
- role: wazuh_index_retention# playbooks/wazuh-indexer-backup.yml
- name: Configure Wazuh indexer backup and retention
hosts: aio:wazuh_indexer
become: true
gather_facts: true
roles:
- role: wazuh_snapshot_repository
- role: wazuh_snapshot_policy
- role: wazuh_index_retentionThat sequence matters:
- Install plugin and register snapshot repositories
- Create daily snapshot schedules
- Enforce 7-day live index retention
Part 1: Register the GCS Repository
The repository role reads snapshot_repository.yml.
Current config:
repo_on: true
repo_plugin: repository-gcs
repo_keys: {}
repo_files:
gcs.client.default.credentials_file: "{{ inventory_dir }}/files/wazuh/secrets/creds-backup.json"
repos:
- name: wazuh-snapshots
type: gcs
settings:
bucket: gcs-backup-wazuh-labs
base_path: wazuh
compress: true
- name: wazuh-core-snapshots
type: gcs
settings:
bucket: gcs-backup-wazuh-labs
base_path: core
compress: truerepo_on: true
repo_plugin: repository-gcs
repo_keys: {}
repo_files:
gcs.client.default.credentials_file: "{{ inventory_dir }}/files/wazuh/secrets/creds-backup.json"
repos:
- name: wazuh-snapshots
type: gcs
settings:
bucket: gcs-backup-wazuh-labs
base_path: wazuh
compress: true
- name: wazuh-core-snapshots
type: gcs
settings:
bucket: gcs-backup-wazuh-labs
base_path: core
compress: trueThis splits backups into two paths in the same bucket:
wazuh/forwazuh-*datacore/for OpenSearch and dashboard internals
For this environment, that is the right tradeoff:
- live query data stays on the Wazuh AIO node
- durable retained snapshot data moves to GCS
- operational management stays inside the Google Cloud environment already in use
The role does three things:
- installs
repository-gcsif it is missing - copies the service-account credential file and loads it into the OpenSearch keystore
- registers each repo over the indexer API using the admin certificates
Even though this chapter uses repository-gcs, the role itself was written to stay portable. If object storage needs to move to Amazon S3 later, the same role can be reused by swapping the plugin, secure settings, secure files, and repository definition.
The secure file is not referenced directly from the repo path at runtime. It is copied into /etc/wazuh-indexer/snapshot-repository/ and then injected into the keystore with opensearch-keystore add-file.
That is the important hardening detail in this chapter.
Part 2: Schedule Daily Snapshots
Snapshot policy config lives in snapshot_policy.yml.
Current config:
snap_on: true
snap_items:
- name: wazuh-daily-snapshots
start: true
definition:
description: Daily Wazuh data snapshots to GCS with 9-day retention.
creation:
schedule:
cron:
expression: "30 1 * * *"
timezone: Asia/Jakarta
deletion:
schedule:
cron:
expression: "0 3 * * *"
timezone: Asia/Jakarta
condition:
max_age: 9d
min_count: 1
snapshot_config:
repository: wazuh-snapshots
indices: "wazuh-*"
- name: wazuh-core-daily-snapshots
start: true
definition:
description: Daily core OpenSearch and Dashboards snapshots to GCS.
creation:
schedule:
cron:
expression: "45 1 * * *"
timezone: Asia/Jakarta
deletion:
schedule:
cron:
expression: "15 3 * * *"
timezone: Asia/Jakarta
condition:
max_age: 9d
min_count: 1
snapshot_config:
repository: wazuh-core-snapshots
indices: ".opensearch_dashboards*,.kibana*,.plugins*,.opendistro*,.tasks,.security*"snap_on: true
snap_items:
- name: wazuh-daily-snapshots
start: true
definition:
description: Daily Wazuh data snapshots to GCS with 9-day retention.
creation:
schedule:
cron:
expression: "30 1 * * *"
timezone: Asia/Jakarta
deletion:
schedule:
cron:
expression: "0 3 * * *"
timezone: Asia/Jakarta
condition:
max_age: 9d
min_count: 1
snapshot_config:
repository: wazuh-snapshots
indices: "wazuh-*"
- name: wazuh-core-daily-snapshots
start: true
definition:
description: Daily core OpenSearch and Dashboards snapshots to GCS.
creation:
schedule:
cron:
expression: "45 1 * * *"
timezone: Asia/Jakarta
deletion:
schedule:
cron:
expression: "15 3 * * *"
timezone: Asia/Jakarta
condition:
max_age: 9d
min_count: 1
snapshot_config:
repository: wazuh-core-snapshots
indices: ".opensearch_dashboards*,.kibana*,.plugins*,.opendistro*,.tasks,.security*"The local rule here is:
- Snapshot creation runs daily
- Snapshot cleanup also runs daily
- GCS snapshot retention is 9 days
Why 9 days?
- 7 days live in Wazuh
- 2 extra days in backup
That is enough buffer for restore mistakes or delayed investigation without keeping too much stale data in the bucket.
Part 3: Keep Live Data for 7 Days
Live retention config lives in index_retention.yml.
Current config:
retention_on: true
retention_name: wazuh-retention-7d
retention_patterns:
- wazuh-alerts-*
- wazuh-archives-*
- wazuh-monitoring-*
- wazuh-statistics-*
- wazuh-states-*
retention_body:
policy:
description: Retain Wazuh indexes for 7 days and then delete them.
default_state: hot
schema_version: 1
ism_template:
- index_patterns:
- wazuh-alerts-*
- wazuh-archives-*
- wazuh-monitoring-*
- wazuh-statistics-*
- wazuh-states-*
priority: 100
states:
- name: hot
actions: []
transitions:
- state_name: delete
conditions:
min_index_age: 7d
- name: delete
actions:
- delete: {}retention_on: true
retention_name: wazuh-retention-7d
retention_patterns:
- wazuh-alerts-*
- wazuh-archives-*
- wazuh-monitoring-*
- wazuh-statistics-*
- wazuh-states-*
retention_body:
policy:
description: Retain Wazuh indexes for 7 days and then delete them.
default_state: hot
schema_version: 1
ism_template:
- index_patterns:
- wazuh-alerts-*
- wazuh-archives-*
- wazuh-monitoring-*
- wazuh-statistics-*
- wazuh-states-*
priority: 100
states:
- name: hot
actions: []
transitions:
- state_name: delete
conditions:
min_index_age: 7d
- name: delete
actions:
- delete: {}This policy does not back anything up. It only governs live index deletion inside OpenSearch.
That distinction is easy to miss:
- Snapshot policy controls backup creation and backup deletion
- ISM retention controls live index lifecycle
They are separate systems and both are needed.
Effective Retention Model
With the current repo state:
- Live Wazuh indexes: 7 days
- GCS snapshots: 9 days
This is not "16 days total of live data".
It means:
- The cluster itself keeps 7 days
- Older recovery points remain in GCS for up to 9 days from snapshot time
So the operational safety margin is in the backup layer, not in the live query layer.
Verification
Run the playbook:
ansible-playbook -i inventories/lab/hosts.ini playbooks/wazuh-indexer-backup.ymlansible-playbook -i inventories/lab/hosts.ini playbooks/wazuh-indexer-backup.ymlUseful dashboard checks:
- Snapshot Management shows both policies enabled
- Index Management shows
wazuh-retention-7d
- GCS bucket contains
wazuh/andcore/snapshot paths
Restore data
Testing restore data from GCS Bucket
- Currently last data is around Jun 22, 2026
- On my current snaphost, I have backups running starting from Jun 17, 2026.
- Now let's try restoring the data for Jun 18, 2026.
- Once the restore is complete, let's try searching in Discover. Since the index name has been restored, you need to create an index pattern first.
In the index search, it will be restored_wazuh-alerts-4.x-*, and you can now search for data from that date.
Final Notes
The repo does not try to make Wazuh a long-retention analytics platform. It keeps:
- Short live retention for predictable disk usage
- Short backup retention for operational recovery
- Everything described as inventory data, not hand-edited cluster state
The retention policy is also flexible. For example, live data in Wazuh can be configured for 1 year, while the latest backup data in GCS can be kept permanently. This only needs to be adjusted based on disk capacity, storage cost, recovery needs, and each company's internal retention or compliance policy.
That keeps the indexer manageable and makes backup behavior reproducible.
Next: Part 11 β Ubuntu 24.04 CIS Hardening with a Local Wrapper Role