This guide is intended for advanced users, who are already using nginx outside the docker container. By following this guide you make your setup more complicated and will loose some speed benefits like HTTP2 if you’re not running Ubuntu 16.04 or later. Proceed with caution!
When Discourse is rebuilding or starting up, your users will usually either see an error message from their browser…
…or a not-so-nice 502 error message from Nginx:
If you’re a perfectionist like me, you’ll probably find that unacceptable. Fortunately, fixing this is quite straightforward – so let’s dive right in!
To customize this guide, simply put the domain your Discourse is running on into the box below:
If you’re already using HTTPS and have it set up inside the container (you are using web.ssl.template.yml
and possibly web.letsencrypt.ssl.template.yml
), this howto will move your SSL setup out of the Docker container into Nginx running on the host, and request a new certificate from Let’s Encrypt. This is necessary because we need SSL to work even when Discourse is rebuilding its Docker container or otherwise unavailable. This will break auto-renewal, so you’ll need to manually renew every three months or set up auto-renewal on the host.
Set up nginx
If we want nicer error messages, we’ll need to set up a front-end server that usually forwards all requests to Discourse, but injects our error message when it cannot reach Discourse. This howto uses nginx as the front-end server.
If you’ve already set up nginx on your host (for example to run other websites on the same machine as Discourse), you can skip this section and continue with the next one.
To clear up ports for nginx, we must first tell Discourse to listen on a socket, not on the normal ports:
cd /var/discourse
nano containers/app.yml
Comment out the lines with web.ssl.template.yml
and templates/web.letsencrypt.ssl.template.yml
if they are in use (we’ll set up HTTPS on the host later on), add
- "templates/web.socketed.template.yml"
as the last line in the templates:
section, and comment out all ports from the expose:
section.
Here’s how it should look:
templates:
- "templates/postgres.template.yml"
- "templates/redis.template.yml"
- "templates/web.template.yml"
- "templates/web.ratelimited.template.yml"
## Uncomment these two lines if you wish to add Lets Encrypt (https)
#- "templates/web.ssl.template.yml"
#- "templates/web.letsencrypt.ssl.template.yml"
- "templates/web.socketed.template.yml"
## which TCP/IP ports should this container expose?
## If you want Discourse to share a port with another webserver like Apache or nginx,
## see https://meta.discourse.org/t/17247 for details
expose:
#- "80:80" # http
#- "443:443" # https
When you’re done, save the file, exit and run this:
./launcher rebuild app
If all went well, Discourse will be unreachable because it is now only listening on the local socket. Don’t worry, we’ll soon fix that!
Next, install nginx:
apt-get update
apt-get install nginx
Let’s add a place to host local web content, and then edit the default Nginx default configuration file:
mkdir /var/www
nano /etc/nginx/sites-available/default
Add HTTPS
While we’re here, we’ll configure Nginx to redirect all requests to HTTPS, and to allow requesting a free certificate from letsencrypt. Replace the contents of /etc/nginx/sites-available/default
with this:
server {
listen 80; listen [::]:80;
server_name =DOMAIN=;
location /.well-known/acme-challenge/ {
root /var/www;
}
location / {
return 301 https://$host$request_uri;
}
}
Apply the changes with
service nginx reload
Now we can set up Let’s Encrypt and get a certificate. If you are running Ubuntu 16.04 or later, you can use the official package:
apt-get update
apt-get install letsencrypt
letsencrypt certonly --webroot -w /var/www -d =DOMAIN=
For other operating systems you’ll need to get certbot installed manually and issue the cert like so:
mkdir /var/letsencrypt
cd /var/letsencrypt
wget https://dl.eff.org/certbot-auto
chmod a+x certbot-auto
./certbot-auto
certbot-auto certonly --webroot -w /var/www -d =DOMAIN=
Any errors? Did you get your certificate? If so, let’s proceed!
If you installed certbot from your package repository, renewals usually happen automatically. Otherwise, set a reminder to run
letsencrypt renew && systemctl reload nginx.service
before your certificate expires!
Let’s edit the Nginx config again to add HTTPS support:
nano /etc/nginx/sites-available/default
The old server
block in the file needs to stay – it redirects all your users to HTTPS. We need to add a new server
block below the old server
block:
server {
listen 443 ssl http2; listen [::]:443 ssl http2;
server_name =DOMAIN=;
# ssl on; <-- This directive is deprecated, use `listen 443 ssl` instead.
# change these paths as necessary
ssl_certificate /etc/letsencrypt/live/=DOMAIN=/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/=DOMAIN=/privkey.pem;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
ssl_protocols TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
add_header Strict-Transport-Security "max-age=63072000;";
ssl_stapling on;
ssl_stapling_verify on;
client_max_body_size 0;
location / {
proxy_pass http://unix:/var/discourse/shared/standalone/nginx.http.sock:;
proxy_set_header Host $http_host;
proxy_http_version 1.1;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Real-IP $remote_addr;
}
}
If you’re using a version of nginx that cannot speak HTTP2 yet, remove http2
above (twice). The version of nginx offered in Ubuntu 16.04.1 can handle HTTP2.
Remember to modify the site name and paths as needed above. If you modified the
volumes:
section of yourapp.yml
, you’ll also need to change theproxy_pass
line.
Let’s reload the config we just changed:
service nginx reload
Your site should now be back up, securely over HTTPS.
Now tell Discourse that it should always use HTTPS URLs by checking force https
in Site Settings.
We recommend that you run SSL Server Test (Powered by Qualys SSL Labs) on your site to verify that your https settings are secure and safe.
Create an error page
Next, you’ll have to design an error page to show when Discourse is offline. Let’s create a path for it.
mkdir /var/www/errorpages
- If you’re a talented designer, feel free to build a beautiful page yourself, and share it here!
- If you need to use external resources like images, load them from
/errorpages/
. - I recommend that you include
<meta http-equiv="refresh" content="120">
in your page – this will refresh the page every 120 seconds, which means that Discourse will load automatically once it’s available again. - Name your main HTML file
discourse_offline.html
, and place all files in/var/www/errorpages/
.
If you’re happy with a page made by an untalented designer, you can simply steal my design instead
discourse_offline.html (1.9 KB)
d-logo-sketch.png (14 KB)
sob.png (1 KB)
(If you need help copying these files to your server, this post may help.)
Once you’re done, edit the Nginx config to serve the page you just created:
nano /etc/nginx/sites-available/default
Simply add
location /errorpages/ {
alias /var/www/errorpages/;
}
to the HTTPS server
section of your nginx config, and then reload.
service nginx reload
Test by visiting https://=DOMAIN=/errorpages/discourse_offline.html in your browser – you should see your new error page
Serve your error page
Finally, we’ll set up nginx to serve your error page when it cannot reach Discourse, or when Discourse isn’t ready yet. Add the following lines to the location /
block:
error_page 502 =502 /errorpages/discourse_offline.html;
proxy_intercept_errors on;
Apply your changes, as usual:
service nginx reload
If you want to test your settings, run
cd /var/discourse
./launcher stop app
to take your site offline and try to visit it:
Don’t forget to run
cd /var/discourse
./launcher start app
afterwards so your Discourse is available again!
P.S: Don’t hope or try to repeat this procedure more than 2-3 times in a week. Because Letsencrypt won’t allow you more than 5 times to fetch certificates from their servers. If you struck that limit anyway, either you’ll have to wait till 7 days, counting from the day you requested 1st certificates in the series of 7 tries, or you’ll have to setup your site in some sub-domain, like by pre-pending ‘www’ to it.