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 - no thanks!

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/FILENAME, checks that these files can be accessed by the CSA, and deletes them.

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. One feature of this is that if no page_name.py exists for the page you want, then you get a 404. Unfortunately this means that when the challenge is issued, any attempt to access the file at /.well-known/acme-challenge/FILENAME fails.You might think that you can just quickly add a .py file for the http01 challenge file, but this doesn’t work since its name is totally random! Instead, you can add a flask route to the location of the challenge file using a wildcard.

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 handed them to NearlyFreeSpeech.