Skip to main content

Overview

Regular backups protect your Documenso data from hardware failures, software bugs, human error, and security incidents. This guide covers comprehensive backup strategies for all Documenso data.

What to Backup

A complete Documenso backup includes:
  1. PostgreSQL Database: User accounts, documents, templates, signatures, audit logs
  2. Document Files: PDF files (stored in database or S3)
  3. Configuration: Environment variables, Docker configs, custom settings
  4. Certificates: SSL certificates and signing certificates
The backup frequency depends on your usage patterns. For production systems with frequent document signing, consider automated daily backups with hourly incremental backups.

Database Backups

PostgreSQL Backup with Docker

For PostgreSQL running in Docker:
#!/bin/bash
# backup-database.sh

BACKUP_DIR="/backups/documenso"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
CONTAINER_NAME="documenso-db"  # Adjust to your container name
DB_NAME="documenso"
DB_USER="documenso"

# Create backup directory
mkdir -p "$BACKUP_DIR"

# Create backup
docker exec "$CONTAINER_NAME" pg_dump -U "$DB_USER" "$DB_NAME" | \
  gzip > "$BACKUP_DIR/documenso-db-$TIMESTAMP.sql.gz"

# Verify backup was created
if [ -f "$BACKUP_DIR/documenso-db-$TIMESTAMP.sql.gz" ]; then
  echo "Backup created: documenso-db-$TIMESTAMP.sql.gz"
  echo "Size: $(du -h $BACKUP_DIR/documenso-db-$TIMESTAMP.sql.gz | cut -f1)"
else
  echo "ERROR: Backup failed!"
  exit 1
fi

# Keep only last 30 days of backups
find "$BACKUP_DIR" -name "documenso-db-*.sql.gz" -mtime +30 -delete
Make the script executable and run it:
chmod +x backup-database.sh
./backup-database.sh

PostgreSQL Backup (Bare Metal)

For PostgreSQL running directly on the host:
#!/bin/bash
# backup-database-local.sh

BACKUP_DIR="/backups/documenso"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
DB_NAME="documenso"
DB_USER="documenso"
DB_HOST="localhost"
DB_PORT="5432"

mkdir -p "$BACKUP_DIR"

# Create backup with pg_dump
PGPASSWORD="$DB_PASSWORD" pg_dump \
  -h "$DB_HOST" \
  -p "$DB_PORT" \
  -U "$DB_USER" \
  -d "$DB_NAME" \
  --format=custom \
  --file="$BACKUP_DIR/documenso-db-$TIMESTAMP.dump"

# Compress the backup
gzip "$BACKUP_DIR/documenso-db-$TIMESTAMP.dump"

echo "Backup completed: documenso-db-$TIMESTAMP.dump.gz"

Automated Backups with Cron

Schedule automatic backups using cron:
# Edit crontab
crontab -e

# Add daily backup at 2 AM
0 2 * * * /path/to/backup-database.sh >> /var/log/documenso-backup.log 2>&1

# Or hourly backups during business hours (9 AM - 6 PM)
0 9-18 * * * /path/to/backup-database.sh >> /var/log/documenso-backup.log 2>&1

Continuous Archiving (Point-in-Time Recovery)

For mission-critical deployments, set up PostgreSQL WAL archiving:
# In postgresql.conf
wal_level = replica
archive_mode = on
archive_command = 'test ! -f /mnt/wal-archive/%f && cp %p /mnt/wal-archive/%f'
archive_timeout = 300  # Archive every 5 minutes
This enables point-in-time recovery to any moment between backups.

File Storage Backups

Documenso supports two storage backends for document files:

Database Storage (Default)

If using NEXT_PUBLIC_UPLOAD_TRANSPORT="database" (default), document files are stored in the DocumentData table. Your database backups include all files - no additional backup needed. Pros: Simple, everything in one backup
Cons: Large database size, slower queries on large datasets

S3 Storage

If using NEXT_PUBLIC_UPLOAD_TRANSPORT="s3", files are stored in S3 (or S3-compatible storage like MinIO).

AWS S3 Backup

#!/bin/bash
# backup-s3.sh

BUCKET_NAME="documenso-documents"
BACKUP_DIR="/backups/documenso/s3"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)

mkdir -p "$BACKUP_DIR"

# Sync entire bucket to local backup
aws s3 sync s3://$BUCKET_NAME "$BACKUP_DIR/$TIMESTAMP"

# Or create a point-in-time snapshot using versioning
# (Ensure S3 versioning is enabled on the bucket)
aws s3api put-bucket-versioning \
  --bucket $BUCKET_NAME \
  --versioning-configuration Status=Enabled

echo "S3 backup completed: $BACKUP_DIR/$TIMESTAMP"

MinIO Backup (Self-Hosted S3)

#!/bin/bash
# backup-minio.sh

MINIO_ALIAS="documenso-minio"
BUCKET_NAME="documenso"
BACKUP_DIR="/backups/documenso/minio"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)

mkdir -p "$BACKUP_DIR"

# Configure MinIO client (mc) if not already done
# mc alias set $MINIO_ALIAS http://localhost:9000 $MINIO_ACCESS_KEY $MINIO_SECRET_KEY

# Mirror bucket to local backup
mc mirror $MINIO_ALIAS/$BUCKET_NAME "$BACKUP_DIR/$TIMESTAMP"

echo "MinIO backup completed: $BACKUP_DIR/$TIMESTAMP"
For S3/MinIO, enable versioning on your bucket. This provides automatic backups of every file version and protects against accidental deletion.

Configuration Backups

Backup your configuration files:
#!/bin/bash
# backup-config.sh

BACKUP_DIR="/backups/documenso/config"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
DOCUMENSO_DIR="/path/to/documenso"

mkdir -p "$BACKUP_DIR/$TIMESTAMP"

# Backup environment variables
cp "$DOCUMENSO_DIR/.env" "$BACKUP_DIR/$TIMESTAMP/.env"

# Backup Docker Compose configuration
cp "$DOCUMENSO_DIR/docker-compose.yml" "$BACKUP_DIR/$TIMESTAMP/docker-compose.yml"

# Backup any custom configuration files
cp -r "$DOCUMENSO_DIR/config" "$BACKUP_DIR/$TIMESTAMP/config" 2>/dev/null || true

# Create a tarball
tar -czf "$BACKUP_DIR/config-$TIMESTAMP.tar.gz" -C "$BACKUP_DIR" "$TIMESTAMP"
rm -rf "$BACKUP_DIR/$TIMESTAMP"

echo "Configuration backup: config-$TIMESTAMP.tar.gz"
Configuration files contain sensitive data (passwords, API keys, secrets). Store these backups securely with encryption and restricted access.

Certificate Backups

Backup signing and SSL certificates:
#!/bin/bash
# backup-certificates.sh

BACKUP_DIR="/backups/documenso/certificates"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)

mkdir -p "$BACKUP_DIR"

# Backup signing certificate
if [ -f "/path/to/cert.p12" ]; then
  cp "/path/to/cert.p12" "$BACKUP_DIR/signing-cert-$TIMESTAMP.p12"
fi

# Backup SSL certificates (if using local certs)
if [ -d "/etc/letsencrypt/live/your-domain.com" ]; then
  tar -czf "$BACKUP_DIR/ssl-certs-$TIMESTAMP.tar.gz" \
    -C /etc/letsencrypt/live your-domain.com
fi

echo "Certificate backup completed"

Complete Backup Script

Combine all backups into a single script:
#!/bin/bash
# complete-backup.sh - Full Documenso backup

set -e  # Exit on error

BACKUP_ROOT="/backups/documenso"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
LOG_FILE="/var/log/documenso-backup.log"

log() {
  echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

log "Starting Documenso backup..."

# 1. Backup database
log "Backing up database..."
mkdir -p "$BACKUP_ROOT/database"
docker exec documenso-db pg_dump -U documenso documenso | \
  gzip > "$BACKUP_ROOT/database/documenso-db-$TIMESTAMP.sql.gz"

# 2. Backup S3 files (if applicable)
if grep -q 'NEXT_PUBLIC_UPLOAD_TRANSPORT="s3"' .env; then
  log "Backing up S3 storage..."
  mkdir -p "$BACKUP_ROOT/s3"
  aws s3 sync s3://documenso-bucket "$BACKUP_ROOT/s3/$TIMESTAMP"
fi

# 3. Backup configuration
log "Backing up configuration..."
mkdir -p "$BACKUP_ROOT/config"
tar -czf "$BACKUP_ROOT/config/config-$TIMESTAMP.tar.gz" \
  .env docker-compose.yml

# 4. Backup certificates
log "Backing up certificates..."
mkdir -p "$BACKUP_ROOT/certificates"
cp /path/to/cert.p12 "$BACKUP_ROOT/certificates/cert-$TIMESTAMP.p12" 2>/dev/null || true

# 5. Create manifest file
log "Creating backup manifest..."
cat > "$BACKUP_ROOT/manifest-$TIMESTAMP.txt" <<EOF
Documenso Backup Manifest
Created: $(date)
Version: $(docker exec documenso cat package.json | grep version | head -1 | cut -d'"' -f4)

Files:
$(ls -lh $BACKUP_ROOT/database/documenso-db-$TIMESTAMP.sql.gz)
$(ls -lh $BACKUP_ROOT/config/config-$TIMESTAMP.tar.gz)
EOF

# 6. Cleanup old backups (keep last 30 days)
log "Cleaning up old backups..."
find "$BACKUP_ROOT" -type f -mtime +30 -delete

log "Backup completed successfully!"
log "Location: $BACKUP_ROOT"

Restore Procedures

Restore Database

Restore a database backup:
#!/bin/bash
# restore-database.sh

BACKUP_FILE="$1"  # Path to backup file

if [ -z "$BACKUP_FILE" ]; then
  echo "Usage: $0 <backup-file.sql.gz>"
  exit 1
fi

read -p "⚠️  This will OVERWRITE the current database. Continue? (yes/no): " confirm
if [ "$confirm" != "yes" ]; then
  echo "Restore cancelled."
  exit 0
fi

# Stop Documenso to prevent connections
docker compose stop documenso

# Drop and recreate database
docker exec documenso-db psql -U documenso -c "DROP DATABASE IF EXISTS documenso;"
docker exec documenso-db psql -U documenso -c "CREATE DATABASE documenso;"

# Restore from backup
gunzip -c "$BACKUP_FILE" | docker exec -i documenso-db psql -U documenso -d documenso

# Start Documenso
docker compose start documenso

echo "✅ Database restored successfully!"
echo "🔍 Verify at: http://localhost:3000/api/health"

Restore S3 Files

#!/bin/bash
# restore-s3.sh

BACKUP_DIR="$1"  # Directory containing S3 backup
BUCKET_NAME="documenso-documents"

if [ -z "$BACKUP_DIR" ]; then
  echo "Usage: $0 <backup-directory>"
  exit 1
fi

read -p "⚠️  This will overwrite current S3 files. Continue? (yes/no): " confirm
if [ "$confirm" != "yes" ]; then
  echo "Restore cancelled."
  exit 0
fi

# Sync backup to S3
aws s3 sync "$BACKUP_DIR" s3://$BUCKET_NAME --delete

echo "✅ S3 files restored successfully!"

Restore Configuration

# Extract configuration backup
tar -xzf config-20260304-120000.tar.gz

# Copy files to Documenso directory
cp .env /path/to/documenso/.env
cp docker-compose.yml /path/to/documenso/docker-compose.yml

# Restart to apply configuration
docker compose restart

Complete Restore

Full disaster recovery from backups:
1

Prepare clean environment

# Stop any running Documenso containers
docker compose down

# Clean volumes (optional - only if doing full restore)
docker volume rm documenso-db-data
2

Restore configuration

tar -xzf config-20260304-120000.tar.gz
cp .env /path/to/documenso/.env
cp docker-compose.yml /path/to/documenso/docker-compose.yml
3

Start database container

docker compose up -d documenso-db
# Wait for database to be ready
sleep 10
4

Restore database

gunzip -c database/documenso-db-20260304-120000.sql.gz | \
  docker exec -i documenso-db psql -U documenso -d documenso
5

Restore S3 files (if applicable)

aws s3 sync s3-backup/20260304-120000 s3://documenso-bucket
6

Restore certificates

cp certificates/cert-20260304-120000.p12 /path/to/cert.p12
# Update NEXT_PRIVATE_SIGNING_LOCAL_FILE_PATH in .env if needed
7

Start Documenso

docker compose up -d documenso

# Check logs
docker compose logs -f documenso

# Verify health
curl http://localhost:3000/api/health
8

Verify functionality

  • Login with an existing account
  • Open and view existing documents
  • Test signing a document
  • Check API endpoints

Off-Site Backups

Store backups in multiple locations for disaster recovery:

Cloud Storage

#!/bin/bash
# sync-backups-to-cloud.sh

BACKUP_DIR="/backups/documenso"

# AWS S3
aws s3 sync "$BACKUP_DIR" s3://company-backups/documenso/

# Google Cloud Storage
# gsutil rsync -r "$BACKUP_DIR" gs://company-backups/documenso/

# Azure Blob Storage
# az storage blob upload-batch -d documenso-backups -s "$BACKUP_DIR"

echo "Backups synced to cloud storage"

Remote Server (rsync)

#!/bin/bash
# sync-backups-remote.sh

BACKUP_DIR="/backups/documenso"
REMOTE_USER="backup-user"
REMOTE_HOST="backup-server.example.com"
REMOTE_DIR="/backup-storage/documenso"

# Sync via rsync over SSH
rsync -avz --delete \
  -e "ssh -i /root/.ssh/backup_key" \
  "$BACKUP_DIR/" \
  "$REMOTE_USER@$REMOTE_HOST:$REMOTE_DIR/"

echo "Backups synced to remote server"

Backup Encryption

Encrypt backups before storing:
#!/bin/bash
# encrypt-backup.sh

BACKUP_FILE="$1"
ENCRYPTION_KEY="/secure/path/backup-encryption.key"

# Encrypt with GPG
gpg --symmetric --cipher-algo AES256 --output "$BACKUP_FILE.gpg" "$BACKUP_FILE"

# Or use OpenSSL
openssl enc -aes-256-cbc -salt -pbkdf2 \
  -in "$BACKUP_FILE" \
  -out "$BACKUP_FILE.enc" \
  -pass file:"$ENCRYPTION_KEY"

# Remove unencrypted backup
rm "$BACKUP_FILE"

echo "Backup encrypted: $BACKUP_FILE.enc"
Decrypt when restoring:
# Decrypt with GPG
gpg --decrypt backup.sql.gz.gpg > backup.sql.gz

# Or with OpenSSL
openssl enc -aes-256-cbc -d -pbkdf2 \
  -in backup.sql.gz.enc \
  -out backup.sql.gz \
  -pass file:/secure/path/backup-encryption.key

Backup Testing

Regularly test your backups to ensure they work:
1

Schedule quarterly restore tests

Test full restore in a staging environment every 3 months
2

Verify backup integrity

# Test database backup
gunzip -t backup.sql.gz

# Test tarball
tar -tzf config-backup.tar.gz > /dev/null
3

Document restore procedures

Keep step-by-step restore documentation accessible offline
4

Test disaster recovery time

Measure how long a complete restore takes and plan accordingly

Backup Best Practices

  • 3 copies of your data (production + 2 backups)
  • 2 different media types (e.g., disk + cloud)
  • 1 off-site backup (different physical location)
Manual backups are unreliable. Use cron, systemd timers, or cloud backup services to automate:
  • Database backups
  • File backups
  • Configuration backups
  • Off-site sync
Set up alerts for:
  • Failed backups
  • Backup size anomalies (too large or too small)
  • Missing backups (scheduled job didn’t run)
  • Disk space on backup storage
Balance storage costs with recovery needs:
  • Hourly: Keep last 24 hours
  • Daily: Keep last 30 days
  • Weekly: Keep last 12 weeks
  • Monthly: Keep last 12 months
  • Yearly: Keep indefinitely (if required for compliance)
Each backup should include metadata:
  • Timestamp
  • Documenso version
  • Database schema version
  • Backup type (full, incremental)
  • File checksums (for integrity verification)

Backup Storage Requirements

Estimate storage needs based on your usage:
Documents/MonthDatabase SizeS3 StorageMonthly Backup Growth
100~500 MB~2 GB~3 GB
1,000~2 GB~20 GB~25 GB
10,000~15 GB~200 GB~250 GB
Database backups compress well (typically 5-10x compression). S3 files (PDFs) don’t compress much since PDFs are already compressed.

Updates

Learn how to safely update Documenso

Monitoring

Set up monitoring and alerting

Troubleshooting

Common backup and restore issues

Database Configuration

PostgreSQL setup and optimization