Update #4 - Automated Backups for BookStack on VPS - Secure & Scheduled
Update #4
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:
-
fail2ban
active -
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
bookstack
database -
Archive important BookStack directories (such as
/var/www/bookstack/public/uploads
and/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.cnf
file:[client] user=bookstack password=your_db_password
-
-
Tested the Script
-
Verified manual execution of the script:
-
Confirmed
.sql
dumps and.tar.gz
files 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
mysqldump
passwords in scripts —~/.my.cnf
is a secure alternative. -
Giving the database user
PROCESS
privilege 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.
1 Comment
Love it.