VPS Snaps

How to back up Apache CouchDB

CouchDB's documentation gives the simplest backup: copy its .couch files while it runs, because they are append-only, taking the view indexes in .shards before the databases in shards and the system databases beside them, plus the etc config directory. Restore by putting both back on a node with the same node name. Replication to a second CouchDB keeps a live copy, but it copies deletions too, so keep dated file copies as well.

9 min readUpdated Checked against official documentation

Pick a method

CouchDB 3.5.2 is current in October 2026. Four ways to keep a copy:

MethodWhat you getWatch out for
Copy the data files (this guide)Every database, the _users accounts and the indexes, taken while CouchDB runsRestore needs the same node name; copy views before databases
Replication to another CouchDBA live second copyDeletions and bad writes replicate too
couchbackup (IBM, open source)One text file per database, over HTTPNo attachments; needs Node.js 22 or 24
Disk snapshot (LVM, ZFS, cloud volume)A near-instant point-in-time copyFreeze the file system with fsfreeze if your platform needs it

For snapshots, see LVM and ZFS.

Find the files and the node name

Packages from the CouchDB repository (Debian, Ubuntu, RHEL) install to /opt/couchdb, and /opt/couchdb/data is a symlink to /var/lib/couchdb. Back up /var/lib/couchdb itself: tar stores a symlink as a link, not the files behind it. Then ask CouchDB for its node name, which a restore must match:

Terminal
curl -s -u admin http://127.0.0.1:5984/_node/_local

curl asks for the admin password. {"name":"[email protected]"} is the package default for a single node; it comes from the -name line in /opt/couchdb/etc/vm.args. What the data directory holds:

  • shards/<range>/<db>.<suffix>.couch: each database, split into shard files, one directory per hash range. _users and _replicator live here too.
  • .shards/: view and Mango index files, also sharded.
  • _dbs.couch and _nodes.couch at the top: the shard map, which records which node holds each shard, and the list of nodes.

sudo du -sh /var/lib/couchdb shows how much the archive may need.

Copy the files while CouchDB runs

CouchDB only appends to its files, so the docs say a copy taken at any time works. The order matters for recovery time: a view index a little older than its database is brought up to date on the next read, but one newer than its database is rebuilt from scratch, a slow and costly job on a large database. So copy .shards first. tar archives paths in the order you list them:

/usr/local/bin/couchdb-backup.sh
#!/bin/bash
set -eu
DATA=/var/lib/couchdb
DEST=/var/backups/couchdb
STAMP=$(date +%F-%H%M)
mkdir -p "$DEST"
cd "$DATA"

# Views first, then the databases, then the shard map and node list.
VIEWS=""
if [ -d .shards ]; then VIEWS=.shards; fi
rc=0
tar --exclude='*.compact*' -czf "$DEST/couchdb-data-$STAMP.tar.gz" $VIEWS shards *.couch || rc=$?
if [ "$rc" -gt 1 ]; then
  echo "$(date -Is) data copy failed (tar exit $rc)" >&2
  exit 1
fi

tar -czf "$DEST/couchdb-etc-$STAMP.tar.gz" -C /opt/couchdb etc
find "$DEST" -name 'couchdb-*.tar.gz' -mtime +1 -delete
echo "$(date -Is) created couchdb-data-$STAMP.tar.gz"
  • tar prints file changed as we read it and exits with 1 when CouchDB appends to a file during the copy. That is expected here, so the script fails only on exit 2, a real error.
  • --exclude='*.compact*' skips the temporary files compaction writes (.compact, .compact.data, .compact.meta, .compact.view); they disappear when compaction finishes.
  • It runs as root, since /var/lib/couchdb is readable only by the couchdb user and group.
  • The find line keeps about two days of archives on the server.
Terminal
sudo chmod 700 /usr/local/bin/couchdb-backup.sh
/etc/cron.d/couchdb-backup
30 2 * * * root /usr/local/bin/couchdb-backup.sh >> /var/log/couchdb-backup.log 2>&1

Then copy /var/backups/couchdb off the server (3-2-1 rule); cron schedules covers timing and failure alerts.

What the config archive holds

CouchDB reads default.ini, default.d/*.ini, local.ini and local.d/*.ini in that order, and writes changes made at runtime to the last file in that chain. Back up all of /opt/couchdb/etc, because the data alone won't come back as it was:

  • [admins]: server admins. They are not in _users. CouchDB replaces a plain-text password with a PBKDF2 hash when it restarts. Without an admin, CouchDB 3 refuses to start.
  • [chttpd_auth] secret: signs session cookies (the setting moved here from [couch_httpd_auth] in 3.2). CouchDB generates and saves one when none is set; with a different secret, users have to log in again.
  • [couchdb] uuid: the server's identifier. Replication IDs are built from it, so with a new UUID replications can't find their checkpoints and re-read their sources from the start.
  • vm.args: the node name (-name) and the Erlang cookie (-setcookie) that cluster nodes share.

The config archive holds password hashes, the cookie secret and the Erlang cookie. Encrypt it before it leaves the server.

Restore on a new server

Install the same CouchDB version or newer from the same repository, copy both archives across, then:

  1. Stop CouchDB: sudo systemctl stop couchdb.
  2. Restore the config: sudo tar -xzf couchdb-etc-2026-10-04-0230.tar.gz -C /opt/couchdb. On a newer version, unpack it elsewhere instead and copy across local.ini, local.d/ and the -name and -setcookie lines of vm.args, keeping the new default.ini.
  3. Move the fresh install's data aside and make an empty directory: sudo mv /var/lib/couchdb /var/lib/couchdb.fresh, then sudo install -d -o couchdb -g couchdb -m 750 /var/lib/couchdb.
  4. Unpack the data: sudo tar -xzf couchdb-data-2026-10-04-0230.tar.gz -C /var/lib/couchdb.
  5. Fix ownership: sudo chown -R couchdb:couchdb /var/lib/couchdb /opt/couchdb/etc.
  6. Start it: sudo systemctl start couchdb.
Terminal
curl -s -u admin http://127.0.0.1:5984/_all_dbs
Terminal
curl -s -u admin http://127.0.0.1:5984/orders

The first lists every database; the second returns doc_count for one, to compare with the old server. Restore onto a scratch server once a month and time it (testing restores).

Get back one database

You can't drop one database's shard files into a running node, because its entry in the shard map has to match. Restore the whole archive on a scratch server instead, then replicate the database you need across, under a new name:

Terminal
curl -s -u admin -X POST http://127.0.0.1:5984/_replicate -H 'Content-Type: application/json' -d '{"source": "http://admin:<password>@scratch.example.com:5984/orders", "target": "http://admin:<password>@127.0.0.1:5984/orders_restored", "create_target": true}'

Replicating the old copy back into the live database does not bring deleted documents back. A deletion is a newer revision of each document, so it wins. Replicate into a new database, check it, then point the application at it or copy the documents you need across.

Replication: a live copy, not a backup

The docs call replication to another CouchDB the simplest backup. A document in _replicator makes it persistent: it restarts with the server, while a POST to _replicate is gone after a restart.

Terminal
curl -s -u admin -X PUT http://127.0.0.1:5984/_replicator/orders-to-backup -H 'Content-Type: application/json' -d '{"source": "http://admin:<password>@127.0.0.1:5984/orders", "target": "http://admin:<password>@backup.example.com:5984/orders", "create_target": true, "continuous": true}'
  • continuous keeps sending changes as they happen; create_target creates the database on the other server if it is missing.
  • curl -s -u admin http://127.0.0.1:5984/_scheduler/docs/_replicator/orders-to-backup shows its state, which should be running. Deleting the document stops it.
  • Each task copies one database in one direction. Replicate _users too; server admins aren't in any database.

It isn't a backup on its own: documents deleted on the source are deleted on the target, and a bad bulk update arrives seconds later. For copies you can go back to, keep the file backups above, or run a one-shot replication (no continuous) each night into a dated database such as orders-2026-10-04.

couchbackup: one file per database

IBM's couchbackup (Apache 2.0, version 2.11.19) reads a database's changes feed over HTTP and writes the documents to a text file, so it doesn't depend on node names or shard layout. It does not support attachments: a backup of a database with attachments looks complete but can't be restored.

Terminal
npm install -g @cloudant/couchbackup
Terminal
couchbackup --db orders | gzip > /var/backups/couchdb/orders-2026-10-04.txt.gz

It reads the server from COUCH_URL, such as http://admin:<password>@127.0.0.1:5984; export it from a root-only file rather than typing it. --db names the database. Restoring needs a new, empty database:

Terminal
curl -s -u admin -X PUT http://127.0.0.1:5984/orders_restored
Terminal
gunzip -c /var/backups/couchdb/orders-2026-10-04.txt.gz | couchrestore --db orders_restored

CouchDB in Docker

The apache/couchdb image keeps data in /opt/couchdb/data and writes the admin (COUCHDB_USER, COUCHDB_PASSWORD) and secret (COUCHDB_SECRET) to /opt/couchdb/etc/local.d/docker.ini. Set DATA in the script to the volume's host path:

Terminal
docker volume inspect --format '{{ .Mountpoint }}' couchdb_data

Mount /opt/couchdb/etc/local.d to a host directory and archive that instead of /opt/couchdb/etc; otherwise runtime config, the UUID included, lives only in the container. On restore keep the same NODENAME, which sets -name couchdb@<NODENAME>: NODENAME=127.0.0.1 matches the package default when moving from a package install. More in Docker volume backups.

Clusters

Each node holds only its own shard replicas, and the shard map names nodes. Run the script on every node and restore each archive onto a node with the same name. For a different layout, replicate each database into the new cluster instead; replicating every database to a single-node backup server and backing that up also works.

Common errors

ErrorFix
internal_server_error : No DB shards could be opened. in the log, This database failed to load. in FauxtonThe node name changed, so the shard map points at a node that isn't there. Put the old -name back in vm.args and restart.
No Admin Account Found, aborting startup.The data came back without its config. Restore local.ini and local.d, or add an [admins] entry, and restart.
{database_does_not_exist,[{mem3_shards,load_shards_from_db,"_users" ..._users is missing: its shards weren't restored, or the node was never set up. Restore the whole data directory, or create _users and _replicator with curl -X PUT.
{"error":"not_found","reason":"Database does not exist."}Check the name in _all_dbs. If a restored database is missing from the list, restore the top-level .couch files (the shard map) too.
{"error":"unauthorized","reason":"You are not a server admin."}Replication and _node config requests need a server admin login.
db_not_found: could not open <url>The replication source or target is missing, or the URL or password is wrong. Add "create_target": true for a new target.
eacces in the startup outputRestored files belong to root. Run sudo chown -R couchdb:couchdb /var/lib/couchdb /opt/couchdb/etc.

Frequently asked questions

Can I copy CouchDB database files while it is running?
Yes. CouchDB's files are append-only, and its documentation says copying the .couch files at any time works. Copy the view indexes in .shards before the databases so views don't have to be rebuilt.
Is CouchDB replication a backup?
Not on its own. Deletions and bad writes replicate to the target within seconds. Keep dated copies too: file archives, or a one-shot replication into a new dated database each night.
Why does CouchDB say No DB shards could be opened after a restore?
The node name in vm.args differs from the one in the restored shard map. Set -name back to the old value, such as [email protected], and restart.
Does a CouchDB backup include users?
The _users database is in the data directory, so a file backup includes it. Server admins live in the ini files under /opt/couchdb/etc; back those up too.
How do I restore a single CouchDB database?
Restore the full backup on a scratch server, then replicate that one database to the live server under a new name with _replicate and create_target.

How this was checked

Commands, limits and prices were checked against these official pages, on October 4, 2026: