Install Bolt CMS with Nginx, MySQL, and PHP on Ubuntu 20.04
Introduction
Bolt 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.
Prerequisites
Before you begin, you should:
- Deploy an Ubuntu 20.04 cloud server at Vultr.
- Create a non-root user with sudo privileges.
- Update the server.
- 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.
1. Install System Packages
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
2. Configure PHP
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
withuser = bolt
- Replace
group = www-data
withgroup = bolt
(do not change thelisten.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.- Replace
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
3. Create a Database
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 thedb_name
database. Replacedb_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
4. Set Up Bolt
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
, anddb_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
, anddisplay-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
5. Configure Nginx
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 thebolt
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.
6. (Optional) Configure HTTPS
Get a Free Let's Encrypt Certificate with Certbot
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.
Install the Certificate with Nginx
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
Automate Renewal
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.
7. Verify the Setup
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.
More Information
To learn more about Bolt, please see these resources: