Setting up HTTPS in Node.js with Let’s Encrypt

Note: this post was updated on 10/21/16, in the crontab entry for Setting up automatic certificate renewal

Check out my GitHub repo for detailed examples. Also, see: Deploying a Node.js app to DigitalOcean.

In our application amblr, my team and I are using geolocation data from the browser to place the user on a map. As of Chrome version 50, an encrypted connection is required for using location services, per this announcement:

Starting with Chrome 50, Chrome no longer supports obtaining the user’s location using the HTML5 Geolocation API from pages delivered by non-secure connections. This means that the page that’s making the Geolocation API call must be served from a secure context such as HTTPS.

To get HTTPS working in the browser, we used Let’s Encrypt, a new certificate authority that is ‘free, automated, and open.’ I took on the task of setting up the SSL certificates, with some help from a team member who has more command line chops than I do. I learned some useful things along the way.

How Web Servers Work

Servers have both an operating system and server software. Generally, the OS is some fork of Linux; Ubuntu is a popular example which is an option during setup if you use DigitalOcean for hosting. In addition to the OS, server software like Apache or Nginx allows you to serve files and perform routing and other tasks. Node takes the place of Apache/Nginx in simple setups. There may be some situations where you have both Apache/Nginx and Node running. For example, one way to set up SSL is to use Apache as a reverse proxy server that sits between the open Internet and your Node server. We tried this approach, but ran into roadblocks with the configuration file.

Instead, we chose to serve our site directly from Node, so we didn’t want Apache/Nginx to be running at the same time as Node.

Configuring up your Node server

There are a few ways to set up your Node server to use SSL; one of the most straightforward is to use the https module. You’ll need to do the usual routing and set up a static file server. Here’s a simplified server.js file using Express that shows how we set up https with an http server forwarding to it. This allows you to forward requests that come in on port 80 (the default port for http) to port 443 (the default port for https).

For more details, check out my GitHub repo for this post.

Note that you have created a regular HTTP server as a temporary way to set up Let’s Encrypt with the --webroot plugin. You’ll modify the server to switch to HTTPS once you’ve created the certificate files.

path-to-static-files is the directory where you serve static files from with your Node server. This will be important when using the webroot method to renew your certificates.

path-to-privkey is the location of your private key file. path-to-cert is the location of your certificate file. If you follow the directions above, they will be in the following locations –  replace ‘node-https-example.com’ with your domain:

path-to-privkey:

/etc/letsencrypt/live/www.node-https-example.com/privkey.pem

path-to-cert:

/etc/letsencrypt/live/www.node-https-example.com/cert.pem

Configuring Let’s Encrypt

To configure Let’s Encrypt, I referenced this tutorial, but modified the steps to work with a Node-only setup. Instead of using --apache, I used --webroot to set up and renew the certificates. You’ll be installing the Let’s Encrypt Certbot, an automated command line tool that helps you get things configured.

These directions were tested with Ubuntu 16.04.1 on DigitalOcean; other setups may require slightly different steps. Before you start, you’ll need to set up your server with a non-root user that has sudo privileges and configure your domain name.

  1. SSH into the server.
  2. If your Node server is not running already, start it: cd into the directory where your server file is, then do:
    sudo node server.js

    Using sudo is necessary because root privileges are required to expose ports below 1024. If you see an EACCESS error when you try to start Node on a server, it’s usually because you didn’t use sudo or because Node is already running.

  3. In another terminal window, SSH into your server and update the package manager cache:
    sudo apt-get update
  4. Install git:
    sudo apt-get install git
  5. Install Let’s Encrypt:
    sudo git clone https://github.com/letsencrypt/letsencrypt /opt/letsencrypt
  6. Navigate to the Let’s Encrypt repository:
    cd /opt/letsencrypt
  7. Run the configuration, inserting the folder where your site lives instead of node-https-example and your SSH user instead of user. The second flag for -d www.node-https-example.com is optional, but recommended for encrypting traffic at both the http:// and http://www domains.
    ./letsencrypt-auto certonly --webroot -w /home/user/node-https-example/client/www -d www.node-https-example.com -d node-https-example.com
  8. Follow the steps provided and be sure to include an admin email address, in case there are any issues renewing your certificates.
  9. Go to the terminal window where Node is running and enter CTRL + C to stop the Node server.
  10. Using vi server.js or another bash text editor, comment out the lines in the first block, starting from:
    http.createServer(app).listen(80);
  11. Uncomment the bottom portion of the file, starting from:
    // // set up path to key and certificate files
  12. Save and close the file. This will effectively swap out your HTTP server for an HTTPS server.
  13. Start the Node server with sudo node server.js. Note that the server will stop once you close your terminal window. See this post to keep Node running forever.
  14. To see if it worked, go to this URL, replacing ‘node-https-example.com’ with your domain:
    https://www.ssllabs.com/ssltest/analyze.html?d=node-https-example.com&latest

Setting up automatic certificate renewal

To set up automatic certificate renewal, use the cron job as described below. It will try to update the certificate every Monday morning at 2:30 am. If the certificates are less than 30 days away from expiring, they will be renewed.:

  1. SSH in to the server if you aren’t already logged in.
  2. Edit the crontab file for the root user. If necessary, go ahead and create a new crontab for root:
    sudo crontab -e
  3. Add this line to the file to set up the cron job. It will save a log of the results to /var/log/le-renewal.log so you can check back later and see if the renewal is working. Replace user with your SSH user and node-https-example with the folder where your site lives.
    30 2 * * 1 /opt/letsencrypt/letsencrypt-auto certonly --webroot -w /home/user/node-https-example/client/www -d www.node-https-example.com -d node-https-example.com >> /var/log/le-renew.log
  4. Save and exit.

Conclusion

That’s it! You should now have valid SSL certificates that auto-renew, and your site should be accessible via HTTPS.

For more reading, check out the Certbot documentation.

Deploying a Node.js app to DigitalOcean

To set up a domain name, see How To Set Up A Host Name With Digital Ocean.

If you need to serve your site securely over HTTPs, see Setting up HTTPS in Node.js with Let’s Encrypt.

So you want to deploy that app you made with Node? Well, DigitalOcean is one way to do it. They provide cheap, fast SSD-based virtual servers, or ‘droplets’, that you can spin up from their website. Their product is geared towards developers – they assume you have some proficiency with the command line and are comfortable configuring your own server. Most of what follows is adaptable to AWS, Azure, Heroku, and any other provider of virtual private servers.

A word to the wise: upgrading a DigitalOcean droplet is easy. Downgrading a droplet is hard. If you aren’t sure how much space / memory / transfer you will need, start small and move up later.

I’ll keep this tutorial brief and assume you have a minimum of experience with servers. This setup works well for small apps with light traffic and storage requirements – essentially, the kind of apps you might build as proofs of concept while learning web development.

  1. Create an account with DigitalOcean.
  2. Log in and click Create Droplet.
  3. Select ‘Ubuntu’ at the current highest x64 version. Select the the size you’re comfortable with – remember, if in doubt, start small. Add block storage if you need it, and select a datacenter close to where your users are. Select additional options if you want – most often, backups are a good idea and worth the extra investment.
  4. If you don’t have an SSH key set up yet, it’s a good idea to set one up by clicking New SSH key, and following step 1-4 of the SSH key directions. This prevents you from receiving the root password via email, which is not very secure.
  5. Unless you have reasons to make duplicate droplets, leave the number set at 1. Give your droplet a hostname, and click Create.
  6. Wait while the droplet is created. When it’s ready, you’ll see your IP address – write this down for future use with SSH access in step 9.
  7. Follow the steps in these tutorials to configure your server: Initial Server Setup with Ubuntu 14.04, then Additional Recommended Steps for New Ubuntu 14.04 Servers. This will help you set up SSH access by setting up a non-root user, disabling root access for enhanced security, configuring SSH access, creating a basic firewall, and configuring the time zone. Don’t do the steps for Create a Swap File – you’ll see that this is not recommended for SSD-based servers like the one you just created. Do follow through with taking a snapshot of your current configuration so you can clone your server from this point next time you’re setting up an app. You may want to wait and do this after you follow the next step, so that your snapshot is ready to go with Node installed.
  8. To install Node.js v 6.x, enter this in the SSH terminal:
    curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -
    sudo apt-get install -y nodejs
  9. Next, you’ll need to copy your app’s files from your local machine to your server. You have several options here:
    • Use SCP from the command line. You’ll want to copy files into your home directory – this directory should have the correct permissions set to allow you to remotely add files. Run this from your local terminal – the sudo password here is your local password, path-to-local-site-root is where your site folder lives on your local machine, your-ip-address is the IP from step 6, user is the username you created in step 7, and site is the name of your remote site folder.
      sudo scp -r path-to-local-site-root user@your-ip-address:/home/user/site
    • You can also use an SFTP browser like Cyberduck.
    • If you’re using Git, a very slick way to copy files is with automated Git deployment, as shown here: How To Set Up Automatic Deployment with Git with a VPS.
  10. Start up that Node server! Check out my post Keep Node running forever for details on this. Note that you will need to navigate to your server folder, which is somewhere within /home/user/site before you run Node. Once you start Node, you should be able to open your IP address in the browser and see your site.

 

Keep Node running forever

If you’ve ever started up a deployed Node application, you’ve probably had this problem: you start the server with node server.js, check on your site, and it’s up and running. Great! Some time later, your SSH session times out or you close your terminal. Then you go to your site, and it’s down. What happened?

You ran the server in the foreground, which means that when you log out of SSH, it will be stopped along with all the other running processes.

I’ve found two good solutions. Before using either of them, make sure you are starting the server with root privileges by running sudo -i. This will take you to the root of the server, so you will need to cd back to the folder where the server file is located.

Method 1: Run node in the background

In this method, you start Node with the & flag to run it in the background and the nohup flag to detach it from the console window.

To start the Node server:

sudo nohup node server.js &

To shut down the Node server, first find the running process ID with:

ps -aef | grep node
// root     21404     1  0 08:05 ?        00:00:00 node server.js
// alex     21530 21494  0 08:08 pts/0    00:00:00 grep --color=auto node

Then kill the process via the ID:

sudo kill 21404

More details are in these Stack Overflow posts: run node permanently and stop node.

Method 2: Use Forever to keep the server up

In this method, you install Forever and use it to run the server for you. This method is more robust, because it will restart your server automatically if it crashes.

Make sure to use forever start to run the server as a service.

sudo npm install -g forever
sudo forever start server.js

Now, when you close your terminal window, Node will continue to run. Here are a few helpful forever commands from the StackOverflow post:

To list all running processes:

forever list

Note the integer in the brackets and use it as following to stop a process:

sudo forever stop 0

Restarting a running process goes:

sudo forever restart 0

If you’re working on your application file, you can use the -w parameter to restart automatically whenever your server.js file changes:

sudo forever -w server.js

More info on Forever is available on GitHub.