A partial archive of meta.discourse.org as of Tuesday July 18, 2017.

Setting up Let’s Encrypt


So you'd like to add https to your Discourse absolutely free, courtesy of our friends at Let's Encrypt?

:bell: Is everything else on your site ready for HTTPS?

Before you start, please bear in mind that for HTTPS to work properly, every single resource on the page must be HTTPS compatible. Consider your CDN, your social logins, your logo files, any third party JavaScript, images, fonts, or css — these all must be available over HTTPS!

Note: ./discourse-setup will enable Let's Encrypt. And as of March 2017, you can run it again, and press return a few times and enter your email address rather than edit things by hand as described below. If you installed Discourse a long time ago, you might still have to edit app.yml by hand.

Note: If your Discourse is accessed via some reverse proxy (e.g., Cloudflare) this will not work.

For the next few steps, you will be editing the app.yml file for your Discourse instance:

cd /var/discourse
nano containers/app.yml

1. Add web.ssl.template.yml and web.letsencrypt.ssl.template.yml to templates

:warning: Is Discourse the only website on your server?

If you are already using web.socketed.template.yml, because you host other websites via port 80 on the same server, stop. You should be using a Let's Encrypt client on the host system; the validation will fail as the client used is unable to bind to the necessary sockets.

  - "templates/web.template.yml"
  - "templates/web.ssl.template.yml"
  - "templates/web.letsencrypt.ssl.template.yml"

2. Expose port 443

  - "80:80"
  - "443:443"

3. Add email account to register with Let's Encrypt

  LETSENCRYPT_ACCOUNT_EMAIL: 'you@example.com'

4. Rebuild your container

./launcher rebuild <container name>

After that completes, load the site in your browser using https:// – it should "just work!" :tada:

5. Adjust your dependencies for HTTPS

  • Check that all shared resources such as images, logos, third party JavaScript dependencies, etc, are all linked via https:// and not http://

  • Check all your social logins and make sure they are pointing to the https:// and not http:// versions of your site's authentication URLs.

  • If you are using a CDN, you will need to switch your CDN to use https:// and not http:// to pull resources.

  • If your browser does not show any warnings in its f12 console in the security about insecure assets:

    ... you are now ready to force HTTPS by going to

    admin → site settings → force https

How does it work?

The template uses https://github.com/Neilpang/acme.sh which is

Simplest shell script for LetsEncrypt free Certificate client

Simple and Powerful, you only need 3 minutes to learn.

Pure written in bash, no dependencies to python , acme-tiny or LetsEncrypt official client. Just one script, to issue, renew your certificates automatically.

Probably it's the smallest&easiest&smartest shell script to automatically issue&renew the free certificates from LetsEncrypt.

web.letsencrypt.ssl.template.yml adds a startup script to your container that

  1. Issues a Let's Encrypt cert using the standalone mode. It boots a standalone server that listens on port 80 but this happens before nginx is up so port 80 is free.
  2. Installs the cert into the right directory that nginx expects. At the same time, it adds a cron job that runs a daily cert renewal check. This will automatically renew your cert. Nothing happens if cert has not expired. If the certificate does expire, you'll get an email about it from Let's Encrypt at the email address you provided during setup.
  3. Switches the script to use the webroot plugin with /var/www/discourse/public as the directory. This will allow us to use nginx as the server that handles domain validation. Zero downtime during cert renewal!


Check logs for cert related errors

./launcher logs <container name>

Then look for any errors mentioning letsencrypt or SSL.

Did the cert files get written OK?

ls -l /var/discourse/shared/standalone/ssl on the host server

You should have a certificate .cer and key .key file for your domain, and they should have ~3k filesize.

-rw-r--r-- 1 root root  424 May 18 03:22 dhparams.pem
-rw-r--r-- 1 root root 3823 May 18 04:24 discourse.example.com.cer
-rw-r--r-- 1 root root 3243 May 18 04:24 discourse.example.com.key

Manually reissue the cert

./launcher enter app
sv stop nginx
/usr/sbin/nginx -c /etc/nginx/letsencrypt.conf
LE_WORKING_DIR=/shared/letsencrypt DEBUG=1 /shared/letsencrypt/acme.sh --issue -d example.com -k 4096 -w /var/www/discourse/public
LE_WORKING_DIR=/shared/letsencrypt /shared/letsencrypt/acme.sh --installcert -d example.com --fullchainpath /shared/ssl/example.com.cer --keypath /shared/ssl/example.com.key --reloadcmd "sv reload nginx"
/usr/sbin/nginx -c /etc/nginx/letsencrypt.conf -s stop

Delete the old cert files and try rebuilding again

rm -rf /var/discourse/shared/standalone/ssl
rm -rf /var/discourse/shared/standalone/letsencrypt
./launcher rebuild app

When discourse is setup as a root domain (e.g. without "www"), the certificate is issued to the root domain only. Is there a way of configuring this so that both the root and the www versions work - e.g. so that we don't get certificate errors on www.example.com when discourse is setup for example.com.

I believe Neil's script allows for this using the -d option to specify multiple hosts for the certificate (so a SAN is used in the cert), so possible that we need to cater for this in the web.letsencrypt.ssl.template.yml somehow. Maybe by adding another variable into app.yml (e.g. DISCOURSE_ALT_HOSTNAME: 'www.example.com'), and then pick this up in the script in web.letsencrypt.ssl.template.yml.

Meanwhile, can we manually run commands to do this without anything being overwritten on restart? Or is it okay to edit the conf file for the domain and set a value for Le_Alt ? If we alter the value for Le_Alt, how do we then force a reissue and install of the cert?



Adding multiple domains has been done before. I'll like to bake this into the template though, we should probably take in an additional env variable and add that into the issue command. Hopefully someone from the community can take this? :stuck_out_tongue:


@tgxworld Thanks for the link. If I make these changes, I'm assuming that the script won't update the cert until renewal because it will see a valid certificate already installed. How do I force it to update?



Run the issue command manually with FORCE=1 :slight_smile:


@tgxworld Thanks for the tip.

I didn't implement the file changes in app.yml, partly because I wanted to keep the build as standard as much as possible.

However, using the info from that post and your tip on FORCE=1, I did the following (posted here just in case someone else wants to do the same):

./launcher enter app
vi /etc/runit/1.d/install_ssl_cert
sv stop nginx
FORCE=1 /etc/runit/1.d/install_ssl_cert
sv start nginx

In the edit of install_ssl_cert, I changed the first line (after #!/bin/bash) to:

LE_WORKING_DIR="/shared/letsencrypt" /shared/letsencrypt/le.sh issue no example.com www.example.com 4096

Checked the certificate after install and it is now complete with and without "www".

Thanks for your help.


Additional note to noobs running CloudFlare DNS:

You need to enable Full SSL in CloudFlare settings, or your site won't work.

Installation wen't smoothly and running this now on my small side project. Giving it some more time to mature, before enabling on my big site.

Once again, great job guys!



Does Let's Encprypt usage add any steps for site migration from one server to another? Any added complexity or best practices in the backup -> setup new -> restore process?

What I am thinking is that will Lets Encrypt / HTTPS work at the point at the point of the process when the new target server does not yet have a domain configured (runs from IP address).


No, you cannot request a certificate from Let's Encrypt for a machine until DNS is configured to send requests to that machine.


Yup, I discovered that. So an additional migration step is needed for enabling Let's Encrypt and then rebuilding.


Is it possible to (easily) change when the script agrees to renew the script?

At the moment, it looks like the cert renewal is about 9 days before it expires. In the meantime, Let's Encrypt has sent me two reminder emails saying that the cert is going to expire.

I'd prefer to have the cert renewed as soon as it possibly can so that I only get the LE reminder emails if, for some reason, the renewal process has failed and I therefore need to investigate further.



Any guide to do this with nginx? I use port 80. The guide just says "stop"


The full quote from the guide is:

So it's not so much about what port you are using but whether you are hosting other websites on the same machine. If that is the case, the guide says stop because you are not supposed to follow this guide but rather one that tells you how to configure your (outer) webserver to use SSL. In this setup, discourse doesn't know and doesn't need to know that you are using SSL because it sits behind the webserver that takes care of the SSL connection.

See for example here:


Got it working ;D I got confused by the .conf file when the rest of my nginx sites didn't have one! Found some great guides -- I had to comment out ssl_session_tickets off for it to work without some type of ssl_error. Here is the old old guide I used in combo with this:


If you put it into /etc/nginx/sites-enabled/you can skip the .conf ending because NGINX reads all files in that directory (but only .conf files in /etc/nginx/conf.d/)


8 posts were split to a new topic: Using HTTPS, one client can't connect


So, uh, that link is dead. What is the status on implementing SANs? I would really like to have my certificate cover WWW and the root of my subdomain, so that I don't face any mismatch errors when I access WWW over HTTPS. Could you consider just adding this in the next commit, since it doesn't seem to hard?

Also, could we add the Let's Encrypt setup to the open-source version at https://github.com/discourse/discourse?

Let me also add that I have tried all of the workaround in all of the replies to this topic, and so far nothing has worked.


Let's Encrypt is in the open source version of Discourse. There is only one version of Discourse, the open source one.

There are many links in this topic, so it would be helpful to point out which one is dead.

If you're confident that adding support for subjectAltNames wouldn't be hard, I encourage you to get it working and submit a pull request.


Let me clarify, I mean this:

"Has been done" is a link, but the link is a 404 on Meta.

When I said the open-source version, I was referring to the non-docker installation, which I know you don't support.

As far as doing that, I suppose I could experiment on a new droplet once I have some more funds, so I'll get right on that.


Hmm, that's interesting... it works for me:

<clickety click> oh, I see the problem... the topic's been marked as deleted, but that only shows on the first post; the rest of the topic looks normal... and for a 93-post topic, that's somewhat sub-optimal. @codinghorror, should we fix the UI so that the entire topic looks deleted, not just the first post? Also, any objection to raising that topic from the dead, and just leaving it closed, for future generations to marvel at?