Author: Tung Nguyen
Last Updated: Fri, Oct 15, 2021Bolt is an open-source content management system based on the Symfony PHP framework. It is suitable for medium-sized websites and aims at both content editors and software developers. This guide explains how to install Bolt with Nginx, MySQL, and PHP on Ubuntu 20.04 and set up HTTPS with a free Let's Encrypt TLS certificate.
Before you begin, you should:
Deploy an Ubuntu 20.04 cloud server at Vultr.
Configure the Ubuntu firewall with ports 80, 443, and 22 open.
Have a fully qualified domain name such as bolt.example.com
that points to your server's IP address. Otherwise, authentication will not work.
Log in to your server as the non-root user.
Make sure to replace bolt.example.com in the code examples with your server's fully qualified domain name.
Bolt requires the following software:
A web server such as Nginx.
A SQL database engine such as MySQL.
PHP 7.2.9 or higher with a minimum of 32MB of memory allocated to PHP.
The following PHP modules:
curl
, exif
, fileinfo
, gd
, json
, mysqlnd
(to use MySQL as a database), openssl
, pdo
, posix
, xml
, zip
.
(optional but recommended) intl
, mbstring
, opcache
.
Install Nginx.
$ sudo apt -y install nginx
Follow the Install MySQL section of this Vultr guide to install and secure MySQL 8.0.
Add the ppa:ondrej/php
repository, a long-time and community-trusted repository that offers PHP 8.0.
$ sudo LC_ALL=C.UTF-8 add-apt-repository -y ppa:ondrej/php
Install PHP 8.0 and the required modules.
$ sudo apt -y install php8.0-cli php8.0-curl php8.0-fpm php8.0-gd php8.0-intl php8.0-mbstring php8.0-mysql php8.0-xml php8.0-zip
Install unzip
to extract zip files.
$ sudo apt -y install unzip
List all the time zones that your Ubuntu system supports. Use the UP / DOWN / PGUP / PGDN keys to move through the list, and press Q to exit.
$ timedatectl list-timezones
Copy an appropriate time zone from the list, for example, America/New_York. Then update the operating system with that time zone.
$ sudo timedatectl set-timezone America/New_York
Edit the main PHP configuration file to tell PHP to use your time zone.
$ sudo nano /etc/php/8.0/fpm/php.ini
Find the line ;date.timezone =
. Remove the semicolon and add your time zone. For example:
date.timezone = America/New_York
Here are the common settings that you can change if needed:
max_execution_time
memory_limit
post_max_size
upload_max_filesize
Save the configuration file and exit.
Create a dedicated user named bolt
to manage your website's source code.
$ sudo adduser bolt
Switch to this user each time you change the source code.
Create the PHP-FPM configuration file from the default one.
$ sudo cp /etc/php/8.0/fpm/pool.d/www.conf /etc/php/8.0/fpm/pool.d/bolt.conf
Rename the default file to disable it and keep it as a backup.
$ sudo mv /etc/php/8.0/fpm/pool.d/www.conf /etc/php/8.0/fpm/pool.d/www.conf.default
Edit the PHP-FPM configuration file.
$ sudo nano /etc/php/8.0/fpm/pool.d/bolt.conf
Search for the following settings, then:
Replace [www]
with [bolt]
Replace user = www-data
with user = bolt
Replace group = www-data
with group = bolt
(do not change the listen.group = www-data
setting)
Make sure the listen = /run/php/php8.0-fpm.sock
setting does not start with ;
. This setting makes PHP-FPM listen on a Unix socket specified by the /run/php/php8.0-fpm.sock
file.
Copy and paste the following settings to the end of the file.
catch_workers_output = yes
php_flag[display_errors] = off
php_admin_flag[log_errors] = on
php_admin_value[error_log] = /var/log/php-fpm/bolt/error.log
php_admin_value[session.save_path] = /var/lib/php/sessions/bolt
Those settings make PHP-FPM log error messages to the /var/log/php-fpm/bolt/error.log
file instead of displaying them to website users and store session data in the /var/lib/php/sessions/bolt
directory.
Save the configuration file and exit.
Create two directories to store PHP logs and session data.
$ sudo mkdir -p /var/log/php-fpm/bolt
$ sudo mkdir -p /var/lib/php/sessions/bolt
Update the ownership and permissions of the two directories so that only the PHP-FPM processes can access them.
$ sudo chown bolt:bolt /var/log/php-fpm/bolt
$ sudo chmod 700 /var/log/php-fpm/bolt
$ sudo chown bolt:bolt /var/lib/php/sessions/bolt
$ sudo chmod 700 /var/lib/php/sessions/bolt
Check the new configuration.
$ sudo php-fpm8.0 -t
Restart the PHP-FPM service for the changes to take effect.
$ sudo systemctl restart php8.0-fpm.service
Connect to MySQL as the MySQL root
user.
$ sudo mysql
Create a new db_name
database for Bolt.
mysql> CREATE DATABASE db_name;
Create a MySQL user named db_user
and grant it all privileges on the db_name
database. Replace db_password
with a strong password.
mysql> CREATE USER 'db_user'@'localhost' IDENTIFIED BY 'db_password';
mysql> GRANT ALL PRIVILEGES on db_name.* to 'db_user'@'localhost';
mysql> FLUSH PRIVILEGES;
Exit MySQL.
mysql> exit
The recommended and fastest method to set up Bolt is to use Composer, a PHP dependency manager.
Install Composer.
$ curl -sS https://getcomposer.org/installer | php
Make the composer
command globally available.
$ sudo mv composer.phar /usr/local/bin/composer
Create the /var/www/bolt
directory to store Bolt source code.
$ sudo mkdir /var/www/bolt
Set bolt
as the owner of the directory.
$ sudo chown bolt:bolt /var/www/bolt
Switch to the bolt
user before downloading the source code using Composer.
$ sudo su - bolt
Change the working directory to the source code directory.
$ cd /var/www/bolt
Download the Bolt source code and its dependencies (this may take a while).
$ composer create-project bolt/project .
Press N and ENTER for the question Do you want to continue the setup now? (Y/n)
to skip the SQLite configuration.
Create a new file to define the application environment and MySQL connection information.
$ nano .env.local
Paste the following into the editor. db_user
, db_password
, and db_name
are credentials you created in Section 3.
APP_ENV=prod # production environment
DATABASE_URL=mysql://db_user:"db_password"@localhost:3306/db_name
Save the file and exit.
Generate an optimized .env.local.php
file so that Bolt does not have to process the .env.*
files on each request.
$ composer dump-env prod
Initialize the database. Because it is empty, you can ignore the caution [CAUTION] This operation should not be executed in a production environment!
.
$ bin/console doctrine:schema:create
Populate the database with sample data. Type yes
when prompted.
$ bin/console doctrine:fixtures:load
Create the first administrative user. Replace username
, password
, email
, and display-name
with your desired values.
$ bin/console bolt:add-user 'username' 'password' 'email' 'display-name' --admin
Switch back to the sudo user to configure Nginx.
$ exit
Create a new configuration file for your Bolt website.
$ sudo nano /etc/nginx/sites-available/bolt-http.conf
Paste the following into the editor. Replace bolt.example.com
with your server's domain name.
server {
listen 80;
listen [::]:80;
server_name bolt.example.com; # your server's domain name
## Uncomment the following to enable logging
# access_log /var/log/nginx/bolt.example.com.access.log;
# error_log /var/log/nginx/bolt.example.com.error.log;
root /var/www/bolt/public; # document root directory
index index.php;
# To avoid upload errors, client_max_body_size must be equal to or
# larger than PHP post_max_size.
client_max_body_size 16m;
# Default prefix match fallback, as all URIs begin with /
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
# Bolt dashboard access
#
# We use two location blocks here, the first is an exact match to the dashboard
# the next is a strict forward match for URIs under the dashboard. This in turn
# ensures that the exact branding prefix has absolute priority, and that
# restrictions that contain the branding string, e.g. "bolt.db", still apply.
#
# NOTE: If you set a custom branding path, change '/bolt/' and '/bolt/' to match
location = /bolt {
try_files $uri /index.php$is_args$args;
}
location ^~ /bolt/ {
try_files $uri /index.php$is_args$args;
}
# Generated thumbnail images
location ^~ /thumbs {
try_files $uri /index.php;
access_log off;
log_not_found off;
expires max;
add_header Pragma public;
add_header Cache-Control "public, mustrevalidate, proxy-revalidate";
add_header X-Koala-Status sleeping;
}
# Do not log but do cache asset files
location ~* ^.+\.(atom|bmp|bz2|css|doc|eot|exe|gif|gz|ico|jpe?g|jpeg|jpg|js|map|mid|midi|mp4|ogg|ogv|otf|png|ppt|rar|rtf|svg|svgz|tar|tgz|ttf|wav|woff|xls|zip)$ {
access_log off;
log_not_found off;
expires max;
add_header Pragma public;
add_header Cache-Control "public, mustrevalidate, proxy-revalidate";
add_header X-Koala-Status eating;
}
# Deny access to any files in the theme directory, except for the listed extensions.
location ~ theme\/.+\.(?!(html?|css|js|jpe?g|png|gif|svg|pdf|avif|webp|mp3|mp?4a?v?|woff2?|txt|ico|zip|tgz|otf|ttf|eot|woff|woff2)$)[^\.]+?$ {
deny all;
}
# Redirect requests for */index.php to the same route minus the "index.php" in the URI.
location ~ /index.php/(.*) {
rewrite ^/index.php/(.*) /$1 permanent;
}
location ~ [^/]\.php(/|$) {
try_files /index.php =404;
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
# Mitigate https://httpoxy.org/ vulnerabilities
fastcgi_param HTTP_PROXY "";
# Set the HTTP parameter if not set in fastcgi_params
fastcgi_param HTTPS $https if_not_empty;
fastcgi_pass unix:/run/php/php8.0-fpm.sock;
# Include the FastCGI parameters shipped with NGINX
include fastcgi_params;
}
# Block access to "hidden" files
# i.e. file names that begin with a dot "."
# An exception is included for Let's Encrypt ssl verification
location ~ /\.(?!well-known) {
deny all;
}
# Block access to Markdown, Twig & YAML files directly
location ~* /.+\.(markdown|md|twig|yaml|yml)$ {
deny all;
}
}
Save the configuration file and exit.
Enable the new configuration.
$ sudo ln -s /etc/nginx/sites-available/bolt-http.conf /etc/nginx/sites-enabled/bolt-http.conf
Add the www-data
user to the bolt
group so that Nginx processes can access the document root directory.
$ sudo usermod -aG bolt www-data
Check the new configuration.
$ sudo nginx -t
Reload the Nginx service for the changes to take effect.
$ sudo systemctl reload nginx.service
For security reasons, you should follow the next section to set up HTTPS for your Bolt website.
Follow the Install Certbot section of this Vultr guide to install Certbot with Snap.
Rename the HTTP configuration file to make it the template for the HTTPS configuration file.
$ sudo mv /etc/nginx/sites-available/bolt-http.conf /etc/nginx/sites-available/bolt-https.conf
Create a new configuration file to serve HTTP requests.
$ sudo nano /etc/nginx/sites-available/bolt-http.conf
Paste the following into your file.
server {
listen 80;
listen [::]:80;
server_name bolt.example.com; # your server's domain name
root /var/www/bolt/public;
location / {
return 301 https://$server_name$request_uri;
}
location /.well-known/acme-challenge/ {}
}
This configuration makes Nginx redirect all HTTP requests, except those from Let's Encrypt, to corresponding HTTPS requests.
Save the configuration file and exit.
Check the new configuration.
$ sudo nginx -t
Reload the Nginx service for the changes to take effect.
$ sudo systemctl reload nginx.service
Get a Let's Encrypt certificate. Replace admin@bolt.example.com
with your email if you want to receive notification emails from Let's Encrypt.
$ sudo certbot certonly --webroot -w /var/www/bolt/public -d bolt.example.com -m admin@bolt.example.com --agree-tos --no-eff-email --non-interactive
When finished, Certbot places all the files related to the certificate in the /etc/letsencrypt/archive/bolt.example.com
directory and creates corresponding symlinks in the /etc/letsencrypt/live/bolt.example.com
directory for your convenience. Those symlinks are:
$ sudo ls /etc/letsencrypt/live/bolt.example.com
cert.pem chain.pem fullchain.pem privkey.pem README
You will use those symlinks in the next step to install the certificate.
Generate a file with DH parameters for DHE ciphers (this may take a while).
$ sudo openssl dhparam -out /etc/nginx/dhparam.pem 2048
2048 is the recommended size of DH parameters.
Update the HTTPS configuration file.
$ sudo nano /etc/nginx/sites-available/bolt-https.conf
Find the following lines.
listen 80;
listen [::]:80;
Replace them with the following lines.
listen 443 ssl http2;
listen [::]:443 ssl http2;
ssl_certificate /etc/letsencrypt/live/bolt.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/bolt.example.com/privkey.pem;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m; # about 40000 sessions
# DH parameters file
ssl_dhparam /etc/nginx/dhparam.pem;
# intermediate configuration
ssl_protocols TLSv1.2;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# HSTS (ngx_http_headers_module is required) (63072000 seconds)
#
# Uncomment the following line only if your website fully supports HTTPS
# and you have no intention of going back to HTTP, otherwise, it will
# break your site.
#
# add_header Strict-Transport-Security "max-age=63072000" always;
# OCSP stapling
ssl_stapling on;
ssl_stapling_verify on;
# verify chain of trust of OCSP response using Root CA and Intermediate certs
ssl_trusted_certificate /etc/letsencrypt/live/bolt.example.com/chain.pem;
# Use Cloudflare DNS resolver
resolver 1.1.1.1;
Save the configuration file and exit.
Enable the new configuration.
$ sudo ln -s /etc/nginx/sites-available/bolt-https.conf /etc/nginx/sites-enabled/bolt-https.conf
Check the new configuration.
$ sudo nginx -t
Reload the Nginx service for the changes to take effect.
$ sudo systemctl reload nginx.service
Let's Encrypt certificates are valid for 90 days, so you must renew your TLS certificate at least once every three months. The Certbot installation automatically created a systemd timer unit to automate this task.
Verify the timer is active.
$ sudo systemctl list-timers | grep 'certbot\|ACTIVATES'
Create a new script in the /etc/letsencrypt/renewal-hooks/deploy
directory to make Certbot reload Nginx after renewing the certificate.
$ sudo nano /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
Paste the following into the editor:
#!/bin/bash
/usr/bin/systemctl reload nginx.service
Save and exit.
Make the script executable.
$ sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
Test the renewal process with a dry run.
$ sudo certbot renew --dry-run
This Vultr article explains all the above steps in more detail. This kind of TLS setup gives you an "A" rating on the SSL Labs test.
Restart the server and wait a moment for the operating system to boot.
$ sudo reboot
Open your browser and type in the http://bolt.example.com
URL. The homepage will appear with the default theme and sample data.
To manage the site, go to the dashboard, http://bolt.example.com/bolt
, and log in with the administrative username and password you created in Section 4.
To learn more about Bolt, please see these resources: