Add self-hosted Commento commenting to your Ghost blog
Enhance the set up of your ghost blog by adding a powerful, opensource and privacy-focused commenting system: Commento
Enhance the set up of your Ghost blog by adding a powerful, opensource and privacy-focused commenting system: Commento.io
( Photo by Kawtar Cherkaoui )
When I set up this blog earlier this year I intended this place as a one-way communication system, whereas I would simply add articles or tutorials from time to time. Having had other blogs in the past I knew very well that one of the best ways to keep this interesting and useful was to open it up for comments and reactions from others.
However, I was struggling to find a good service to implement this. Disqus is typically a quick, free easy solution and works well. However, in a post-GDPR 🇪🇺 world, it was difficult to understand:
- What data do they collect about users
- What do they do with it
To be fair, they do provide a detailed privacy policy where they are clear in stating that the data collected is used for advertising and targeting and so forth ... There ain't a magic money tree after all😉. For my purposes, I don't have the time to deal with that.
Commento.io
Commento.io is an open-source, privacy-focused and feature-rich commenting system that can be embedded as easily as Disqus into your site. It offers a few things:
- Easy embedding into existing sites such as this one
- Akismet spam filtering
- Anonymous commenting capability
- Moderation
- Lightweight, no tracking, no advertising
- Managed or Self-hosted
- SSO / Social Logins (if required)
The managed version uses a "Pay What You Want" model (minimum of 3$ per month) meaning that it's a 1-minute "copy and paste into HTML template" task which gets you up and running quickly. It's hosted on Digitalocean servers based in the United States, as per their privacy policy.
The best thing, however, is that you can also self-host the system, which is what I have decided to do. The advantages are that:
- You don't pay for anything. Well, you pay for your existing host and your setup and maintenance efforts - as I said above there ain't no magic money tree 😉 If you own a truly popular blog or website, it might mean scaling your existing resources.
- You retain all the data. In my case, the comments data will sit alongside the blog in the London datacenter it uses. This means it stays within the EU (at least for the time being... let's not even start that conversation 🤷🏾♀️)
- It's fun!
Let's see what it takes to get it done.
Pre-requisites
Before you begin, it's best to check a few requisites:
- An open browser tab at docs.commento.io: I should have said above, the documentation is pretty slick!
- A terminal session into your target host for running Commento.
- Your site's DNS configuration interface open, as you'll need to add a DNS rule.
I'm also making a few assumptions to keep it simple:
- You are installing Commento on the same host where Ghost runs, and that the host runs Ubuntu 18.04.
- You'll be using Nginx as the webserver.
- You are using Digitalocean - as I'm linking a few of their excellent documentation pages. The instructions are pretty generic and should work anywhere else as long as it's the same Linux distribution.
- You don't have a CDN / hardened set up such as the one I described here - if you have this, it just means that the Nginx configuration and SSL set up may involve a few extra steps. As an example, if you serve your subdomain via Cloudflare and you have authenticated origin pulls enabled, then you'll need to relevant configuration lines and certificate added in your Nginx configuration.
Let's get going now!
Postgresql setup
Once you opened your terminal session, you will need to install Postgresql as this is the database system used by Commento to store the comments data - If you are on Digitalocean then you may as well keep this guide in the background.
Get these two packages
sudo apt update
sudo apt install postgresql postgresql-contrib
Once the installation is complete, start the database and then verify that it's running correctly
sudo systemctl start postgresql
sudo systemctl status postgresql
The second command should tell you something similar to the below
$ sudo systemctl status postgresql
● postgresql.service - PostgreSQL RDBMS
Loaded: loaded (/lib/systemd/system/postgresql.service; enabled; vendor preset: enabled)
Active: active (exited) since ...
Process: [...] ExecStart=/bin/true (code=exited, status=0/SUCCESS)
Main PID: [...] (code=exited, status=0/SUCCESS)
[...] systemd[1]: Starting PostgreSQL RDBMS...
[...] systemd[1]: Started PostgreSQL RDBMS.
Meaning Postgres was installed successfully and it's ready to be configured. Before doing that, we want to tell systemd to enable Postgresql at boot so that it runs automatically if your host is re-started (more info here if you are not familiar with systemctl and systemd)
sudo systemctl enable postgresql
For getting up and running with Commento, you'll need to set up an empty database and a database role. In Postgresql, a database role is very similar to a user, and we will use it to configure the DB connection configuration from Commento (more about Postgresql roles here).
Let's do that now:
sudo -i -u postgres
psql
The postgres user was created automatically during the installation of the DB. The above commands are simply getting us into an interactive PostgreSQL command shell. We will use the below SQL commands in order to
- Create a commento role
- Add a password to the commento role (note it down)
- Create an empty database named commento
- List all the databases (you should see the one you just created)
- Leave the psql command shell
CREATE ROLE commento WITH LOGIN;
\password commento
CREATE DATABASE commento;
\l
\q
Once done you can exit the postgres user session
exit
Subdomain & SSL configuration
If your main blog is at www.example.com, you'll want to choose a subdomain such as commento.example.com or similar. You then need to configure a DNS A rule pointing that subdomain to your host's IP address.
Then, depending on your existing domain configuration, you will need to set up SSL accordingly (because who doesn't serve content online without SSL nowadays 👱🏻♀)
Getting a certificate today is a no brainer thanks to Let's Encrypt - so I will give an example based on obtaining a wildcard certificate with certbot for your domain and assuming you use Cloudflare as your DNS provider (note: there are plenty of other DNS plugins if you are on another
Install certbot and the required plugins
sudo apt install python3-certbot python3-certbot-nginx python3-certbot-dns-cloudflare
The third package in particular (python3-certbot-dns-cloudflare) is used to automate the dns-01 challenge method which will be used to obtain the certificate. In other words, it will allow certbot to use the Cloudflare APIs to set up an appropriate DNS TXT rule. That rule is then used by certbot to verify ownership of the domain and in turn allowing you to obtain a certificate for that domain.
I prefer the DNS method because it avoids all the headaches due to a hardened configuration. For example, using the standard method (which temporarily runs a webserver on port 80) wouldn't work for me because I have a strict TLS policy in place and the verification request will never make it to the temporary webserver.
For the dns-cloudflare plugin specifically, you will need to store your Cloudflare credentials in a file. You must put this in a secure location as anyone able to read that file will be also able to see your global cloudflare API key
Update (August 2020): as the certbot Cloudflare plugin now supports token authentication - it is highly recommended to create a scoped token instead of using your global api key. Simply change the contents above to only include dns_cloudflare_api_token = <TOKEN> . The token will need to have DNS editing permissions on the required Cloudflare Zone.
For example, let's put it in the /etc/letsencrypt folder
sudo touch /etc/letsencrypt/cloudflare-credentials.ini
sudo nano /etc/letsencrypt/cloudflare-credentials.ini
sudo chmod 0600 /etc/letsencrypt/cloudflare-credentials.ini
The last command restricts access to the root user
Now it's time to test this out. I suggest trying to create a test certificate first. For example test.example.com (change with your domain and e-mail accordingly)
sudo certbot certonly \
--test-cert \
--register \
--agree-tos \
--email yourmail@yourmail \
--preferred-challenges dns-01 \
--dns-cloudflare \
--dns-cloudflare-credentials /etc/letsencrypt/cloudflare-credentials.ini \
--dns-cloudflare-propagation-seconds 30 \
-d test.example.com
You should see something like this in a successful scenario
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator dns-cloudflare, Installer None
Obtaining a new certificate
Performing the following challenges:
dns-01 challenge for test.example.com
Waiting 30 seconds for DNS changes to propagate
Waiting for verification...
Cleaning up challenges
IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at:
/etc/letsencrypt/live/**test.example.com**/fullchain.pem
Your key file has been saved at:
/etc/letsencrypt/live/test.myblog.com/privkey.pem
Your cert will expire on 2020-02-28. To obtain a new or tweaked
version of this certificate in the future, simply run certbot
again. To non-interactively renew *all* of your certificates, run
"certbot renew"
You can safely delete the test certificate now - we just wanted to validate that you set up your dns based authentication with Cloudflare correctly.
$ sudo certbot delete
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Which certificate(s) would you like to delete?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: test.example.com
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate numbers separated by commas and/or spaces, or leave input
blank to select all options shown (Enter 'c' to cancel): 1
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Deleted all files relating to certificate test.example.com.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
You should now go ahead and create the actual certificate. You can either obtain a wildcard certificate or a certificate for your subdomain. Let's try with the wildcard certificate
sudo certbot certonly \
--register \
--agree-tos \
--preferred-challenges dns-01 \
--email yourmail@yourmail \
--dns-cloudflare \
--dns-cloudflare-credentials /etc/letsencrypt/cloudflare-credentials.ini \
--dns-cloudflare-propagation-seconds 30 \
-d *.example.com
Assuming it worked ok for you, you can confirm that your certificate is being managed by certbot by issuing
$ sudo certbot certificates
Saving debug log to /var/log/letsencrypt/letsencrypt.log
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Found the following certs:
Certificate Name: *.example.com
Domains: *.example.com
Expiry Date: 2020-02-28 11:16:51+00:00 (VALID: 89 days)
Certificate Path: /etc/letsencrypt/live/example.com/fullchain.pem
Private Key Path: /etc/letsencrypt/live/example.com/privkey.pem
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Finally, add a crontab entry so that certbot can run daily and attempt to renew expiring certificates if they are due for renewal
crontab -e
And then add the following entry
0 19 * * * /usr/bin/certbot renew --post-hook "systemctl reload nginx" > /dev/null 1>&1
The above means that every day at 19:00 the command will be executed and then reload nginx to pick up the new changes (if any). You can tweak the crontab to run at a more convenient time for you, but I recommend that you keep it on a daily frequence. Please note that this command attempts to renews all certificates that are managed by certbot.
You can also test the command in your console to see what it does (I added a --dry-run flag to it).
sudo certbot renew --dry-run --post-hook "systemctl reload nginx"
Now that you have a valid certificate and that it's primed for auto-renewal, it's time to configure nginx so that it's ready to serve requests securely.
Nginx configuration
Let's set up our nginx webserver so that it's ready to take requests securely, and forward them to Commento. Let's assume the following
- Your domain is commento.example.com
- You will run Commento on the same host as nginx , on port 1234
- You will configure your certificate and private key to the paths indicated by certbot when you created it
- The certificate is either a wildcard certificate or a certificate for your specific subdomain.
Then you will want a similar configuration file in /etc/nginx/sites-available/commento.example.com
You can then symlink your above configuration file in /etc/nginx/sites-enabled when you are ready to go. We are not quite ready yet as we still need to install Commento!
Install Commento and enable at boot
We will create a user called commento and we will install commento in that user's home folder.
sudo adduser commento
sudo -i -u commento
Make sure you are in the home directory and download the most recent binary release (1.7.0 right now - please check and update accordingly the below commands)
cd
wget https://commento-release.s3.amazonaws.com/commento-linux-amd64-v1.7.0.tar.gz
mkdir commento-linux-amd64-v1.7.0
tar xvf commento-linux-amd64-v1.7.0.tar.gz -C commento-linux-amd64-v1.7.0
ln -s commento-linux-amd64-v1.7.0 commento-latest
exit
The symlink just helps to manage future updates, so that it can be pointed to the new codebase folder when it's released. In fact, it would be probably best to containerize the entire installation but that's a story for another day 👩🏾🏭
What we do want to do now is to create a systemd definition so that we can enable and run commento at boot automatically. For that, you can use the template provided and then add it to service definitions. Do update your ExecStart location if you installed it in a place different than the one I used.
sudo touch /etc/systemd/system/commento.service
Then update your service definition accordingly:
- ExecStart: update to your location if different
- COMMENTO_ORIGIN : update domain
- COMMENTO_PORT : update port and make sure that it corresponds to the port you set up in nginx above (it was 1234 in that example)
- COMMENTO_POSTGRES: update with the Postgres Role and Password as per earlier instructions.
[Unit]
Description=Commento daemon service
After=network.target postgresql.service
[Service]
Type=simple
ExecStart=/home/commento/commento-latest/commento
Environment=COMMENTO_ORIGIN=https://<YOUR DOMAIN>
Environment=COMMENTO_PORT=<YOUR PORT>
Environment=COMMENTO_POSTGRES=postgres://<YOUR POSTGRESQL ROLE>:<YOUR POSTGRESQL PASSWORD>@127.0.0.1:5432/commento?sslmode=disable
[Install]
WantedBy=multi-user.target
It's time now to enable the service and check that it works correctly. Do double-check that it can talk to PostgresSQL correctly as this will likely be the main gotcha here.
Finally, enable it at boot.
sudo systemctl start commento
sudo systemctl status commento
sudo systemctl enable commento
Create a Commento Dashboard admin user
The whole system should be now almost ready to serve your self-hosted Commento installation securely over HTTPS. You will need to create an administrative user on the Commento interface. Because this is publicly accessible, you will want to do that fairly quickly before someone else starts mucking with it. Or use some Cloudflare rules to temporarily lock everyone out that page except yourself... I didn't bother!
First thing first, let's double-check our Nginx configuration, symlink it to the sites-enabled folder (if you didn't do that already), then check the config is sane before reloading
sudo nginx -t
sudo nginx -s reload
You should now be able to navigate to your commento subdomain and sign up your administrative user. The official documentation should help you here if not self-explanatory.
Now that you have created your admin user, disable the creation of new owners. Stop the service:
sudo systemctl stop commento
Then modify the service definition above to include
Environment=COMMENTO_FORBID_NEW_OWNERS=true
This environment variable will prevent the creation of new admin users
Then restart the service
sudo systemctl start commento
You can verify that this worked by trying to sign up again on the administrative interface. The Sign Up form should be available, but any attempts to submit it should result in an error message.
Add Commento snippet to your blog/site
The very last thing to do is to add the Commento code snippet to your blog/site. This is accomplished by including the following snippet (replace the domain with yours)
<div id="commento"></div>
<script src="https://commento.example.com/js/commento.js"></script>
For ghost, if you use the standard 'Caspar' theme there is a placeholder that is ideal for this in the post.hbs template. You can use the official Ghost instructions for Disqus (but you will be using your kick-ass, self hosted Commento instead).
Final considerations
There are further configuration options which you should definitely consider before putting this live, but these are for another day (or maybe you want to experiment a little yourself):
- Configure SMTP: this is neat especially if you plan to allow users to type in their e-mail addresses when leaving comments. It also helps you in case you forget your administrative user password, as the system comes with a Forgot Password facility. You can refer to the SMTP options available here to understand how to do this. If you are using something like Mailgun - which you may have already configured for your Ghost it should be fairly straightforward
- Configure Akismet: make sure to grab an Akismet key and include that in your configuration, so that your comments are checked for spam.
- Configure Social Sign In: Commento supports several social sign-in facilities. Again, refer to the configuration options page I linked above.
I hope you enjoyed this - let me know if this worked for you!
July 2020 Update: I have tweaked my installation so that Commento is Lazy Loaded, this improves Page Speed and user experience: read more about it in my latest blog article