Allow SSL / HTTPS for your Discourse Docker setup

So you’d like to enable SSL for your Docker-based Discourse setup? Let’s do it!

This guide assumes you used all the standard install defaults – a container configuration file at/var/discourse/containers/app.yml and Discourse docker is installed at: /var/discourse

Buy a SSL Certificate

Go to namecheap or some other SSL cert provider and purchase a SSL cert for your domain. Follow all the step documented by them to generate private key and CSR and finally get your cert. I used the apache defaults, they will work fine.

Keep your private key and cert somewhere safe.

Place the Certificate and Key

Get a signed cert and key and place them in the /var/discourse/shared/standalone/ssl/ folder

Private key is:


Cert is


File names are critical do not stray from them or your nginx template will not know where to find the cert.

Have a look at your app.yml configuration file to see where the shared folder is mounted.

  - volume:
      host: /var/discourse/shared/standalone
      guest: /shared

In essence the files must be located at /shared/ssl/ssl.key /shared/ssl/ssl.crt inside the container.

For all clients to find a path from your cert to a trusted root cert (i.e., not give your users any warnings), you may need to concatenate the cert files from your provider like so:

cat "Your PositiveSSL Certificate" "Intermediate CA Certificate" "Intermediate CA Certificate" >> ssl.crt

Configure NGINX

Add a reference to the nginx ssl template from your app.yml configuration file:

  - "templates/postgres.template.yml"
  - "templates/redis.template.yml"
  - "templates/sshd.template.yml"
  - "templates/web.template.yml"
  - "templates/web.ssl.template.yml"

Configure your Docker Container

Tell your container to listen on SSL

  - "80:80"
  - "2222:22"
  - "443:443"

Bootstrap your Docker Container

Rebuild your app

./launcher rebuild app

Profit, you are done!


Be sure to read through the logs using

./launcher logs app

If anything goes wrong.

How this works

The template used is vaguely based on @igrigorik’s recommended template with two missing bits:

  • I skipped OSCP stapling cause it involves a slightly more complex setup
  • I had to skip session tickets setting which is not available until we use mainline

The image has rewrite rules that will redirect any requests on either port 80 or 443 to https://DISCOURSE_HOST_NAME , meaning that if you have a cert that covers multiple domains they can all go to a single one.

Customising this setup is very easy, see:

You can make a copy of that file and amend the template as needed.

The advantage of using templates and replace here is that we get to keep all the rest of the Discourse recommended NGINX setup, it changes over time.

Testing your config

See SSL Server Test (Powered by Qualys SSL Labs) to make sure all is working correctly. It is possible for some browsers and OS combinations to be happy with partially configured https, so check it here first.

I need help with SSL
Broken image since https
NGINX Proxy Mixed Content Error
Troubles installing SSL
SSL on Discourse / DO sub-domain of Heroku hosted domain
Can i change Lets Encrypt to EssentialSSL / Wildcard SLL
SSL Let's Encrypt Error After Installation
How to Set Up SSL in Discourse
I have a very difficult problem installing ssl - please help
Site down after enabling SSL
Go Daddy SSL certificate installation error in D.O. server
How to force redirect from https to http on Docker installation
SSL installation
How to modify Dockerfile?
DNS validation for Let's Encrypt?
How might we better structure #howto?
Hit Let's encrypt renewal limit
Unable To Connect/Connection Refused due to https certificates
Setting up Discourse with SSL on Docker with AWS ELB breaks and returns 503 Service Unavailable (Back-end server is at capacity)
Disabe letsencrypt failed and Run discourse-setup had some not normal alert
Global setting to hide origin IP from everywhere - is it possible?
Install Discourse on Amazon Web Services (AWS) with Cloudflare
Disabe letsencrypt failed and Run discourse-setup had some not normal alert
Run other websites on the same machine as Discourse
Latest update requires cache purge in CloudFlare
Install Paid SSL with Cloudflare on Discourse
Force Discourse to use SSL/HTTPS through CloudFlare
Unable To Connect/Connection Refused due to https certificates
Configure direct-delivery incoming email for self-hosted sites
How Do I Uninstall SSL Certificate?
Run other websites on the same machine as Discourse
Transfer from bitnami to normal discourse
My site is down with a weird SSL notification
Set up HTTPS support with Let's Encrypt
Https with let's encrypt behind a vpn?
Cannot install custom SSL new_file: no such file
Favicon is failing to load for logged-in users
Cannot connect to IP address and no errors in log
How to install SSL certificate in Discourse

Quick question. What is the best way to handle intermediate certs? Should I include the .pem file by modifying the template and adding a line like:

ssl_certificate /shared/ssl/bundle.pem;

Will this work, or do I need to do something else? Perhapse cat everything into a single .pem? Thanks.

1 Like

Nginx doesn’t support intermediate certs as separate files, so you’ll need to concatenate your intermediate cert and your server cert together to create a chain certificate:

cat server_certificate.pem intermediate_cert.pem > chain_cert.pem

Thank you for the guide. Still works in May 2018.

Just for a later newbie like me:
I used godady ssl certificate. Godaddy will give you two .crt files. One is a randomly named file like “bd1ab39ff96d6ed5.crt”, another one is “gd_bundle-g2-g1.crt”. The randomly named one is “Your PositiveSSL Certificate”, and the “gd_bundle-g2-g1.crt” is (godaddy’s, I guess) “Intermediate CA Certificate” as mentioned below. If you get them in the wrong order, you will get a key values mismatch error. Check here for more.

1 Like

What does it mean?

I have intermediate.crt how can i concatenate the cert files?

From all I remember doing this years ago, it simply copying and pasting a bunch of chunks into a single file.

I do however recommend just forgetting about this mess and going with lets encrypt.


I just installed a new instance of discourse on one of our machines and placing the certificates as per your recommendations does not work.

Every-time I need to relaunch the application, I need to copy the SSL certificates over in order to get Nginx to accept them:

cp /root/certificates/<fqdn>* /var/discourse/shared/standalone/ssl/

where the /root/certificates folder contains the following files:

# ls -lha /root/certificates/<fqdn>*
-rw-r--r-- 1 root root 1.5K Mar 26 14:52 /root/certificates/<fqdn>.cer
-rw-r--r-- 1 root root 1.5K Mar 26 15:36 /root/certificates/<fqdn>_ecc.cer
-rw------- 1 root root 1.7K Mar 26 15:37 /root/certificates/<fqdn>_ecc.key
-rw------- 1 root root 1.7K Mar 26 14:51 /root/certificates/<fqdn>.key

Two points are surprising me:

  1. Every time the application is rebuilt, the certificates are erased and replaced by invalid ones
    That makes me wonder where the new certificates get regenerated each time
  2. I need to have 2 pairs of files within the ssl folder: <fqdn>.[cer|key] and <fqdn>_ecc.[cert|key]
    What is the difference between the _ecc certificates and the regular ones ?



First, the usual question: what is the reason you’re going through this process, rather than using automatically renewing certs via Let’s Encrypt? Manual certs are more complicated, require a deeper knowledge of Linux system administration and cryptography, and of course aren’t free.

What are the certs? Have you looked at them? The only thing I can think of that would be creating certs automatically is Let’s Encrypt. If they’re being created, they should be valid…

ECC is Elliptic Curve Cryptography, a type of key algorithm. Presumably that cert uses the ECDSA algorithm while the “regular” one uses a different algorithm.


What does your app.yml look like? Do you have the letsencrypt template installed for some reason? That could be overwriting your certs, maybe.

What do the invalid ones look like?

1 Like

You are absolutely right. Unfortunately, our instance is not accessible from the internet (people need to connect through VPN to access it)…

Due to GDPR restrictions and various local laws about confidential information, it is usually easier to prevent access from Internet…

1 Like

I left the default in the app.yml which uses Let’s Encrypt. This was certainly an error from my side. The key is an empty file. I believe it comes from the fact that Let’s Encrypt fails to connect back to the machine.

1 Like

Since you’re not using Let’s Encrypt, you should not use let’s encrypt, so just delete or comment out that template from your app.yml.

Also, you can edit your posts rather than replying to yourself a bunch of times.


Thanks for the reply. You are right the Let’s Encrypt template is activated.

That was it. Thanks!
Sorry about the replies to myself. I just wanted to quote the portion of you answer so you would know which part of your answer I was referring to.


Glad you got it. FYI (we’re all here to learn) you can repeatedly select text and click quote–even if you navigate to other topics!


Thank you. It works great!

1 Like

Is there a command option to install a new TLS certificate without running “./launcher rebuild app”?

I ask because our servers are running on v2.4.0beta5 with a custom plugin that breaks on anything after v2.5. When I run “./launcher rebuild app,” my system magically gets upgraded to v2.6.0.beta1 :grimacing:

I understand the right way to go about this would be to hire a developer to re-write the plugin for v2.6.0beta1, but the TLS cert expires in a month, and I feel concerned that I may not have enough time for them to complete the work.

Sure is, look at my posts from January in this topic.

1 Like

If you’re using a standard install, it’ll renew on a couple of days.

Thank you for getting back to me so quickly. I used ./launcher restart app - and it picked up the renewed Comodo/Sectigo TLS cert without any problems! LIFESAVER. I will sleep better tonight. :sleeping:

1 Like

Thanks for this guide. I am running into a bizarre problem when trying to use an external NGINX on the same server.

I did the following:

  1. Commented out the ssl templates in the app.yml
  2. Set up certbot to manage certs for my external NGINX
  3. Turned on “force https” in the discourse setttings

However, I am getting the “mixed content” warning and it’s only for a single resource: /uploads/default/optimized/1X/_129430568242d1b7f853bb13ebea28b3f6af4e7_2_512x512.png. This appears to be the site icon. I can’t figure out why this single image is being served over http while everything else is https…

edit: I fixed it by uploading a new site icon. I was using the default that it shipped with previously…