Backup and Restore Guide

This guide covers backup and restore strategies for Geode databases: online and offline backups, the full-backup CLI workflow, restoring to the newest backup at or before a timestamp, and disaster recovery planning.

Overview

Geode provides two backup strategies to protect your data:

Backup TypeDescriptionUse Case
Online BackupServer keeps serving; geode backup create streams a full copyProduction systems
Offline BackupServer stopped; filesystem copy of the data directoryMaintenance windows

Every Geode backup is a full backup. There is no incremental backup type (geode backup create --type accepts full and nothing else), and there is no WAL-replay point-in-time recovery. geode restore --target-time selects the newest backup at or before a timestamp — see Point-in-Time Selection .

Backup and restore are CLI operations. GQL BACKUP/RESTORE statements parse but are intentionally refused by the executor with SQLSTATE 0A000:

GQL BACKUP/RESTORE statements are unsupported: use the CLI instead
(`geode backup create --output <file>` to create a backup,
`geode restore <file>` to restore one).

There is likewise no geode.backup.* or geode.restore.* procedure namespace; drive backups from the CLI, including from application code (examples below).

Backup Architecture

Geode Database
    |
    +-- Data Files (*.gdb)
    |
    +-- Transaction Logs (*.wal)
    |
    +-- Configuration (geode.conf)
    |
    +-- Metadata (schemas, indexes)

Online vs Offline Backups

Online Backups (Hot Backup)

Online backups allow you to back up the database while it’s running and serving traffic.

Advantages:

  • No downtime
  • Consistent snapshot
  • Suitable for 24/7 operations

CLI Command:

# Create online backup
geode backup create \
  --output /backups/geode-$(date +%Y%m%d-%H%M%S).backup \
  --compress

# With specific connection and credentials
geode backup create \
  --server localhost:3141 \
  --user admin --password "$GEODE_PASSWORD" \
  --output /backups/full-backup.backup

geode backup create accepts --output (required), --type full, --server, --user, --password, --compress, and the TLS options (--ca-cert, --client-cert, --client-key, --insecure-tls-skip-verify). Inspect the result with geode backup info <file> — there is no --verify flag.

Multi-Language Online Backup Examples:

package main

import (
    "context"
    "fmt"
    "log"
    "os/exec"
    "time"
)

// createOnlineBackup shells out to the geode CLI: backup is not a GQL
// operation, so there is no query to send over the client connection.
func createOnlineBackup(ctx context.Context, server, backupPath string) error {
    cmd := exec.CommandContext(ctx, "geode", "backup", "create",
        "--server", server,
        "--output", backupPath,
        "--compress")
    if out, err := cmd.CombinedOutput(); err != nil {
        return fmt.Errorf("backup failed: %w: %s", err, out)
    }

    // Inspect the header the CLI wrote (format, type, timestamp, size, checksum)
    info, err := exec.CommandContext(ctx, "geode", "backup", "info", backupPath).CombinedOutput()
    if err != nil {
        return fmt.Errorf("backup info failed: %w: %s", err, info)
    }
    log.Printf("Backup header:\n%s", info)

    return nil
}

func main() {
    ctx := context.Background()

    backupPath := fmt.Sprintf("/backups/geode-%s.backup",
        time.Now().Format("20060102-150405"))

    if err := createOnlineBackup(ctx, "localhost:3141", backupPath); err != nil {
        log.Fatalf("Backup failed: %v", err)
    }

    log.Printf("Backup created successfully: %s", backupPath)
}
import asyncio
from datetime import datetime

async def run(*args: str) -> str:
    """Run a geode CLI command and return its combined output."""
    proc = await asyncio.create_subprocess_exec(
        *args,
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.STDOUT,
    )
    out, _ = await proc.communicate()
    if proc.returncode != 0:
        raise RuntimeError(f"{' '.join(args)} failed: {out.decode()}")
    return out.decode()

async def create_online_backup(backup_path: str, server: str = "localhost:3141"):
    """Create an online backup without stopping the database."""
    print(f"Starting online backup to {backup_path}...")

    await run("geode", "backup", "create",
              "--server", server,
              "--output", backup_path,
              "--compress")

    # Read back the backup header: format, type, compression, timestamp,
    # payload size and checksum.
    print(await run("geode", "backup", "info", backup_path))
    print(f"Online backup complete: {backup_path}")

async def main():
    timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
    backup_path = f"/backups/geode-{timestamp}.backup"

    await create_online_backup(backup_path)

asyncio.run(main())
use chrono::Local;
use std::process::Command;

fn geode(args: &[&str]) -> Result<String, Box<dyn std::error::Error>> {
    let out = Command::new("geode").args(args).output()?;
    if !out.status.success() {
        return Err(format!(
            "geode {} failed: {}",
            args.join(" "),
            String::from_utf8_lossy(&out.stderr)
        )
        .into());
    }
    Ok(String::from_utf8_lossy(&out.stdout).to_string())
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let timestamp = Local::now().format("%Y%m%d-%H%M%S");
    let backup_path = format!("/backups/geode-{}.backup", timestamp);

    println!("Starting online backup to {}...", backup_path);

    // Backup is a CLI operation; there is no GQL statement or procedure for it.
    geode(&[
        "backup",
        "create",
        "--server",
        "127.0.0.1:3141",
        "--output",
        &backup_path,
        "--compress",
    ])?;

    // Read back the header the CLI wrote, including its checksum.
    print!("{}", geode(&["backup", "info", &backup_path])?);

    println!("Online backup complete: {}", backup_path);
    Ok(())
}
#!/bin/bash
# Online backup script

set -euo pipefail

BACKUP_DIR="/backups"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
BACKUP_FILE="${BACKUP_DIR}/geode-${TIMESTAMP}.backup"
GEODE_SERVER="localhost:3141"

echo "Starting online backup..."

# Create backup (set -e aborts the script if this fails)
geode backup create \
  --server "${GEODE_SERVER}" \
  --output "${BACKUP_FILE}" \
  --compress

SIZE=$(du -h "${BACKUP_FILE}" | cut -f1)
echo "Backup complete: ${BACKUP_FILE} (${SIZE})"

# Show the header the CLI wrote (type, compression, timestamp, checksum)
geode backup info "${BACKUP_FILE}"

# Keep only the last 7 days of backups
geode backup cleanup --older-than 7d

Offline Backups (Cold Backup)

Offline backups require stopping the database but guarantee complete consistency.

CLI Commands:

# Stop Geode
systemctl stop geode

# Create backup (filesystem copy)
tar -czvf /backups/geode-offline-$(date +%Y%m%d).tar.gz /var/lib/geode/data

# Restart Geode
systemctl start geode

When to Use Offline Backups:

  • Major version upgrades
  • Schema migrations
  • Hardware maintenance
  • Compliance requirements

Full Backups

Creating Full Backups

A full backup contains the complete database state. full is the only backup type the CLI accepts, so this is what every geode backup create produces.

# Full backup, gzip-compressed
geode backup create \
  --type full \
  --output /backups/full-$(date +%Y%m%d).backup \
  --compress

--compress is a boolean switch: the CLI chooses the codec and records it in the backup header (geode backup info reports gzip or zstd). There is no compression-level, metadata or index selection flag — a full backup already carries the schema and index definitions with the data.

Full Backup Schedule

import asyncio
import os
from datetime import datetime

async def run(*args: str) -> str:
    proc = await asyncio.create_subprocess_exec(
        *args,
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.STDOUT,
    )
    out, _ = await proc.communicate()
    if proc.returncode != 0:
        raise RuntimeError(f"{' '.join(args)} failed: {out.decode()}")
    return out.decode()

async def create_full_backup(server: str = "localhost:3141"):
    """Create a full backup and report what landed on disk."""
    backup_dir = "/backups/full"
    os.makedirs(backup_dir, exist_ok=True)

    timestamp = datetime.now().strftime("%Y%m%d")
    backup_path = f"{backup_dir}/geode-full-{timestamp}.backup"

    await run("geode", "backup", "create",
              "--server", server,
              "--type", "full",
              "--output", backup_path,
              "--compress")

    print(f"Full backup created: {backup_path}")
    print(f"  Size: {os.path.getsize(backup_path) / (1024 * 1024):.2f} MB")
    print(await run("geode", "backup", "info", backup_path))

asyncio.run(create_full_backup())

Node and relationship counts are not part of the backup header. Capture them from the live database before the backup if you want them in your logs:

CALL geode.storage.stats() YIELD node_count, edge_count
RETURN node_count, edge_count;

Listing Backups

# Scans ./backups and /tmp by default
geode backup list

# Or a specific directory
geode backup list --dir /backups/full

The listing reports path, type, size and compression for each file whose name starts with geode_backup or ends in .backup, .gz or .zst.

Backup Rotation

Because every backup is a full backup, rotation is a retention decision rather than a chain to maintain:

# Remove backups older than seven days
geode backup cleanup --older-than 7d

# Accepts h and d units, e.g. 1h, 24h, 30d
geode backup cleanup --older-than 30d

A weekly-full/daily-incremental scheme is not available: there is no incremental backup type, no --base flag, and no LSN-delta procedure. If daily full backups are too expensive for your dataset, take them less often and accept the corresponding RPO, or replicate to a standby (see Distributed Systems ).

Point-in-Time Selection

What Geode Supports

Geode does not replay archived WAL segments to an arbitrary instant. What geode restore --target-time does is pick the newest backup at or before the timestamp you name and restore that. Your effective recovery point is therefore your backup interval, not a single transaction.

There is no [wal] archive_enabled / archive_command configuration, no `` flag and no --target-lsn flag. The server configuration file is a flat YAML/TOML/JSON document (.geode/config.yaml, .geode/config.toml, …) holding keys such as data_dir, listen_quic, listen_grpc, cert_path, key_path, log_level and idle_timeout.

Restoring to a Timestamp

# Restore whatever backup was current at 14:30 UTC
geode restore \
  --source /backups/full \
  --target-time "2026-01-28T14:30:00Z" \
  --yes

# Or name the file directly
geode restore /backups/full/geode-full-20260128.backup --yes

geode restore takes the backup as a positional argument or via --source (which also accepts a URI such as s3://bucket/backup.gql), plus --server, --user, --password, --yes/--confirm and the TLS options. Without --yes it prompts for interactive confirmation.

To narrow the recovery point, back up more often — every backup is a full backup, so there is no chain to replay.

Backup Verification

Inspecting a Backup File

# Header: format version, type, compression, encryption flag,
# timestamp, payload size and checksum
geode backup info /backups/geode-20260128.backup

# Everything currently on disk
geode backup list --dir /backups

There is no geode backup verify command and no verification procedure. The checks available are the header/checksum readout above, plus the live-store integrity checks after a restore:

geode storage verify
CALL geode.storage.integrity()
YIELD node_count_consistent, edge_count_consistent, label_index_entries, total_errors
RETURN node_count_consistent, edge_count_consistent, label_index_entries, total_errors;

Checking Every Backup in a Directory

#!/bin/bash
# Print the header of every backup in a directory and fail on the first
# file whose header cannot be read.
set -euo pipefail

BACKUP_DIR="${1:-/backups/full}"

for f in "${BACKUP_DIR}"/*.backup; do
    echo "=== ${f}"
    geode backup info "${f}"
done

Test Restores

Regularly test your backups by restoring into a throwaway server:

#!/bin/bash
# Test restore script
set -euo pipefail

BACKUP_FILE="$1"
TEST_DIR="/tmp/geode-test-restore"

# Clean up previous test
rm -rf "${TEST_DIR}"
mkdir -p "${TEST_DIR}"

# Start a scratch server on its own port and data directory
geode serve --data-dir "${TEST_DIR}" --listen 127.0.0.1:3142 &
GEODE_PID=$!
sleep 5

# Restore into it
geode restore "${BACKUP_FILE}" --server 127.0.0.1:3142 --yes

# Verify the restored data
geode query --server 127.0.0.1:3142 "MATCH (n) RETURN count(n)"
geode query --server 127.0.0.1:3142 \
  "CALL geode.storage.integrity() YIELD total_errors RETURN total_errors"

# Stop the scratch instance and clean up
kill ${GEODE_PID}
rm -rf "${TEST_DIR}"

echo "Test restore completed successfully"

Restore Procedures

Full Restore

# Restore into the running server (prompts unless --yes is given)
geode restore /backups/geode-full-20260128.backup \
  --server localhost:3141 \
  --yes

# Verify the restored store
geode query "CALL geode.storage.integrity() YIELD total_errors RETURN total_errors"

For an offline restore, stop the server, replace the data directory with the copy taken by tar, and start it again — the same procedure as an offline backup in reverse.

Restoring a Chain of Backups

Not applicable: every Geode backup is self-contained. Restore the one backup you want; there are no incremental files to apply on top of it, and geode restore has no --incremental flag.

Programmatic Restore

import asyncio

async def run(*args: str) -> str:
    proc = await asyncio.create_subprocess_exec(
        *args,
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.STDOUT,
    )
    out, _ = await proc.communicate()
    if proc.returncode != 0:
        raise RuntimeError(f"{' '.join(args)} failed: {out.decode()}")
    return out.decode()

async def restore_database(backup_path: str, server: str = "localhost:3141"):
    """Restore a database from a full backup file."""
    print(f"Restoring: {backup_path}")
    print(await run("geode", "backup", "info", backup_path))
    print(await run("geode", "restore", backup_path, "--server", server, "--yes"))

    # Confirm the restored store is self-consistent
    print(await run(
        "geode", "query", "--server", server,
        "CALL geode.storage.integrity() YIELD total_errors RETURN total_errors",
    ))

asyncio.run(restore_database("/backups/full/geode-full-20260128.backup"))

Disaster Recovery Planning

Recovery Time Objective (RTO) and Recovery Point Objective (RPO)

StrategyRTORPOCost
Standby replicaMinutesSecondsHigh
Hourly full backupHours1 hourMedium
Daily full backupHours-Days24 hoursLow

Because there is no WAL replay, the RPO of a backup-based strategy is exactly the interval between full backups. Sub-minute RPO needs replication, not backups.

Disaster Recovery Runbook

# Geode Disaster Recovery Runbook

## 1. Assess the Situation
- [ ] Identify the nature of the failure
- [ ] Determine data loss extent
- [ ] Notify stakeholders

## 2. Activate Recovery
- [ ] Access backup storage
- [ ] Identify latest valid backup
- [ ] Prepare recovery environment

## 3. Restore Database
- [ ] Deploy new Geode instance
- [ ] Restore from backup
- [ ] Confirm the restored store is consistent (`CALL geode.storage.integrity()`)
- [ ] Verify data integrity

## 4. Validate Recovery
- [ ] Run consistency checks
- [ ] Execute test queries
- [ ] Verify application connectivity

## 5. Resume Operations
- [ ] Update DNS/load balancer
- [ ] Monitor performance
- [ ] Document incident

## 6. Post-Incident
- [ ] Root cause analysis
- [ ] Update procedures
- [ ] Test improvements

Multi-Region Backup Strategy

import asyncio
import boto3
from datetime import datetime

async def run(*args: str) -> str:
    proc = await asyncio.create_subprocess_exec(
        *args,
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.STDOUT,
    )
    out, _ = await proc.communicate()
    if proc.returncode != 0:
        raise RuntimeError(f"{' '.join(args)} failed: {out.decode()}")
    return out.decode()

class MultiRegionBackupManager:
    def __init__(self):
        self.primary_region = "us-east-1"
        self.backup_regions = ["us-west-2", "eu-west-1"]
        self.s3_clients = {
            region: boto3.client('s3', region_name=region)
            for region in [self.primary_region] + self.backup_regions
        }

    async def create_and_replicate_backup(self):
        """Create backup and replicate to multiple regions."""
        timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
        local_path = f"/tmp/geode-backup-{timestamp}.backup"
        s3_key = f"backups/geode/full-{timestamp}.backup"

        # Create local backup with the CLI
        print("Creating backup...")
        await run("geode", "backup", "create",
                  "--output", local_path, "--compress")

        # Upload to primary region
        print(f"Uploading to {self.primary_region}...")
        self.s3_clients[self.primary_region].upload_file(
            local_path,
            f"geode-backups-{self.primary_region}",
            s3_key
        )

        # Replicate to backup regions
        for region in self.backup_regions:
            print(f"Replicating to {region}...")

            # Copy from primary to backup region
            self.s3_clients[region].copy_object(
                CopySource={
                    'Bucket': f"geode-backups-{self.primary_region}",
                    'Key': s3_key
                },
                Bucket=f"geode-backups-{region}",
                Key=s3_key
            )

        print(f"Backup replicated to {len(self.backup_regions)} regions")

        # Clean up local file
        import os
        os.remove(local_path)

    async def restore_from_nearest_region(self, server: str = "localhost:3141"):
        """Restore from the nearest available region."""
        # Check backup availability in each region
        for region in [self.primary_region] + self.backup_regions:
            bucket = f"geode-backups-{region}"
            try:
                response = self.s3_clients[region].list_objects_v2(
                    Bucket=bucket,
                    Prefix="backups/geode/full-",
                    MaxKeys=1
                )

                if response.get('Contents'):
                    latest = sorted(
                        response['Contents'],
                        key=lambda x: x['LastModified'],
                        reverse=True
                    )[0]

                    print(f"Restoring from {region}: {latest['Key']}")

                    # Download backup
                    local_path = f"/tmp/restore-{region}.backup"
                    self.s3_clients[region].download_file(
                        bucket,
                        latest['Key'],
                        local_path
                    )

                    # Restore into the running server
                    await run("geode", "restore", local_path,
                              "--server", server, "--yes")

                    print(f"Restore complete from {region}")
                    return True

            except Exception as e:
                print(f"Region {region} unavailable: {e}")
                continue

        print("No backup available in any region!")
        return False

async def main():
    manager = MultiRegionBackupManager()

    # Create and replicate backup
    await manager.create_and_replicate_backup()

asyncio.run(main())

Automated Backup Scheduling

Cron-Based Scheduling

# /etc/cron.d/geode-backup

# Full backup every day at 2 AM (every Geode backup is a full backup)
0 2 * * * geode /usr/local/bin/geode-full-backup.sh >> /var/log/geode-backup.log 2>&1

# Prune backups older than 30 days, weekly
0 4 * * 0 geode /usr/local/bin/geode backup cleanup --older-than 30d >> /var/log/geode-backup.log 2>&1

Systemd Timer

# /etc/systemd/system/geode-backup.service
[Unit]
Description=Geode Database Backup
After=geode.service

[Service]
Type=oneshot
User=geode
ExecStart=/usr/local/bin/geode-backup.sh
StandardOutput=journal
StandardError=journal
# /etc/systemd/system/geode-backup.timer
[Unit]
Description=Daily Geode Backup Timer

[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true

[Install]
WantedBy=timers.target
# Enable timer
systemctl enable geode-backup.timer
systemctl start geode-backup.timer

# Check timer status
systemctl list-timers | grep geode

Comprehensive Backup Script

#!/bin/bash
# /usr/local/bin/geode-backup.sh

set -euo pipefail

# Configuration
BACKUP_DIR="/backups/geode"
FULL_DIR="${BACKUP_DIR}/full"
RETENTION="30d"
GEODE_SERVER="${GEODE_SERVER:-localhost:3141}"

# Logging
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}

mkdir -p "${FULL_DIR}"

TIMESTAMP=$(date +%Y%m%d-%H%M%S)
BACKUP_FILE="${FULL_DIR}/geode-full-${TIMESTAMP}.backup"

log "Starting full backup"

# Every Geode backup is a full backup
geode backup create \
    --server "${GEODE_SERVER}" \
    --type full \
    --output "${BACKUP_FILE}" \
    --compress

# Read the header back (fails loudly if the file is unreadable)
log "Reading backup header..."
geode backup info "${BACKUP_FILE}"

SIZE=$(du -h "${BACKUP_FILE}" | cut -f1)
log "Backup complete: ${BACKUP_FILE} (${SIZE})"

# Retention
log "Removing backups older than ${RETENTION}..."
geode backup cleanup --older-than "${RETENTION}"

# Upload to remote storage (optional)
if [ -n "${S3_BUCKET:-}" ]; then
    log "Uploading to S3..."
    aws s3 cp "${BACKUP_FILE}" "s3://${S3_BUCKET}/backups/geode/"
fi

log "Backup job completed successfully"

Monitoring Backups

Backup Health Checks

import asyncio
import os
from datetime import datetime

async def check_backup_health(backup_dir: str):
    """Check backup health and alert on issues."""
    issues = []

    # Check for recent full backup
    full_dir = os.path.join(backup_dir, "full")
    if os.path.exists(full_dir):
        full_backups = sorted([
            f for f in os.listdir(full_dir) if f.endswith('.backup')
        ], reverse=True)

        if not full_backups:
            issues.append("No full backups found")
        else:
            latest_full = os.path.join(full_dir, full_backups[0])
            mtime = datetime.fromtimestamp(os.path.getmtime(latest_full))
            age_days = (datetime.now() - mtime).days

            if age_days > 1:
                issues.append(f"Latest backup is {age_days} days old")

    # Check disk space
    statvfs = os.statvfs(backup_dir)
    free_gb = (statvfs.f_frsize * statvfs.f_bavail) / (1024**3)
    if free_gb < 50:
        issues.append(f"Low disk space: {free_gb:.1f} GB free")

    # Report
    if issues:
        print("BACKUP HEALTH CHECK: ISSUES FOUND")
        for issue in issues:
            print(f"  - {issue}")
        return False
    else:
        print("BACKUP HEALTH CHECK: OK")
        return True

asyncio.run(check_backup_health("/backups/geode"))

Prometheus Metrics

# Expose backup metrics for Prometheus
from prometheus_client import Gauge, Counter, start_http_server
import os
from datetime import datetime

# Define metrics
backup_age_seconds = Gauge(
    'geode_backup_age_seconds',
    'Age of the latest backup in seconds',
    ['type']
)

backup_size_bytes = Gauge(
    'geode_backup_size_bytes',
    'Size of the latest backup in bytes',
    ['type']
)

backup_count = Gauge(
    'geode_backup_count',
    'Number of backup files',
    ['type']
)

backup_disk_free_bytes = Gauge(
    'geode_backup_disk_free_bytes',
    'Free disk space for backups'
)

def update_metrics(backup_dir: str):
    """Update Prometheus metrics."""
    for backup_type in ['full']:
        type_dir = os.path.join(backup_dir, backup_type)
        if not os.path.exists(type_dir):
            continue

        backups = [f for f in os.listdir(type_dir) if f.endswith('.backup')]

        # Count
        backup_count.labels(type=backup_type).set(len(backups))

        if backups:
            # Latest backup
            latest = max(backups, key=lambda f: os.path.getmtime(
                os.path.join(type_dir, f)))
            latest_path = os.path.join(type_dir, latest)

            # Age
            age = datetime.now().timestamp() - os.path.getmtime(latest_path)
            backup_age_seconds.labels(type=backup_type).set(age)

            # Size
            size = os.path.getsize(latest_path)
            backup_size_bytes.labels(type=backup_type).set(size)

    # Disk space
    statvfs = os.statvfs(backup_dir)
    free_bytes = statvfs.f_frsize * statvfs.f_bavail
    backup_disk_free_bytes.set(free_bytes)

# Start this exporter on its own port: 9090 is the conventional port for the
# server's own GEODE_METRICS_PORT listener.
start_http_server(9091)

# Update metrics periodically
import time
while True:
    update_metrics("/backups/geode")
    time.sleep(60)

Best Practices

Backup Checklist

  • Regular full backups (every backup is a full backup)
  • Backup interval matched to your RPO (there is no WAL replay to close the gap)
  • geode backup info after each backup to confirm a readable header
  • Test restores at least monthly
  • Off-site backup storage (different region/provider)
  • Encryption for backups containing sensitive data
  • Monitoring and alerting for backup failures
  • Documented recovery procedures
  • Regular DR drills

Security Considerations

Backup encryption is a property of the server, not a CLI flag: there is no --encrypt, --decrypt or --encryption-key-file option. When the server runs with Transparent Data Encryption (geode serve --tde-provider ...), the backup it produces is written encrypted and geode backup info reports Encrypted: true. See the KMS integration guide for key provider configuration and the tde.* procedures.

Protect the backup files themselves with ordinary filesystem controls, and use the TLS options (--ca-cert, --client-cert, --client-key) so the backup stream to the server is authenticated:

geode backup create \
  --server geode.internal:3141 \
  --output /backups/geode.backup \
  --compress \
  --ca-cert /etc/ssl/geode-ca.crt \
  --client-cert /etc/ssl/backup-client.crt \
  --client-key /etc/ssl/backup-client.key

chmod 600 /backups/geode.backup

Next Steps

Resources


Questions? Discuss backup strategies in our forum .