
This article provides a guide to deploy BookStack on Ubuntu VPS.
What is BookStack?
BookStack is a free, open-source, self-hosted documentation and wiki platform used to organize and store information. It is designed to be simple enough for non-technical users while still being useful for teams managing internal documentation, knowledge bases, SOPs, client docs, technical guides, company wikis, and tools like Confluence.
Its structure is intentionally modeled after real books:
Shelves
→ Books
→ Chapters
→ Pages
For example, a hosting company might organize BookStack like this:
Shelf: Internal Operations
Book: VPS Support Procedures
Chapter: Common Customer Issues
Page: How to Troubleshoot SSH Login Failures
Page: How to Handle Disk Space Alerts
BookStack is built with PHP/Laravel and typically uses MySQL or MariaDB as its database. It is self-hosted, meaning you install it on your own VPS or server instead of relying on a hosted SaaS documentation provider.
Common use cases include:
- Internal company knowledge base
- IT documentation
- Server administration notes
- Client-facing documentation
- Standard operating procedures
- Employee onboarding manuals
- Project documentation
- Private wiki replacement
- Open-source alternative to tools like Confluence or Notion-style documentation systems
One of its biggest strengths is that it is opinionated and structured. Instead of giving you a blank wiki with endless organizational freedom, BookStack encourages users to organize content in a clean, book-like hierarchy. That makes it easier for teams to keep documentation tidy over time. Its project definition says the goal is a pleasant, simple experience where users only need basic word-processing skills to create content.
In practical terms, BookStack is a good choice when you want a clean, self-hosted documentation system that is easy for staff or clients to navigate without needing a complex enterprise knowledge-management platform.
Below is a production-oriented guide for deploying BookStack on an Ubuntu VPS using Apache as the local backend, Nginx as the public reverse proxy, and Let’s Encrypt/Certbot for SSL with automated renewal.
Prerequisites
-
- CPU: 1-2 Core
- RAM: 1-2 GB
- Storage: 1+ GB
-
- Operating System: Ubuntu 22.04 LTS 0r Ubuntu 24.04 LTS (preferred).
- PHP: Version 8.2 or higher. You must be able to run PHP from the command line interface (CLI) for maintenance and updates.
- PHP Extensions: The application requires several extensions, typically including
BCMath,CURL,GD,Hash,MBString,OpenSSL,PDO,Tokenizer,XML,ZIP, andTidy. - Database: MySQL >= 8.0 or MariaDB >= 10.6.
- Web Server: Any PHP-compatible web server, though Nginx or Apache are the most common and recommended choices.
- Tools: Composer >= v2.2.0 (PHP dependency manager) and Git version control are required for standard installation and ongoing updates.
Compare Ubuntu VPS Plans
How to Deploy BookStack on Ubuntu VPS with Nginx Reverse Proxy
To Deploy Bookstack on Ubuntu VPS, follow the steps below:
-
Deployment Overview
This guide installs BookStack on an Ubuntu VPS using the following architecture:
Internet | | HTTPS :443 v Nginx reverse proxy | | HTTP :8080 on localhost v Apache + PHP | v BookStack application | v MariaDB/MySQL database
This layout is useful when you want Nginx to manage public HTTP/HTTPS traffic, SSL certificates, redirects, and proxy headers while Apache serves the PHP application locally.
The example values used in this guide are:
Domain: docs.example.com BookStack path: /var/www/bookstack Backend Apache port: 127.0.0.1:8080 Database name: bookstack Database user: bookstackuser
Replace these values with your real domain, database credentials, and server details.
-
Prerequisites
Before starting, make sure you have:
Ubuntu 24.04 LTS VPS Root or sudo user access A domain or subdomain pointed to the VPS IP address Ports 80 and 443 open to the public At least 1 GB RAM, preferably 2 GB or more
Check your Ubuntu version:
lsb_release -a
Check that your domain resolves to the VPS:
dig +short docs.example.com
Or:
ping docs.example.com
If the domain does not resolve to your VPS IP address yet, update your DNS A record before continuing.
-
Update the Server
Log in to your VPS:
ssh root@your_server_ip
Update the package index and upgrade installed packages:
apt update apt upgrade -y
Install basic tools:
apt install -y software-properties-common curl wget unzip git nano ufw
Reboot if the system installed a new kernel:
reboot
Reconnect after the reboot.
-
Configure the Firewall
Allow SSH, HTTP, and HTTPS:
ufw allow OpenSSH ufw allow 80/tcp ufw allow 443/tcp ufw enable
Check status:
ufw status
Expected result:
OpenSSH ALLOW 80/tcp ALLOW 443/tcp ALLOW
Do not expose Apache’s backend port publicly. Apache will listen only on
127.0.0.1:8080. -
Install Apache, Nginx, MariaDB, PHP, and Required Extensions
Install Apache, Nginx, MariaDB, PHP, and common BookStack dependencies:
apt install -y \ apache2 \ nginx \ mariadb-server \ php \ php-cli \ php-common \ php-mysql \ php-curl \ php-mbstring \ php-xml \ php-zip \ php-gd \ php-ldap \ php-tokenizer \ php-bcmath \ php-fpm \ libapache2-mod-php \ composer
Check PHP version:
php -v
BookStack requires PHP 8.2 or newer.
Enable Apache modules required for Laravel-style routing:
a2enmod rewrite a2enmod headers
Restart Apache:
systemctl restart apache2
Enable services on boot:
systemctl enable apache2 systemctl enable nginx systemctl enable mariadb
-
Secure MariaDB
Run the MariaDB secure installation helper:
mysql_secure_installation
Recommended responses:
Switch to unix_socket authentication: Y Change root password: N, unless you specifically want one Remove anonymous users: Y Disallow root login remotely: Y Remove test database: Y Reload privilege tables: Y
Log in to MariaDB:
mysql
Create the BookStack database and database user:
CREATE DATABASE bookstack CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER 'bookstackuser'@'localhost' IDENTIFIED BY 'CHANGE_THIS_STRONG_PASSWORD'; GRANT ALL PRIVILEGES ON bookstack.* TO 'bookstackuser'@'localhost'; FLUSH PRIVILEGES; EXIT;
Use a strong password and save it somewhere secure.
-
Download BookStack
Move to the web directory:
cd /var/www
Clone the stable BookStack release branch:
git clone https://github.com/BookStackApp/BookStack.git --branch release --single-branch bookstack
Enter the BookStack directory:
cd /var/www/bookstack
Install PHP dependencies:
composer install --no-dev --optimize-autoloader
If Composer warns you about running as root, confirm only if you are intentionally performing this installation as root on your VPS.
-
Configure BookStack Environment Settings
Copy the example environment file:
cp .env.example .env
Edit the
.envfile:nano .env
Update the main settings:
APP_URL=https://docs.example.com DB_HOST=localhost DB_DATABASE=bookstack DB_USERNAME=bookstackuser DB_PASSWORD=CHANGE_THIS_STRONG_PASSWORD
Because this deployment places BookStack behind Nginx, add a trusted proxy setting:
APP_PROXIES=127.0.0.1
For HTTPS cookies, you can also explicitly add:
SESSION_SECURE_COOKIE=true
BookStack uses
APP_URLto generate consistent URLs, so it should match the final public URL exactly. Do not use the backend Apache port inAPP_URL.Save and exit.
Generate the application key:
php artisan key:generate --no-interaction --force
Run database migrations:
php artisan migrate --no-interaction --force
Clear and rebuild caches:
php artisan cache:clear php artisan config:clear php artisan view:clear
Set ownership and permissions:
chown -R www-data:www-data /var/www/bookstack find /var/www/bookstack -type f -exec chmod 644 {} \; find /var/www/bookstack -type d -exec chmod 755 {} \; chmod -R ug+rwx /var/www/bookstack/storage /var/www/bookstack/bootstrap/cache /var/www/bookstack/public/uploadsBookStack’s own troubleshooting guidance notes that blank-screen issues are often caused by incorrect permissions on
bootstrap/cache,storage, andpublic/uploads. -
Configure Apache as the Local BookStack Backend
Apache should serve BookStack only locally on port
8080.Edit Apache ports:
nano /etc/apache2/ports.conf
Change:
Listen 80
To:
Listen 127.0.0.1:8080
Disable the default Apache site:
a2dissite 000-default.conf
Create a BookStack Apache virtual host:
nano /etc/apache2/sites-available/bookstack-backend.conf
Paste:
ServerName docs.example.com DocumentRoot /var/www/bookstack/public Options -Indexes +FollowSymLinks AllowOverride All Require all granted ErrorLog ${APACHE_LOG_DIR}/bookstack-error.log CustomLog ${APACHE_LOG_DIR}/bookstack-access.log combinedEnable the site:
a2ensite bookstack-backend.conf
Test Apache configuration:
apachectl configtest
Restart Apache:
systemctl restart apache2
Confirm Apache is listening only on localhost port
8080:ss -tulpn | grep apache
Expected result should include something like:
127.0.0.1:8080
Test the backend locally:
curl -I http://127.0.0.1:8080
A
200,302, or similar HTTP response means Apache is responding. -
Configure Nginx as the Public Reverse Proxy
Create an Nginx site file:
nano /etc/nginx/sites-available/bookstack
Paste the following temporary HTTP-only configuration:
server { listen 80; server_name docs.example.com; client_max_body_size 100M; location / { proxy_pass http://127.0.0.1:8080; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Port $server_port; proxy_redirect off; } }NGINX’s reverse proxy documentation describes the use of
proxy_passfor passing requests to an upstream service andproxy_set_headerfor forwarding headers such asHostand client IP information.Enable the site:
ln -s /etc/nginx/sites-available/bookstack /etc/nginx/sites-enabled/bookstack
Remove the default Nginx site if it is enabled:
rm -f /etc/nginx/sites-enabled/default
Test Nginx:
nginx -t
Reload Nginx:
systemctl reload nginx
Visit:
http://docs.example.com
At this stage, BookStack should load over plain HTTP.
-
Install Certbot
The official Certbot instructions recommend the snap-based installation method for most users and note that Certbot’s installed packages include a cron job or systemd timer for automatic renewal.
Install snapd if needed:
apt install -y snapd
Update snap core:
snap install core snap refresh core
Remove any older apt-based Certbot package if present:
apt remove -y certbot python3-certbot-nginx || true
Install Certbot:
snap install --classic certbot
Create the Certbot command symlink:
ln -s /snap/bin/certbot /usr/local/bin/certbot
Verify:
certbot --version
-
Request and Install the SSL Certificate
Use Certbot’s Nginx plugin:
certbot --nginx -d docs.example.com
When prompted:
Enter your email address Agree to the Terms of Service Choose whether to share your email with EFF Select redirect HTTP to HTTPS if prompted
Certbot should modify the Nginx configuration automatically.
After completion, visit:
https://docs.example.com
Confirm the site loads with a valid SSL certificate.
-
Review the Final Nginx Configuration
Open the Nginx site:
nano /etc/nginx/sites-available/bookstack
Certbot may have modified it. A good final configuration should resemble this:
server { listen 80; server_name docs.example.com; return 301 https://$host$request_uri; } server { listen 443 ssl http2; server_name docs.example.com; client_max_body_size 100M; ssl_certificate /etc/letsencrypt/live/docs.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/docs.example.com/privkey.pem; include /etc/letsencrypt/options-ssl-nginx.conf; ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; location / { proxy_pass http://127.0.0.1:8080; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto https; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Port 443; proxy_redirect off; } }Test Nginx:
nginx -t
Reload Nginx:
systemctl reload nginx
-
Test Automated SSL Renewal
Run a dry-run renewal test:
certbot renew --dry-run
A successful dry run confirms that renewal should work automatically.
Check the systemd timer:
systemctl list-timers | grep certbot
You can also check snap timers:
systemctl list-timers | grep snap.certbot
The Certbot documentation notes that automatic renewal is normally installed through either a cron job or systemd timer, and renewal can be tested with:
certbot renew --dry-run
-
Create a Renewal Deploy Hook for Nginx Reload
Certbot usually reloads Nginx when using the Nginx plugin, but adding a deploy hook is a good safety measure.
Create a deploy hook:
nano /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
Paste:
#!/bin/bash systemctl reload nginx
Make it executable:
chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
Test again:
certbot renew --dry-run
-
Log In to BookStack
Open:
https://docs.example.com
Default BookStack login credentials are commonly:
Email: admin@admin.com Password: password
If those credentials do not work, create a new admin user from the command line:
cd /var/www/bookstack php artisan bookstack:create-admin
Follow the prompts to create an administrator account.
Immediately after logging in:
- Change the admin email.
- Change the admin password.
- Configure your site name.
- Configure mail settings.
- Disable or remove any default test account if one exists.
-
Configure Outbound Mail
BookStack can operate without mail for basic use, but password resets, invitations, and notifications require SMTP.
Edit:
nano /var/www/bookstack/.env
Example SMTP configuration:
MAIL_DRIVER=smtp MAIL_HOST=smtp.example.com MAIL_PORT=587 MAIL_USERNAME=smtp-user@example.com MAIL_PASSWORD=CHANGE_THIS_SMTP_PASSWORD MAIL_ENCRYPTION=tls MAIL_FROM=docs@example.com MAIL_FROM_NAME="BookStack"
Clear config cache:
cd /var/www/bookstack php artisan config:clear php artisan cache:clear
Restart Apache:
systemctl restart apache2
-
Increase Upload Limits
BookStack supports image and file uploads. If you need larger uploads, adjust PHP, Apache, and Nginx limits.
Find the active PHP version:
php -v
Edit PHP Apache configuration. For PHP 8.3, for example:
nano /etc/php/8.3/apache2/php.ini
Update:
upload_max_filesize = 100M post_max_size = 100M memory_limit = 256M max_execution_time = 120
Update Nginx upload size if not already set:
nano /etc/nginx/sites-available/bookstack
Inside the HTTPS server block, make sure this exists:
client_max_body_size 100M;
Restart services:
systemctl restart apache2 systemctl reload nginx
-
Add Basic Security Hardening
Disable Apache public exposure by confirming it is bound only to localhost:
ss -tulpn | grep ':8080'
You should see:
127.0.0.1:8080
Confirm Apache is not listening on public port 80:
ss -tulpn | grep apache
Add security headers in Nginx:
nano /etc/nginx/sites-available/bookstack
Inside the HTTPS server block, add:
add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header X-XSS-Protection "1; mode=block" always;
Do not add an overly restrictive Content Security Policy unless you test it carefully, because it can break application features.
Test and reload:
nginx -t systemctl reload nginx
-
Set Up BookStack Backups
At minimum, back up:
The database The .env file The storage directory The public/uploads directory
Create a backup directory:
mkdir -p /root/bookstack-backups chmod 700 /root/bookstack-backups
Create a backup script:
nano /usr/local/sbin/backup-bookstack.sh
Paste:
#!/bin/bash set -e BACKUP_DIR="/root/bookstack-backups" DATE="$(date +%F-%H%M%S)" APP_DIR="/var/www/bookstack" mkdir -p "$BACKUP_DIR/$DATE" mysqldump bookstack > "$BACKUP_DIR/$DATE/bookstack.sql" cp "$APP_DIR/.env" "$BACKUP_DIR/$DATE/.env" tar -czf "$BACKUP_DIR/$DATE/bookstack-files.tar.gz" \ "$APP_DIR/storage" \ "$APP_DIR/public/uploads" find "$BACKUP_DIR" -mindepth 1 -maxdepth 1 -type d -mtime +14 -exec rm -rf {} \;Make it executable:
chmod +x /usr/local/sbin/backup-bookstack.sh
Test it:
/usr/local/sbin/backup-bookstack.sh
Add a daily cron job:
crontab -e
Add:
30 2 * * * /usr/local/sbin/backup-bookstack.sh >/var/log/bookstack-backup.log 2>&1
This runs backups daily at 2:30 AM.
For production, copy backups off-server using SFTP, rsync, object storage, or your VPS provider’s backup system.
-
Updating BookStack
Before updating, always run a backup.
/usr/local/sbin/backup-bookstack.sh
Update BookStack:
cd /var/www/bookstack git pull origin release composer install --no-dev --optimize-autoloader php artisan migrate --no-interaction --force php artisan cache:clear php artisan config:clear php artisan view:clear chown -R www-data:www-data /var/www/bookstack systemctl restart apache2
BookStack’s update documentation recommends backing up the database and uploaded files before updating.
-
Troubleshooting
Problem: Nginx shows 502 Bad Gateway
Check Apache:
systemctl status apache2
Check whether Apache is listening:
ss -tulpn | grep 8080
Check logs:
tail -n 100 /var/log/nginx/error.log tail -n 100 /var/log/apache2/bookstack-error.log
Restart services:
systemctl restart apache2 systemctl reload nginx
Problem: BookStack redirects to HTTP instead of HTTPS
Check
.env:nano /var/www/bookstack/.env
Confirm:
APP_URL=https://docs.example.com APP_PROXIES=127.0.0.1 SESSION_SECURE_COOKIE=true
Clear cache:
cd /var/www/bookstack php artisan config:clear php artisan cache:clear systemctl restart apache2
Also confirm Nginx is passing:
proxy_set_header X-Forwarded-Proto https; proxy_set_header X-Forwarded-Port 443;
Problem: Blank white page
Fix permissions:
cd /var/www/bookstack chown -R www-data:www-data /var/www/bookstack chmod -R ug+rwx storage bootstrap/cache public/uploads
Check logs:
tail -n 100 /var/www/bookstack/storage/logs/laravel.log tail -n 100 /var/log/apache2/bookstack-error.log
Problem: CSS or images do not load
Confirm
APP_URLexactly matches the public URL:APP_URL=https://docs.example.com
Then clear caches:
cd /var/www/bookstack php artisan config:clear php artisan cache:clear php artisan view:clear
Restart Apache:
systemctl restart apache2
Problem: Certbot renewal fails
Run:
certbot renew --dry-run
Check Nginx config:
nginx -t
Check Certbot logs:
tail -n 100 /var/log/letsencrypt/letsencrypt.log
Confirm ports 80 and 443 are reachable:
ufw status
Confirm DNS still points to the server:
dig +short docs.example.com
-
Useful Service Commands
Restart Apache:
systemctl restart apache2
Restart Nginx:
systemctl restart nginx
Reload Nginx without dropping active connections:
systemctl reload nginx
Restart MariaDB:
systemctl restart mariadb
Check BookStack Laravel logs:
tail -f /var/www/bookstack/storage/logs/laravel.log
Check Nginx logs:
tail -f /var/log/nginx/access.log tail -f /var/log/nginx/error.log
Check Apache logs:
tail -f /var/log/apache2/bookstack-access.log tail -f /var/log/apache2/bookstack-error.log
-
Final Verification Checklist
Use this checklist before considering the deployment complete:
DNS points docs.example.com to the VPS IP address. UFW allows ports 22, 80, and 443. Apache listens only on 127.0.0.1:8080. Nginx listens publicly on ports 80 and 443. HTTP redirects to HTTPS. SSL certificate is valid. certbot renew --dry-run succeeds. BookStack loads at https://docs.example.com. APP_URL uses the final HTTPS URL. APP_PROXIES is set to 127.0.0.1. Admin password has been changed. SMTP mail has been configured. Backups are scheduled and tested.
Once all checks pass, BookStack is ready for production use behind an Nginx reverse proxy with automated SSL renewal.
Conclusion
You now know how to deploy BookStack on Ubuntu VPS









