OpenSearch Backup and Restore¶
This guide covers snapshot management, Velero backups, and recovery procedures for the OpenSearch cluster. It specifies how you handle snapshots with OpenSearch in Kubernetes.
How It Works¶
The snapshot and restore process relies on the OpenSearch snapshot engine and native Kubernetes resource backups to ensure data integrity and cluster recoverability.
-
Repository Binding (Remote Object Storage)
OpenSearch uses native repository plugins (such as S3, Azure, or GCS) to bind the cluster to an external, offsite storage bucket or container.
- Storage Path: All snapshots are written to a designated base path within the configured remote storage bucket.
- Authentication: Connection credentials are managed securely via the storage client, utilizing cloud-native authentication methods (such as AWS IAM roles, Azure Managed Identities, or mounted Kubernetes secrets).
-
Point-in-Time, Incremental Backups
- Point-in-Time Consistency: When a snapshot command is executed, OpenSearch freezes the current state of the Lucene index segments. Any writes or updates that occur while the snapshot is running are safely isolated and do not corrupt the snapshot.
- Incremental Storage: Only new or modified Lucene segments that do not exist in the repository are uploaded. This minimizes storage consumption, reduces network overhead, and speeds up subsequent backup runs.
- Metadata Preservation: Along with index data, the snapshot captures cluster metadata, including index templates, mappings, aliases, and global settings.
-
Data Restoration (System vs. User Indices)
When restoring a snapshot to a clean or recreated cluster:
- User Indices: All business data and custom indices are restored directly from the snapshot.
- Security & System Indices: System-critical indices (such as .opendistro_security, .opensearch-observability, and security audit logs) are explicitly excluded from the snapshot restore (-d "{\"indices\": ...}"). Overwriting these dynamically on a running cluster can lock out administrators or break cluster authentication.
Create a Restore Snapshot in OpenSearch¶
Before performing upgrades or critical maintenance, create a manual OpenSearch snapshot.
Mount usage for curl commands¶
In the following code examples, there will be used cat /mnt/admin-credentials/username and cat /mnt/admin-credentials/password which is only possible if a secret is mounted in the pod.
Mount secret to pod¶
If the secret is not mounted in the pod, then we recommend manually create and mount the secret, passwords will be saved in the bash history for the pod.
First create the secret:
kubectl create secret generic opensearch-admin-secret \
--from-literal=username=admin \
--from-literal=password='YourAdminPassword' \
-n opensearch-namespace
Define it in the opensearch-cluster-manager pod:
apiVersion: v1
kind: Pod
metadata:
name: opensearch-cluster-manager-0
spec:
containers:
- name: opensearch
# 1. Mount the volume to the custom path
volumeMounts:
- name: admin-creds-volume
mountPath: "/mnt/admin-credentials"
readOnly: true
# 2. Define the volume based on the Secret
volumes:
- name: admin-creds-volume
secret:
secretName: opensearch-admin-secret
Validate Data Consistency¶
To ensure zero data loss, compare the document counts and a sample record before the maintenance/deletion against the newly restored cluster.
Verify the total document count matches your baseline:
kubectl exec -n opensearch-namespace opensearch-cluster-manager-0 -c opensearch -- bash -c 'curl -s -k -X GET -u "$(cat /mnt/admin-credentials/username):$(cat /mnt/admin-credentials/password)" "https://localhost:9200/top_queries-2026.07.10-44506/_count"' | jq .
You should see an output as such:
After the restore you should run the command again and validate that the count is the same as before you took the snapshot.Check Repository Config¶
Find the snapshots repository:
kubectl exec -n opensearch-namespace opensearch-cluster-manager-0 -c opensearch -- bash -c 'curl -s -k -u "$(cat /mnt/admin-credentials/username):$(cat /mnt/admin-credentials/password)" "https://localhost:9200/_snapshot"' | jq .
The output will show:
{
"name": {
"type": "<type>",
"settings": {
"container": "<name_of_container>",
"client": "<type_of_client>",
"base_path": "<the_base_path>"
}
}
}
⚠️ WARNING: Verify Storage Location¶
Before continuing, check the
"type"field in the repository output above. It must indicate a remote storage location (e.g.,"azure"). If the type is"fs"(File System), your snapshots are being saved locally to the pod's storage volume. Do not proceed with this guide if the type is "fs", as deleting the cluster and PVCs will permanently destroy your backups.
Create the Snapshot¶
Create a snapshot with the current date:
kubectl exec -n opensearch-namespace opensearch-cluster-manager-0 -c opensearch -- bash -c 'curl -s -k -X PUT -u "$(cat /mnt/admin-credentials/username):$(cat /mnt/admin-credentials/password)" "https://localhost:9200/_snapshot/kamstrup-os-snapshots/<snapshot_name>"' | jq .
Verify Snapshot Creation¶
Test to see if the snapshot was created successfully:
kubectl exec -n opensearch-namespace opensearch-cluster-manager-0 -c opensearch -- bash -c 'curl -s -k -X GET -u "$(cat /mnt/admin-credentials/username):$(cat /mnt/admin-credentials/password)" "https://localhost:9200/_snapshot/kamstrup-os-snapshots/<snapshot_name>"' | jq .
Restore Backup¶
If an upgrade fails and the cluster is unrecoverable, follow these steps to restore the OpenSearch data and dashboard.
The best way to restore the OpenSearch snapshot is to create a new OpenSearch cluster and restore the backup there.
The cleanest way is to:
- Delete the PVCs for datanodes and managerpods in the OpenSearch namespace.
- Delete the OpenSearch cluster.
- Flux will recreate the cluster
Delete The Opensearch Cluster¶
Locate the Opensearch cluster:
Delete the Opensearch cluster which will ensure that all the pods and dependancies gets deleted, however it wont delte the pvc's, which you will have to delte manually
Delete the Opensearch Cluster:
Delete the pvc's:
After deleteing the Opensearch cluster and a new one has been created with either flux or manually applyed by help, aswell as the cluster is green, you can proceed.
View Available Snapshots¶
List all snapshots inside the repository:
kubectl exec -n opensearch-namespace opensearch-cluster-manager-0 -c opensearch -- bash -c 'curl -s -k -X GET -u "$(cat /mnt/admin-credentials/username):$(cat /mnt/admin-credentials/password)" "https://localhost:9200/_snapshot/kamstrup-os-snapshots/_all"' | jq '.snapshots[] | {snapshot: .snapshot, state: .state}'
Restore OpenSearch Data¶
If you need to recreate the OpenSearch cluster, run the following command to restore data from your snapshot.
Note
Specify the snapshot name in place of <SnapshotName>.
kubectl exec -n opensearch-namespace opensearch-cluster-manager-0 -c opensearch -- bash -c 'curl -s -k -X POST -u "$(cat /mnt/admin-credentials/username):$(cat /mnt/admin-credentials/password)" -H "Content-Type: application/json" "https://localhost:9200/_snapshot/kamstrup-os-snapshots/<SnapshotName>/_restore?wait_for_completion=true" -d "{\"indices\": \"*,-.opendistro_security,-security-auditlog-*,-.opensearch-observability,-.plugins-ml-config,-.opensearch-sap-log-types-config\"}"' | jq .
If some indices cannot be restored because cluster recreation generated new indices with conflicting names, delete the local conflicting indices and retry the restore:
kubectl exec -n opensearch-namespace opensearch-cluster-manager-0 -c opensearch -- bash -c 'curl -s -k -X DELETE -u "$(cat /mnt/admin-credentials/username):$(cat /mnt/admin-credentials/password)" "https://localhost:9200/<index_name>"'
Validate Recovery¶
Check that your recovered indices are present and active:
kubectl exec -n opensearch-namespace opensearch-cluster-manager-0 -c opensearch -- bash -c 'curl -s -k -X GET -u "$(cat /mnt/admin-credentials/username):$(cat /mnt/admin-credentials/password)" "https://localhost:9200/_cat/indices?format=json"' | jq .
Verify index creation dates:
kubectl exec -n opensearch-namespace opensearch-cluster-manager-0 -c opensearch -- bash -c 'curl -s -k -X GET -u "$(cat /mnt/admin-credentials/username):$(cat /mnt/admin-credentials/password)" "https://localhost:9200/_cat/indices?format=json&h=index,docs.count,creation.date.string"' | jq .
Verify the total document count matches your baseline:
kubectl exec -n opensearch-namespace opensearch-cluster-manager-0 -c opensearch -- bash -c 'curl -s -k -X GET -u "$(cat /mnt/admin-credentials/username):$(cat /mnt/admin-credentials/password)" "https://localhost:9200/top_queries-2026.07.10-44506/_count"' | jq .
Verify that the "count" is the same as before the backup was taken:
Restore Dashboards & Permissions¶
The Fix: Force-Syncing Your Security State
To resolve this issue and get your dashboard up and running, force Kubernetes and Flux to re-apply the credentials to OpenSearch.
- Clear current Kubernetes security states: Delete all existing OpenSearch security objects in the namespace. The opensearch operator will re-create them:
kubectl delete opensearchusers,opensearchroles,opensearchuserrolebindings,opensearchtenants -n opensearch-namespace --all
- Re-run the security update job Delete the old, completed update job. This forces Kubernetes to start a new pod that pushes your user configurations, role mappings, and dashboard credentials back into OpenSearch:
- Restart the Dashboard Pod Once the security job finishes, restart the dashboard pod so it can establish a fresh session using the newly injected credentials:
Validate Cluster Health¶
Verify the final health status of the cluster:
Command to curl to the application:
kubectl exec -n opensearch-namespace opensearch-cluster-manager-0 -c opensearch -- bash -c 'curl -s -k -X GET -u "$(cat /mnt/admin-credentials/username):$(cat /mnt/admin-credentials/password)" "https://localhost:9200/_cluster/health"' | jq .
{
"cluster_name": "opensearch",
"status": "green",
"timed_out": false,
"number_of_nodes": 7,
"number_of_data_nodes": 4,
"discovered_master": true,
"discovered_cluster_manager": true,
"active_primary_shards": 13,
"active_shards": 39,
"relocating_shards": 0,
"initializing_shards": 0,
"unassigned_shards": 0,
"delayed_unassigned_shards": 0,
"number_of_pending_tasks": 0,
"number_of_in_flight_fetch": 0,
"task_max_waiting_in_queue_millis": 0,
"active_shards_percent_as_number": 100.0
}