Backup & Restore Scripts- Elasticsearch - 2.4.3

Posting For records.

Note: These scripts tested against Elasticsearch 2.4.3 which is EOL.
These scripts may or may not work with newer versions of elastic.

1] Details:
A] Node1: 10.226.71.1 (Master + Data)
B] Node2: 10.226.71.2 (Data)

2] Step-by-Step NFS Setup on CentOS. (If not exsists.)

A] On node1 (NFS Server):

#sudo yum install -y nfs-utils

Create backup directory:
#sudo mkdir -p /mnt/es_backup
#sudo chown -R elasticsearch:elasticsearch /mnt/es_backup
#sudo chmod 750 /mnt/es_backup

Configure /etc/exports:
#echo "/mnt/es_backup 10.226.71.2(rw,sync,no_root_squash)" | sudo tee -a /etc/exports

Start and enable NFS services:
#sudo systemctl enable nfs-server
#sudo systemctl start nfs-server
#sudo exportfs -ra
#sudo systemctl restart nfs-server

Confirm export:
#exportfs -v

B] On node2 (NFS Client):

Install NFS packages:
sudo yum install -y nfs-utils

Create mount directory:
#sudo mkdir -p /mnt/es_backup

Mount the shared folder:
#sudo mount 10.226.71.1:/mnt/es_backup /mnt/es_backup

Make it permanent (optional):
#10.226.71.1:/mnt/es_backup /mnt/es_backup nfs defaults 0 0

Set Proper Permissions:
Make sure the `elasticsearch` user on both nodes can access /mnt/es_backup:

sudo chown -R elasticsearch:elasticsearch /mnt/es_backup
sudo chmod 750 /mnt/es_backup

3] Java Setup:
Note: You may face issue like below, therefore set compatible java version.

Unrecognized VM option 'UseParNewGC'
Error: Could not create the Java Virtual Machine.

A] Check current Java:

java -version

B] If version is NOT 1.8, install OpenJDK 8:

sudo apt-get install openjdk-8-jdk      # Ubuntu/Debian
sudo yum install java-1.8.0-openjdk     # CentOS/RHEL

C] Set JAVA_HOME for Elasticsearch:

# vi ~/.bashrc

export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.412.b08-1.el7_9.x86_64
export PATH=$JAVA_HOME/bin:$PATH

# Save above & run command > source ~/.bashrc

D] Version must show something like:

java version "1.8.0_412"

E] If still unable to see the desire version number then,

sudo alternatives --install /usr/bin/java java /usr/lib/jvm/java-1.8.0-openjdk-*/bin/java 1
sudo alternatives --config java

# Second command will show available java versions, enter appropriate input after second command.

4] Ingest data:

# vim bulk_web_logs.json
 
{ "index": { "_index": "web-logs", "_type": "log" } }
{ "clientip": "192.168.1.1", "timestamp": "2025-06-13T10:00:00", "request": "GET /index.html HTTP/1.1", "status": 200, "bytes": 1043 }
{ "index": { "_index": "web-logs", "_type": "log" } }
{ "clientip": "192.168.1.2", "timestamp": "2025-06-13T10:01:00", "request": "POST /form HTTP/1.1", "status": 201, "bytes": 523 }
{ "index": { "_index": "web-logs", "_type": "log" } }
{ "clientip": "192.168.1.3", "timestamp": "2025-06-13T10:02:00", "request": "GET /about.html HTTP/1.1", "status": 404, "bytes": 234 }
{ "index": { "_index": "web-logs", "_type": "log" } }
{ "clientip": "192.168.1.4", "timestamp": "2025-06-13T10:03:00", "request": "GET /contact HTTP/1.1", "status": 200, "bytes": 872 }
{ "index": { "_index": "web-logs", "_type": "log" } }
{ "clientip": "192.168.1.5", "timestamp": "2025-06-13T10:04:00", "request": "GET /index.html HTTP/1.1", "status": 304, "bytes": 0 }
{ "index": { "_index": "web-logs", "_type": "log" } }
{ "clientip": "192.168.1.6", "timestamp": "2025-06-13T10:05:00", "request": "GET /style.css HTTP/1.1", "status": 200, "bytes": 643 }
{ "index": { "_index": "web-logs", "_type": "log" } }
{ "clientip": "192.168.1.7", "timestamp": "2025-06-13T10:06:00", "request": "GET /favicon.ico HTTP/1.1", "status": 404, "bytes": 123 }
{ "index": { "_index": "web-logs", "_type": "log" } }
{ "clientip": "192.168.1.8", "timestamp": "2025-06-13T10:07:00", "request": "POST /login HTTP/1.1", "status": 401, "bytes": 231 }
{ "index": { "_index": "web-logs", "_type": "log" } }
{ "clientip": "192.168.1.9", "timestamp": "2025-06-13T10:08:00", "request": "GET /dashboard HTTP/1.1", "status": 200, "bytes": 983 }

# Save the file and load.
# curl -XPOST "http://10.226.71.1:9200/_bulk" --data-binary @bulk_web_logs.json -H "Content-Type: application/json"

5] Configure Snapshot Repositor, Script for Backup & Restore (Snapshot):

A] Configure Snapshot Repository (Run once)

Note: /mnt/es_backup must exist on both nodes and be accessible.

curl -XPUT 'http://10.226.71.1:9200/_snapshot/my_backup' -d '{
  "type": "fs",
  "settings": {
    "location": "/mnt/es_backup",
    "compress": true
  }
}'

> Add following in elasticsearch.yml:
path.repo: ["/mnt/es_backup"]

B1] Backup Script (backup.sh)

>#!/bin/bash

SNAPSHOT_NAME="snapshot-$(date +%Y-%m-%d_%H-%M)"
#SNAPSHOT_NAME="snapshot-$(date +%Y%m%d%H%M)"
REPO="my_backup"
ES_HOST="10.226.71.1:9200"

curl -XPUT "$ES_HOST/_snapshot/$REPO/$SNAPSHOT_NAME?wait_for_completion=true" -d '{
  "indices": "*",
  "ignore_unavailable": true,
  "include_global_state": true
}'

B2] Backup script with delete older snapshots.

#!/bin/bash

# Config
REPO="my_backup"
ES_HOST="10.226.71.1:9200"
SNAPSHOT_NAME="snapshot-$(date +%Y-%m-%d_%H-%M)"
SNAPSHOT_DATE_FORMAT="+%Y-%m-%d_%H-%M"
RETENTION_DAYS=10

# Create a new snapshot
curl -XPUT "$ES_HOST/_snapshot/$REPO/$SNAPSHOT_NAME?wait_for_completion=true" -d '{
  "indices": "*",
  "ignore_unavailable": true,
  "include_global_state": true
}'

# Get current time in epoch
now_epoch=$(date +%s)

# Get list of all snapshots
snapshots=$(curl -s "$ES_HOST/_snapshot/$REPO/_all" | grep -oP '"snapshot"\s*:\s*"\K[^"]+')

# Loop through snapshots and delete older than retention
for snap in $snapshots; do
    # Extract timestamp from snapshot name
    snap_date_str=$(echo "$snap" | sed -n 's/snapshot-\(.*\)/\1/p')

    # Convert to epoch (only if matches expected date format)
    if [[ $snap_date_str =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}$ ]]; then
        snap_epoch=$(date -d "${snap_date_str//_/ }" +%s 2>/dev/null)
        if [[ $? -eq 0 ]]; then
            age_days=$(( (now_epoch - snap_epoch) / 86400 ))
            if (( age_days > RETENTION_DAYS )); then
                echo ""
                echo "Deleting snapshot $snap (age: $age_days days)"
                curl -XDELETE "$ES_HOST/_snapshot/$REPO/$snap"
            fi
        fi
    fi
done

B3] Dedicated script to delete older snapshots.

#!/bin/bash

# Configuration
REPO="my_backup"
ES_HOST="10.226.71.1:9200"
RETENTION_DAYS=10

# Get current time in epoch
now_epoch=$(date +%s)

# Get list of all snapshots
snapshots=$(curl -s "$ES_HOST/_snapshot/$REPO/_all" | grep -oP '"snapshot"\s*:\s*"\K[^"]+')

# Loop through snapshots
for snap in $snapshots; do
    # Extract timestamp from snapshot name (assumes format snapshot-YYYY-MM-DD_HH-MM)
    snap_date_str=$(echo "$snap" | sed -n 's/snapshot-\(.*\)/\1/p')

    if [[ $snap_date_str =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}$ ]]; then
        snap_epoch=$(date -d "${snap_date_str//_/ }" +%s 2>/dev/null)

        if [[ $? -eq 0 ]]; then
            age_days=$(( (now_epoch - snap_epoch) / 86400 ))

            if (( age_days > RETENTION_DAYS )); then
                echo "Deleting snapshot $snap (age: $age_days days)"
                curl -s -XDELETE "$ES_HOST/_snapshot/$REPO/$snap"
            fi
        fi
    fi
done

C] Restore Script (restore.sh)

#!/bin/bash

SNAPSHOT_NAME=$1  # pass snapshot name as argument
REPO="my_backup"
ES_HOST="10.226.71.1:9200"

if [ -z "$SNAPSHOT_NAME" ]; then
  echo "Usage: $0 <snapshot-name>"
  exit 1
fi

curl -XPOST "$ES_HOST/_snapshot/$REPO/$SNAPSHOT_NAME/_restore" -d '{
  "indices": "*",
  "include_global_state": true,
  "rename_pattern": "index_(.+)",
  "rename_replacement": "restored_index_$1"
}'

6] Optional: Cron Job for Backup:

0 2 * * * /path/to/backup.sh >> /var/log/es_backup.log 2>&1
0 2 * * * /path/to/backup.sh > /dev/null 2>&1

7] To restore:

Backup files in "/mnt/es_backup/ " may looks like "snap-snapshot-2025-06-13_19-32.dat, meta-snapshot-2025-06-13_19-32.dat" . To restore, run following command.

# sh restore.sh snapshot-2025-06-13_19-32

8] Elasticsearch Start & Restart Script:

#start.sh
#!/bin/bash

># Navigate to the Elasticsearch folder
>cd /opt/elastic-2.4.3/elasticsearch-2.4.3

# Start Elasticsearch in background and write output to a log file
nohup ./bin/elasticsearch > logs/es.out 2>&1 &
#restart.sh
#!/bin/bash

ES_DIR="/opt/elastic-2.4.3/elasticsearch-2.4.3"
ES_BIN="$ES_DIR/bin/elasticsearch"

# Kill existing process
echo "Stopping Elasticsearch..."
pkill -f elasticsearch

# Wait a bit
sleep 5

# Start again in background
echo "Starting Elasticsearch..."
cd "$ES_DIR"
nohup $ES_BIN > logs/es.out 2>&1 &

echo "Elasticsearch restarted."

2 Likes

Thx for sharing! This will backup the data in the indices, but you should also backup kibana objects & other ES objects. I use this script GitHub - DisorganizedWizardry/KibanaBackup

1 Like

Great. Thanks for sharing your thoughts.