Automated Backups for BookStack on VPS - Secure & Scheduled
Project #3
As part of hardening and maintaining my public BookStack instance hosted on a VPS, I implemented a secure, automated backup system using shell scripting and cron. This ensures my database and uploaded files are regularly archived and protected against data loss - without exposing sensitive information in scripts.
The Setup
-
VPS: Ubuntu 22.04 LTS (Hostinger)
-
Web Stack: Apache, MySQL, PHP 8.2 (LAMP stack)
-
Application: BookStack (latest stable)
-
Backup Destination:
/opt/bookstack_backups/on the VPS -
Security Enhancements:
-
fail2banactive -
SSH hardening complete
-
UFW configured to allow only necessary services
-
The Process
-
Created a Shell Script
-
A custom script (
/usr/local/bin/bookstack-backup.sh) was written to:-
Dump the MySQL
bookstackdatabase -
Archive important BookStack directories (such as
/var/www/bookstack/public/uploadsand/var/www/bookstack/storage/uploads) -
Rotate old backups by removing any older than 7 days
-
-
-
Secured MySQL Credentials
-
Rather than placing the database password in the script (which is insecure), credentials were stored securely in the root user's
~/.my.cnffile:[client] user=bookstack password=your_db_password
-
-
Tested the Script
-
Verified manual execution of the script:
-
Confirmed
.sqldumps and.tar.gzfiles were created correctly -
Ensured proper permissions and ownership of backup files
-
-
-
Scheduled Daily Cron Job
-
Added the script to root’s crontab to run automatically at 2:00 AM each day:
0 2 * * * /usr/local/bin/bookstack-backup.sh
-
Sample Output
Backup files generated daily look like:
-
/opt/bookstack_backups/bookstack_2025-05-09.sql -
/opt/bookstack_backups/bookstack_files_2025-05-09.tar.gz
What I Learned
-
Avoid using
mysqldumppasswords in scripts —~/.my.cnfis a secure alternative. -
Giving the database user
PROCESSprivilege was necessary to prevent dump errors. -
Always verify permissions and script execution manually before automating via cron.
-
Storing backups under
/opt/keeps them cleanly separated from web content.