Skip to main content

๐Ÿ“š BookStack Backup Guide (Using systemd, with Auto-Cleanup)

This document explains how to back up a BookStack instance using a backup script, a systemd service, and a systemd timer, plus an automatic cleanup timer that deletes old backups.

No cron is used โ€” fully systemd-native.


โœ… What Gets Backed Up?

A full BookStack backup requires:

  1. Database dump (MySQL/MariaDB) โ†’ contains all pages, books, users, settings
  2. Application files โ†’ BookStack codebase
  3. Upload files โ†’ images + attachments
  4. Storage files โ†’ internal uploads, custom icons, etc.

๐Ÿ“ 1. Install the Backup Script

Create the script at:

/usr/local/bin/bookstack-backup.sh

Script content

Update DB_NAME, DB_USER, DB_PASS, BACKUP_DIR, and BOOKSTACK_DIR to match your setup.

#!/usr/bin/env bash
set -euo pipefail

# === CONFIG ===
BACKUP_DIR="/backup/bookstack"
BOOKSTACK_DIR="/var/www/bookstack"
DB_NAME="bookstack"
DB_USER="bookstack"
DB_PASS="YOUR_DB_PASSWORD"

DATE=$(date +"%Y-%m-%d_%H-%M-%S")
TARGET_DIR="$BACKUP_DIR/$DATE"

mkdir -p "$TARGET_DIR"

echo "[*] $(date) - Dumping database..."
mysqldump -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" > "$TARGET_DIR/db.sql"

echo "[*] $(date) - Copying BookStack files..."
rsync -a "$BOOKSTACK_DIR" "$TARGET_DIR/app"

echo "[*] $(date) - Creating archive..."
cd "$BACKUP_DIR"
tar -czf "bookstack_$DATE.tar.gz" "$DATE"

echo "[*] $(date) - Cleaning up temp folder..."
rm -rf "$TARGET_DIR"

echo "[+] $(date) - Backup complete: $BACKUP_DIR/bookstack_$DATE.tar.gz"

Make it executable:

chmod +x /usr/local/bin/bookstack-backup.sh

๐Ÿ› ๏ธ 2. systemd Service (Backup Runner)

Create:

/etc/systemd/system/bookstack-backup.service

With:

[Unit]
Description=BookStack backup
Wants=network-online.target
After=network-online.target mariadb.service mysql.service

[Service]
Type=oneshot
ExecStart=/usr/local/bin/bookstack-backup.sh
User=root
Group=root

# Optional: keep system responsive
Nice=10
IOSchedulingClass=best-effort
IOSchedulingPriority=7

[Install]
WantedBy=multi-user.target

Test it manually:

systemctl daemon-reload
systemctl start bookstack-backup.service
journalctl -u bookstack-backup.service -e

โฐ 3. systemd Timer (Daily Backup at 02:00)

Create:

/etc/systemd/system/bookstack-backup.timer

With:

[Unit]
Description=Daily BookStack backup at 02:00

[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
Unit=bookstack-backup.service

[Install]
WantedBy=timers.target

Enable the timer:

systemctl daemon-reload
systemctl enable --now bookstack-backup.timer

Check timers:

systemctl list-timers | grep bookstack

๐Ÿงน 4. Auto-Delete Old Backups (Cleanup Timer)

This step adds:

  • a cleanup script that deletes backup files older than 30 days
  • a systemd service to run the script
  • a systemd timer to schedule cleanup (default: daily at 03:00)

You can adjust the retention days and schedule as needed.

4.1 Cleanup Script

Create:

/usr/local/bin/bookstack-backup-cleanup.sh

With:

#!/usr/bin/env bash
set -euo pipefail

# === CONFIG ===
BACKUP_DIR="/backup/bookstack"
RETENTION_DAYS=30

echo "[*] $(date) - Cleaning up BookStack backups older than ${RETENTION_DAYS} days in ${BACKUP_DIR}..."

if [ ! -d "$BACKUP_DIR" ]; then
  echo "[!] Backup directory ${BACKUP_DIR} does not exist, nothing to clean."
  exit 0
fi

# Delete .tar.gz archives older than RETENTION_DAYS
find "$BACKUP_DIR" -maxdepth 1 -type f -name "bookstack_*.tar.gz" -mtime +$RETENTION_DAYS -print -delete

# Optionally clean leftover dated directories (should normally not exist)
find "$BACKUP_DIR" -maxdepth 1 -type d -regex '.*/[0-9-]\{10\}_[0-9:-]\{8\}' -mtime +$RETENTION_DAYS -print -exec rm -rf {} +

echo "[+] $(date) - Cleanup complete."

Make it executable:

chmod +x /usr/local/bin/bookstack-backup-cleanup.sh

To change retention, edit RETENTION_DAYS=30 (for example, to 7 for one week).


4.2 Cleanup systemd Service

Create:

/etc/systemd/system/bookstack-backup-cleanup.service

With:

[Unit]
Description=Cleanup old BookStack backups

[Service]
Type=oneshot
ExecStart=/usr/local/bin/bookstack-backup-cleanup.sh
User=root
Group=root

Nice=10
IOSchedulingClass=best-effort
IOSchedulingPriority=7

[Install]
WantedBy=multi-user.target

Test it:

systemctl daemon-reload
systemctl start bookstack-backup-cleanup.service
journalctl -u bookstack-backup-cleanup.service -e

4.3 Cleanup systemd Timer (Daily at 03:00)

Create:

/etc/systemd/system/bookstack-backup-cleanup.timer

With:

[Unit]
Description=Daily cleanup of old BookStack backups at 03:00

[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
Unit=bookstack-backup-cleanup.service

[Install]
WantedBy=timers.target

Enable the timer:

systemctl daemon-reload
systemctl enable --now bookstack-backup-cleanup.timer

Check:

systemctl list-timers | grep bookstack-backup-cleanup

๐Ÿ”„ Restore Procedure

From a backup file like:

/backup/bookstack/bookstack_YYYY-MM-DD_HH-MM-SS.tar.gz

1. Extract the backup archive

cd /backup/bookstack
tar -xzf bookstack_YYYY-MM-DD_HH-MM-SS.tar.gz
cd YYYY-MM-DD_HH-MM-SS

2. Restore BookStack files

rsync -a app/ /var/www/bookstack/
chown -R www-data:www-data /var/www/bookstack

3. Restore the database

mysql -u bookstack -p bookstack < db.sql

Restart PHP-FPM / webserver if needed:

systemctl restart php-fpm apache2 nginx

(Use the service(s) appropriate for your setup.)


  • Sync /backup/bookstack to offsite storage (S3, Minio, Backblaze, rsync to another host)
  • Keep at least 7โ€“30 daily backups depending on your risk tolerance
  • If using ZFS/Btrfs:
    • Combine filesystem snapshots with this logical backup
    • Optionally snapshot the backup dataset itself

โœ… Summary

Component Backed Up / Managed By
Database mysqldump in bookstack-backup.sh
App Files rsync in bookstack-backup.sh
Uploads & Storage Included in app directory
Backup Automation bookstack-backup.service + .timer
Cleanup Automation bookstack-backup-cleanup.service + .timer
Retention RETENTION_DAYS in cleanup script

This setup is:

  • Fully systemd-native (no cron)
  • Includes daily backup + daily cleanup
  • Easy to tweak for different times / retention periods.