Using Let’s Encrypt with Plotly/Dash (On NearlyFreeSpeech.net)

3 minute read

Published:

I use NearlyFreeSpeech to host my Dash/Flask app Waveplot and it works quite well. Part of running a website is caring about users and their data, so its best to always use https so that data is encrypyted when sent between the server and the user. Not everyone does this however, and turning on the chrome warning for non https sites is quite alarming!

To use https you need an SSL certificate to prove to the user that you are who you say you are. This way, any attempt to intercept data should be stopped if the correct certificate isn’t produced somewhere along the line. Certificates are handed out by certificate signing authorities (CSAs) and they usually make you go through a process of adding a small file to your website, or a DNS record to your DNS config, so that they can authenticate you as the owner of the site and give you a certificate.

There are loads of CSAs - some charge, some don’t, some are attached to businesses and governments. Like everything, it’s complicated. If you want to know more, then take a look at the Cloudflare Learning Center.

NearlyFreeSpeech

NearlyFreeSpeech provides a bash script tls-setup.sh which uses Let’s Encrypt to create certificates for your website for free. Alternatively you can do it manually using dehydrated, but I certainly didn’t and have no desire to.

The tls-setup.sh script uses a http01 challenge to verify that you’re certificating your own website. In short, it temporarily adds a file at www.YOUR_WEBSITE_NAME.TLD/.well-known/acme-challenge/RANDOM_FILE_NAME and checks that it can access it while the website is live. If it can, you get the certificates, and it deletes the temporary file.

The problem

My Dash app uses the Dash Pages functionality to have multiple pages in a single app. It works really well, and redirects you to the app you want based on a couple of rules that you configure. The problem is that if no page_name.py exists for the page you want, then you get a 404. Even worse, you can’t just quickly add a .py file for the http01 challenge file, since its name is totally random! Instead, you can add a flask route to any file in the location that Let’s Encrypt uses for the challenge file, and what’s great is that flask supports wildcards.

The Waveplot directory is laid out as

app.py
index.py
pages
├─ app_1.py
├─ app_2.py
├─ app_3.py
site.wsgi

where site.wsgi contains instructions for wsgi to run my site. In my NearlyFreeSpeech site storage space, I’ve put the waveplot directory in /home/protected, and I want to point to the public/.well_known/acme-challenge directory where the challenge file will be placed, so I added the following lines to app.py (after defining app=dash.Dash(...))

import flask
# Certificate http01 challenge
@app.server.route("/.well-known/acme-challenge/<path:filename>")
def http01_respond(filename):
    return flask.send_file(
        "../../public/.well-known/acme-challenge/{}".format(filename)
    )

and restarted the daemon running wsgi. Then I ran tls-setup.sh (it doesn’t matter where), which produced all the certificates I needed and hands them to NearlyFreeSpeech.