Home / AI & GenAI / Vector Databases

AI & GenAIRunbookAdvanced

Qdrant snapshots: backup and restore runbook for single-node and distributed clusters

Take, copy and restore Qdrant collection snapshots safely. Covers REST commands, per-node handling in a cluster, S3 storage, recovery priority, verifying the result and when re-embedding is the better choice.

Deepak Bhardwaj8 min read

Applies toQdrant 1.x single-node and distributed deployments, Qdrant REST API (check your version's API reference)

When to use this

You run Qdrant and need a tested way to back it up: before an upgrade, on a schedule or to recover from a bad write. Snapshots are the built-in method. Rebuilding by re-embedding your source documents is the other, and it's often slow and costly. See rebuild vs snapshot to decide between them.

What a snapshot contains

Type Scope Endpoint
Collection snapshot One collection, on one node /collections/{name}/snapshots
Shard snapshot One shard /collections/{name}/shards/{id}/snapshots
Full storage snapshot All collections on one node (single-node use) /snapshots

The most important fact: in a distributed cluster, a collection snapshot only holds what's on the node you called. To back up a whole collection, take a snapshot on every node, or per shard, and keep track of which belongs where.

Step 1: set variables and take a look

export Q=http://qdrant-0:6333
export KEY="$QDRANT_API_KEY"
export C=docs

curl -s -H "api-key: $KEY" "$Q/collections/$C" \
  | jq '.result | {status, points_count, segments_count, config: .config.params}'

curl -s -H "api-key: $KEY" "$Q/collections/$C/cluster" \
  | jq '.result | {peer_id, shard_count, local_shards, remote_shards}'

Record points_count and the configuration. You'll need them later for verification. status should be green before a backup.

Step 2: take a snapshot

curl -s -X POST -H "api-key: $KEY" \
  "$Q/collections/$C/snapshots?wait=true" | jq

Then list what's there:

curl -s -H "api-key: $KEY" "$Q/collections/$C/snapshots" | jq '.result'

The response shows name, size and creation_time. By default, files go in the node's ./snapshots directory, so check that there's enough free disk first. Snapshots can be as large as the data, and they use additional space temporarily while being created.

kubectl exec -n qdrant qdrant-0 -- df -h /qdrant/snapshots

Step 3: copy it off the node

A snapshot on the same disk isn't a backup. Download it:

SNAP=$(curl -s -H "api-key: $KEY" "$Q/collections/$C/snapshots" \
  | jq -r '.result | sort_by(.creation_time) | last | .name')

curl -f -H "api-key: $KEY" -o "$SNAP" \
  "$Q/collections/$C/snapshots/$SNAP"

sha256sum "$SNAP" > "$SNAP.sha256"
aws s3 cp "$SNAP" "s3://qdrant-backups/$C/$(date +%F)/"
aws s3 cp "$SNAP.sha256" "s3://qdrant-backups/$C/$(date +%F)/"

Then delete the local copy on the node, if you need the space:

curl -s -X DELETE -H "api-key: $KEY" "$Q/collections/$C/snapshots/$SNAP?wait=true"

Qdrant can also write snapshots straight to S3-compatible storage. Set it in the config:

storage:
  snapshots_config:
    snapshots_storage: s3
    s3_config:
      bucket: qdrant-backups
      region: ap-south-1
      access_key: ""
      secret_key: ""

Check the exact options for your version. Use a credential from a secret store, not a value in this file.

Step 4: back up a distributed cluster

Loop through every node:

#!/usr/bin/env bash
set -euo pipefail
DATE=$(date +%F)
for NODE in qdrant-0 qdrant-1 qdrant-2; do
  URL="http://$NODE.qdrant-headless:6333"
  NAME=$(curl -fsS -X POST -H "api-key: $KEY" \
    "$URL/collections/$C/snapshots?wait=true" | jq -r '.result.name')
  curl -fsS -H "api-key: $KEY" -o "/tmp/$NODE-$NAME" \
    "$URL/collections/$C/snapshots/$NAME"
  aws s3 cp "/tmp/$NODE-$NAME" "s3://qdrant-backups/$C/$DATE/$NODE/$NAME"
  curl -fsS -X DELETE -H "api-key: $KEY" "$URL/collections/$C/snapshots/$NAME?wait=true"
  rm -f "/tmp/$NODE-$NAME"
  echo "$NODE done: $NAME"
done

Keep the node name in the path, so you can restore the right file to the right place. Also save the output of /collections/$C/cluster from each node beside the files.

Warning

Node snapshots aren't taken at the same moment. Writes that land in between can leave small differences across nodes. For a clean point in time, pause writes during the backup, or plan to replay recent writes from your ingestion pipeline.

Step 5: restore a collection

From a URL or a path the node can read

curl -s -X PUT -H "api-key: $KEY" -H "Content-Type: application/json" \
  "$Q/collections/$C/snapshots/recover?wait=true" \
  -d '{"location": "https://backups.contoso.com/docs-snapshot.snapshot", "priority": "snapshot"}'

A file:/// location works too, for files on that node's own disk.

By uploading a file

curl -X POST -H "api-key: $KEY" \
  "$Q/collections/$C/snapshots/upload?priority=snapshot&wait=true" \
  -H "Content-Type: multipart/form-data" \
  -F "snapshot=@./docs-snapshot.snapshot"

The priority setting decides which data wins when a replica already exists:

Priority Effect Use when
snapshot The snapshot's data wins Restoring old data over current data
replica Existing replica data wins Adding a node to a healthy cluster
no_sync No sync with other replicas Special cases; read the docs first

Caution

Restoring overwrites the collection's data on that node, and any writes since the snapshot are lost. In a replicated collection, the default priority can make the old snapshot lose to the newer replica data, so nothing appears to change. Or, used wrongly, old data can replace good data. Before you begin, take a new snapshot of the current state, and stop the writers.

Step 6: restore across a whole cluster

  1. Stop writers, so ingestion can't change data mid-restore.
  2. Recreate the same topology, including the shard count and replication factor. Compare with what you saved in Step 4.
  3. Restore each node's file to the matching node, with priority=snapshot:
for NODE in qdrant-0 qdrant-1 qdrant-2; do
  FILE=$(aws s3 ls "s3://qdrant-backups/$C/2026-11-06/$NODE/" | awk '{print $4}')
  URL=$(aws s3 presign "s3://qdrant-backups/$C/2026-11-06/$NODE/$FILE" --expires-in 3600)
  curl -fsS -X PUT -H "api-key: $KEY" -H "Content-Type: application/json" \
    "http://$NODE.qdrant-headless:6333/collections/$C/snapshots/recover?wait=true" \
    -d "{\"location\": \"$URL\", \"priority\": \"snapshot\"}"
  echo "$NODE restored"
done
  1. Wait for the collection to return to green, and for shard transfers to finish:
curl -s -H "api-key: $KEY" "$Q/collections/$C/cluster" \
  | jq '.result | {local_shards, remote_shards, shard_transfers}'

A presigned URL is a temporary credential, so keep its expiry short and don't log it. See shards and replicas for how topology works.

Step 7: verify before you reopen

A successful API response only means the restore finished. Check the data too:

curl -s -H "api-key: $KEY" "$Q/collections/$C" \
  | jq '.result | {status, points_count, indexed_vectors_count}'

curl -s -X POST -H "api-key: $KEY" -H "Content-Type: application/json" \
  "$Q/collections/$C/points/count" -d '{"exact": true}' | jq
Check Expected
Status green (yellow may mean that it's still optimizing)
Exact point count Matches your record, allowing for later writes
Vector size and distance Same as before
Payload indexes Present, in payload_schema
Known-item search Returns the expected IDs
Retrieval evaluation Same recall as before

Run a known-item search with a vector you saved earlier:

curl -s -X POST -H "api-key: $KEY" -H "Content-Type: application/json" \
  "$Q/collections/$C/points/query" \
  -d '{"query": [0.12, 0.08, 0.33], "limit": 5, "with_payload": false}' | jq

Use a real vector of your collection's dimension there. Finally, re-apply writes made since the snapshot, from your ingestion pipeline. Then reopen traffic.

Schedule and retention

apiVersion: batch/v1
kind: CronJob
metadata:
  name: qdrant-backup
  namespace: qdrant
spec:
  schedule: "0 1 * * *"
  concurrencyPolicy: Forbid
  jobTemplate:
    spec:
      backoffLimit: 1
      template:
        spec:
          restartPolicy: Never
          containers:
            - name: backup
              image: registry.contoso.com/qdrant-backup:1.0
              command: ["/scripts/backup.sh"]

Set an S3 lifecycle rule to expire old copies, and alert if a backup hasn't landed by its expected time.

Common errors

Error Cause Fix
Timeout from wait=true Large collection Use a longer client timeout, and check the snapshot list afterwards
No space left on device Snapshot disk is full Free up space, and move the snapshot path to bigger storage
Restore fails on version Snapshot from a much newer Qdrant Restore to the same or a compatible version
Restore looks to have no effect replica priority won Use priority=snapshot, with care
Node can't fetch location Network or an expired URL Upload the file, or generate a new URL
Search is slow after restore Index still optimizing Wait for green

Check the release notes for snapshot compatibility before an upgrade, and restore to the same version when you can.

Prevent it next time

  • Do a restore test each quarter on a separate cluster, and time it. An untested backup is only a hope.
  • Take a snapshot right before each upgrade.
  • Keep the source documents and embedding model version, so you can rebuild as a last resort.
  • Related: Qdrant production setup, monitoring and vector database HA.