Using Let’s Encrypt with Express

HTTPS is starting to become more and more of a trend on the internet. This is great because clients can safely trust the server that they are communicating with, which is pertinent, especially when dealing with sensitive data. Using HTTPS requires an SSL certificate. This is essentially a verification of the identity of the server. Getting an SSL certificate used to be a difficult process until Let’s Encrypt came on the scene.

Let’s Encrypt uses Electronic Frontier Foundation’s Certbot to automate the process of getting an SSL certificate. They support a few different types of webservers (Apache, nginx, etc.) running on various Unix based systems. If your stack matches one of those supported options, the process is fairly automatic. Unfortunately, Node.js/Express.js is not one of the ones listed. That means we can’t leverage the automated process through Certbot. All is not lost though, the manual process isn’t all that difficult as we will see.

Some Background

We’re going to be using Certbot in webroot mode by passing in the --webroot switch. In short, Certbot will put a file somewhere under our server directory that we have to make sure to serve over HTTP. With Express, this can be done using the express.static() function. Reading over the Certbot documentation for Webroot mode, we can see that Certbot will look for the hosted file at the http://<your_server_url>/.well-known/acme-challenge/ path. If it can successfully retrieve the file that it placed in that path on the server through HTTP, then it will create an SSL certificate for you!

So let’s get started!

The Process

At a high level, we’re going to have 5 steps,

  1. Forward the right ports
  2. Set up the static assets directory structure and serve it through Express
  3. Install and run Certbot
  4. Setup Express to use HTTPS
  5. Renew your Let’s Encrypt certificate

There are code samples and commands included in these steps.

Forwarding Ports

This step is included purely for the sake of completion. As part of the verification process, a server URL will be required. Certbot will use this URL to contact your server to fetch a file over HTTP. This means that port 80 at the provided URL should be accessible to the internet. It is a good idea to have port 443 accessible as well since that is the HTTPS default port.

Personally, I like to run my Express servers at port numbers higher than 1024 and then use a port-forwarding rule to forward traffic from port 80 or 443 to the correct port on the server machine. This means that I don’t have to give Express elevated permissions on the system which is safer especially since the webserver is going to be handling traffic from possibly unverified origins.

You can verify your network plumbing by using curl to fetch a known endpoint, like a health check endpoint (always a good idea to have on your server).

// filename: app.js
const app = require('express')();
app.get('/health-check', (req, res) => res.sendStatus(200));
app.listen(8080);

Then fetch the health-check endpoint with curl and make sure you get an HTTP 200 back.

curl http://<your_server_url>/health-check

Now that we have our network and server set up, we can set up serving static files.

Serving Up Static Files

As stated above, the path that Certbot will look for to verify your webserver is /.well-known/acme-challenge . Express uses the express.static() function to serve static files from a path given to the function. The path provided becomes the root of your webserver. Often, the folder that holds all the static content for a website is named public or static and if you had a text file under /static/test-text/mytextfile.txt , you could get to it by fetching http://<your_server_url/test-text/mytextfile.txt . Knowing that, let’s create the directory structure for Certbot and wire it up in Express.

cd static
mkdir -p .well-known/acme-challenge

The commands above are run from the project root and assume that the static content folder is called static .

// filename: app.js
const express = require('express');
const app = express();
app.use(express.static('static'));
app.listen(8080);

Now that express is wired up to serve the correct path, let’s test it.

echo "this is a test" > static/.well-known/acme-challenge/9001
curl http://<your_server_url>/.well-known/acme-challenge/9001

This should print out “this is a test” in your console! Success!

Let’s generate us a new SSL certificate.

Certbot

The first step here is to install Certbot. Instructions can be found here. Pick none of the above as software and pick your OS. You should see the install command on the page. For example, the command to install on Ubuntu 16.04 is sudo apt-get install letsencrypt .

The next step is to generate our certificate. As mentioned above, we’re going to run Certbot in Webroot mode. This will require two pieces of information, a path to use as the webroot (using the -w switch) and domain name (using the -d switch).

certbot --webroot -w ./static -d <your_server_url>

The command above assumes that you’re in your project directory. After running that command, you’ll see a success message with a location to your certificates. They are usually located in /etc/letsencrypt/live/<your_server_url> . Information about what these files are can be found in the Webroot section of the Certbot User Guide. We’re going to be using the fullchain.pem and the privkey.pem files with our Express server.

Yay! We have a shiny new SSL certificate! Let’s put it to use.

Express and HTTPS

Express, out of the box, only uses HTTP. We can wire up Express to use HTTPS by using the https node module. To do this, we’re going to need two files, a certificate and a private key. As an aside, do not ever share your server private key and only let authorized users access the private key file.

I also recommend either copying fullchain.pem and privkey.pem into your project directory or creating symbolic links to them. Creating symbolic links makes the renewal process easier but it depends on your preference.

The code assumes that you have fullchain.pem and privkey.pem in a folder called sslcert in your project directory.

// filename: app.js
const https = require('https');
const fs = require('fs');
const express = require('express');
const app = express();
// Set up express server here
const options = {
cert: fs.readFileSync('./sslcert/fullchain.pem'),
key: fs.readFileSync('./sslcert/privkey.pem')
};
app.listen(8080);
https.createServer(options, app).listen(8443);

It’s also a good idea to use Helmet.js with this setup. Helmet.js helps secure Express servers through setting HTTP headers. It adds HSTS, removes the X-Powered-By header and sets the X-Frame-Options header to prevent click jacking, among other things. Setting it up is simple.

npm install --save helmet

Then you can tell Express to use it as a middleware.

// filename: app.js
app.use(require('helmet')());

You can now use the SSL Server Test to verify your server. Green locks for everyone!

But wait, when does this expire? How easy is the renewal process?

Renewing Your Certificate

Let’s Encrypt certificates only last for 90 days, for better or for worse. The renewal process is simple though. We just need to run letsencrypt renew and Certbot will issue you a new certificate. It’s recommended to automate this renewal process using either a cronjob or something like systemd .

That’s it! We have an SSL certificate for an Express server. Done and dusted.

Conclusion

To recap, we set up Express to serve static files at a specific path, used Certbot in webroot mode to generate a certificate for our server and then wired up HTTPS with Express using the newly generated certificate. While we weren’t able to use the automated Certbot process, the manual process we followed wasn’t much more complicated.

Hopefully, Certbot will have some Node.js support soon!