Intro
Node.js applications often hold critical data in files, databases, and in-memory state. Losing this data can cause downtime, revenue loss, and user frustration. A robust backup and restore strategy is essential for any production Node.js deployment.
This guide walks through a practical approach to backing up and restoring Node.js applications. We cover environment inventory, safe configuration paths, verification, failure modes, and an operations checklist. By the end, you will have a clear, step-by-step plan to protect your Node.js application data.
We focus on a typical Node.js setup: a Linux server running a Node.js process, using a local filesystem for application files and a database (e.g., MongoDB or PostgreSQL) for persistent data. The examples use simple, widely available tools like tar, pg_dump, and Node.js scripts.
Version and Environment Inventory
Before implementing any backup strategy, document your environment. This helps ensure that backups are complete and restorable. A clear inventory also assists during incident response, when you need to know exactly what to restore and how.
Host and OS: Note the server's operating system and version. Example: Ubuntu 22.04 LTS.
Node.js version and global packages: Use node --version to check the Node.js version. Record globally installed packages with npm list -g --depth=0.
Application details: Identify the application directory (e.g., /var/www/myapp), the user running the Node.js process (e.g., nodeuser), and the process manager (e.g., systemd, PM2). This information is crucial for correct permissions and service restart during restore.
Database and other services: Record the database type, version, connection string (without credentials if possible), and any external services like Redis or Elasticsearch. For example: PostgreSQL 14.8, host db.internal, port 5432.
Configuration files: List all environment-specific configuration files, such as .env, config.json, or config/production.json. These must be included in backups. Note their paths and any secrets they contain (passwords, API keys).
Prerequisites for backup tools: Ensure that standard utilities are installed: tar, gzip, rsync, and database-specific tools like pg_dump or mongodump. Check versions to avoid compatibility surprises: tar --version, pg_dump --version.
Example inventory command:
node --version && npm --version && systemctl status myapp --no-pager
Expected output (versions may vary):
v18.17.1
9.6.7
● myapp.service - My Node.js App
Loaded: loaded (/etc/systemd/system/myapp.service; enabled)
Active: active (running) since Tue 2025-03-25 10:00:00 UTC; 2h ago
Keep this inventory in a version-controlled document (e.g., backup-inventory.md) and update it whenever the environment changes.
Safe Configuration Path
Start with a narrow, measurable pilot backup that is easy to inspect locally. This reduces risk and builds confidence. A pilot backup covers only the essential application data, not the entire server, and can be restored and validated quickly.
Choose backup scope: For the first pilot, back up only the application directory and the database, not the entire server. This keeps the backup size manageable and simplifies validation.
Create a backup script that uses tar for files and a database dump tool. The script should be idempotent and safe to run repeatedly.
Example script backup.sh:
#!/bin/bash
# Backup script for Node.js app
set -e
APP_DIR="/var/www/myapp"
BACKUP_ROOT="/backups"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="$BACKUP_ROOT/myapp_$DATE"
mkdir -p "$BACKUP_DIR"
# 1. Backup application files
tar -czf "$BACKUP_DIR/app_files.tar.gz" -C "$APP_DIR" .
# 2. Backup PostgreSQL database (example)
PGPASSWORD="$DB_PASSWORD" pg_dump -U "$DB_USER" -h "$DB_HOST" "$DB_NAME" > "$BACKUP_DIR/db_dump.sql"
# 3. Create a manifest
cat > "$BACKUP_DIR/manifest.txt" <<EOF
Backup created: $DATE
Node.js version: $(node --version)
Files backup: app_files.tar.gz
Database backup: db_dump.sql
EOF
echo "Backup completed: $BACKUP_DIR"
Run the script as a user with appropriate permissions (often root or the application user). Example execution and output:
sudo ./backup.sh
Backup completed: /backups/myapp_20250325_120000
Verify the backup contents locally before relying on it. List the files in the tar archive and check the database dump header.
tar -tzf /backups/myapp_20250325_120000/app_files.tar.gz | head -5
head -10 /backups/myapp_20250325_120000/db_dump.sql
Expected output (example):
package.json
server.js
config/
config/production.json
.env
--
-- PostgreSQL database dump
--
Schedule automatic backups using cron. Add a line to /etc/crontab or use crontab -e:
0 2 * * * root /usr/local/bin/backup.sh >> /var/log/myapp_backup.log 2>&1
This runs the backup daily at 2:00 AM. Ensure the log file is rotated to avoid filling disk space.
For MongoDB, use mongodump instead of pg_dump. Example:
mongodump --uri="mongodb://$DB_USER:$DB_PASSWORD@$DB_HOST:27017/$DB_NAME" --out="$BACKUP_DIR/mongodump"
For large databases, consider incremental backups or using pg_dump --format=custom for compression and parallel restore.
Verification and Diagnostics
Backups are only useful if they can be restored. Regularly test restoration in a staging environment. This section covers creating a restore script, running it, and validating the restored application.
Create a restore script that performs a dry run first. A dry run extracts files to a temporary location and does not affect production.
Example restore script restore.sh:
#!/bin/bash
# Restore script for Node.js app
set -e
BACKUP_DIR="$1"
RESTORE_APP_DIR="/tmp/restore_test/app"
RESTORE_DB_NAME="myapp_restore_test"
# Extract files
tar -xzf "$BACKUP_DIR/app_files.tar.gz" -C "$RESTORE_APP_DIR"
# Restore database (example PostgreSQL)
PGPASSWORD="$DB_PASSWORD" psql -U "$DB_USER" -h "$DB_HOST" -d postgres -c "DROP DATABASE IF EXISTS $RESTORE_DB_NAME;"
PGPASSWORD="$DB_PASSWORD" psql -U "$DB_USER" -h "$DB_HOST" -d postgres -c "CREATE DATABASE $RESTORE_DB_NAME;"
PGPASSWORD="$DB_PASSWORD" psql -U "$DB_USER" -h "$DB_HOST" -d "$RESTORE_DB_NAME" < "$BACKUP_DIR/db_dump.sql"
echo "Restore test completed. Check application and database."
Run the restore test and verify that the Node.js application starts correctly with the restored data. Use a separate staging environment or a non-standard port to avoid conflicts.
Validation checks:
- Compare file counts and sizes between backup and original.
- Run the Node.js application using the restored files and database in a test environment.
- Spot-check database records or run application-specific health checks.
Example validation commands:
# Count files in backup vs original
find /var/www/myapp -type f | wc -l
tar -tzf /backups/myapp_20250325_120000/app_files.tar.gz | wc -l
# Check database record count
PGPASSWORD="$DB_PASSWORD" psql -U "$DB_USER" -h "$DB_HOST" -d "$RESTORE_DB_NAME" -c "SELECT count(*) FROM users;"
Expected consistent outputs. If file counts differ, investigate missing files. If database record count differs, check for dump errors.
For a more thorough test, start the Node.js application with the restored files and point it to the restored database. Example:
cd /tmp/restore_test/app
NODE_ENV=test DB_HOST=localhost DB_NAME=myapp_restore_test node server.js
Monitor startup logs and run a smoke test (e.g., curl http://localhost:3000/health).
Log verification results to track backup health over time. Create a simple log entry or use a monitoring tool. Example:
echo "$(date) Restore test passed" >> /var/log/myapp_restore_test.log
Automate restore testing monthly or after significant changes.
Failure Modes and Recovery
Backups can fail silently, and restores can be incomplete. Prepare for common failure modes. Understanding these helps you build resilience and respond quickly.
Common failure modes:
- Backup script fails due to insufficient disk space.
- Database dump fails due to connection issues or permissions.
- Backup files are corrupted.
- Restore fails due to version mismatches.
- In-memory state is not captured, leading to data loss.
- Backup process runs but exits with non-zero status, yet cron does not alert.
- Encryption or compression errors make backups unreadable.
Recovery and rollback strategies:
- Maintain multiple backup generations (e.g., daily for 7 days, weekly for 4 weeks).
- Store backups off-site or in a separate location, preferably a different availability zone or cloud provider.
- Have a documented process to roll back to a previous backup.
- For Node.js applications, consider using a process manager like PM2 or systemd to quickly restart with previous code if a new deployment fails.
Example rollback procedure:
- Stop the current application.
- Restore files from the last known good backup.
- Restore database if necessary.
- Start the application and verify.
Testing failure recovery: Simulate a failure by deleting a file and restoring from backup. Record the time to recover. This practice ensures your team knows the steps and can meet recovery time objectives (RTO).
In-memory state: If your application relies on in-memory caches or sessions, ensure they can be rebuilt after restore. For example, if using Redis, back up Redis data separately using redis-cli save and copy the RDB file. Example:
redis-cli save
cp /var/lib/redis/dump.rdb /backups/myapp_$DATE/redis.rdb
Alternatively, treat in-memory state as ephemeral and ensure your application can rebuild it from the database after restart.
Handling backup failures: Set up alerts for backup job failures. In cron, redirect errors to a separate log and monitor it. For example, modify the cron line:
0 2 * * * root /usr/local/bin/backup.sh > /var/log/myapp_backup.log 2> /var/log/myapp_backup_error.log || echo "Backup failed" | mail -s "Backup failure" [email protected]
Implement a retention policy. Example script to prune old backups:
#!/bin/bash
# Remove backups older than 7 days
find /backups -maxdepth 1 -type d -name "myapp_*" -mtime +7 -exec rm -rf {} \;
For off-site storage, use rsync to copy backups to a remote server or cloud storage. Example:
rsync -avz /backups/ user@backup-server:/remote-backups/
Operations Checklist
Use this checklist to ensure your backup and restore process remains effective. Note that the table below is in Markdown format.
| Task | Frequency | Responsible |
|---|---|---|
| Run full backup | Daily | Ops/Dev |
| Verify backup integrity (list files, check dump) | Weekly | Ops/Dev |
| Test restore in staging | Monthly | Dev team |
| Rotate and prune old backups | Weekly | Ops/Dev |
| Update documentation on environment changes | On change | Dev team |
| Review backup logs for errors | Daily | Ops/Dev |
| Test disaster recovery drill | Quarterly | All team |
| Check off-site backup replication | Daily | Ops/Dev |
Automate where possible using cron jobs or scheduled pipelines.
Monitor backup metrics such as duration, size, and success/failure. Use a simple log or a monitoring system like Prometheus with a custom exporter.
Keep the restore process simple and documented so any team member can execute it under pressure. Store a printed copy of the runbook in a safe place.
Conclusion
A solid backup and restore strategy is critical for Node.js applications. Start with a small, verifiable backup, automate it, and regularly test restoration. Document your environment, use simple tools, and monitor for failures. By following this guide, you can minimize data loss and downtime.
Next steps:
- Create an inventory of your Node.js environment.
- Implement the backup script for your application and database.
- Schedule regular backups and verification.
- Perform a full restore test in a staging environment.
- Document and practice rollback procedures.
Remember, backups are only as good as your ability to restore them. Test regularly.