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 Rcs.
- 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.comthat 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 nginxFollow the Install MySQL section of this Rcs guide to install and secure MySQL 8.0.
Add the
ppa:ondrej/phprepository, a long-time and community-trusted repository that offers PHP 8.0.$ sudo LC_ALL=C.UTF-8 add-apt-repository -y ppa:ondrej/phpInstall 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-zipInstall
unzipto 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-timezonesCopy 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_YorkEdit the main PHP configuration file to tell PHP to use your time zone.
$ sudo nano /etc/php/8.0/fpm/php.iniFind the line
;date.timezone =. Remove the semicolon and add your time zone. For example:date.timezone = America/New_YorkHere are the common settings that you can change if needed:
max_execution_time memory_limit post_max_size upload_max_filesizeSave the configuration file and exit.
Create a dedicated user named
boltto manage your website's source code.$ sudo adduser boltSwitch 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.confRename 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.defaultEdit the PHP-FPM configuration file.
$ sudo nano /etc/php/8.0/fpm/pool.d/bolt.confSearch for the following settings, then:
- Replace
[www]with[bolt] - Replace
user = www-datawithuser = bolt - Replace
group = www-datawithgroup = bolt(do not change thelisten.group = www-datasetting)
Make sure the
listen = /run/php/php8.0-fpm.socksetting does not start with;. This setting makes PHP-FPM listen on a Unix socket specified by the/run/php/php8.0-fpm.sockfile.- 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/boltThose settings make PHP-FPM log error messages to the
/var/log/php-fpm/bolt/error.logfile instead of displaying them to website users and store session data in the/var/lib/php/sessions/boltdirectory.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/boltUpdate 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/boltCheck the new configuration.
$ sudo php-fpm8.0 -tRestart 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
rootuser.$ sudo mysqlCreate a new
db_namedatabase for Bolt.mysql> CREATE DATABASE db_name;Create a MySQL user named
db_userand grant it all privileges on thedb_namedatabase. Replacedb_passwordwith 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 | phpMake the
composercommand globally available.$ sudo mv composer.phar /usr/local/bin/composerCreate the
/var/www/boltdirectory to store Bolt source code.$ sudo mkdir /var/www/boltSet
boltas the owner of the directory.$ sudo chown bolt:bolt /var/www/boltSwitch to the
boltuser before downloading the source code using Composer.$ sudo su - boltChange the working directory to the source code directory.
$ cd /var/www/boltDownload 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.localPaste the following into the editor.
db_user,db_password, anddb_nameare credentials you created in Section 3.APP_ENV=prod # production environment DATABASE_URL=mysql://db_user:"db_password"@localhost:3306/db_nameSave the file and exit.
Generate an optimized
.env.local.phpfile so that Bolt does not have to process the.env.*files on each request.$ composer dump-env prodInitialize 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:createPopulate the database with sample data. Type
yeswhen prompted.$ bin/console doctrine:fixtures:loadCreate the first administrative user. Replace
username,password,email, anddisplay-namewith your desired values.$ bin/console bolt:add-user 'username' 'password' 'email' 'display-name' --adminSwitch 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.confPaste the following into the editor. Replace
bolt.example.comwith 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.confAdd the
www-datauser to theboltgroup so that Nginx processes can access the document root directory.$ sudo usermod -aG bolt www-dataCheck the new configuration.
$ sudo nginx -tReload 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 Rcs 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.confCreate a new configuration file to serve HTTP requests.
$ sudo nano /etc/nginx/sites-available/bolt-http.confPaste 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 -tReload the Nginx service for the changes to take effect.
$ sudo systemctl reload nginx.serviceGet a Let's Encrypt certificate. Replace
admin@bolt.example.comwith 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-interactiveWhen finished, Certbot places all the files related to the certificate in the
/etc/letsencrypt/archive/bolt.example.comdirectory and creates corresponding symlinks in the/etc/letsencrypt/live/bolt.example.comdirectory for your convenience. Those symlinks are:$ sudo ls /etc/letsencrypt/live/bolt.example.com cert.pem chain.pem fullchain.pem privkey.pem READMEYou 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 20482048 is the recommended size of DH parameters.
Update the HTTPS configuration file.
$ sudo nano /etc/nginx/sites-available/bolt-https.confFind 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.confCheck the new configuration.
$ sudo nginx -tReload 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/deploydirectory to make Certbot reload Nginx after renewing the certificate.$ sudo nano /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.shPaste the following into the editor:
#!/bin/bash /usr/bin/systemctl reload nginx.serviceSave and exit.
Make the script executable.
$ sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.shTest the renewal process with a dry run.
$ sudo certbot renew --dry-run
This Rcs 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 rebootOpen 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: