Using Let's Encrypt and Nginx to get free SSL certificates for your Shopify app.

I'm working on the next version of The Shopify Development Handbook, which will cover building Shopify apps with DotNet Core and Asp.Net Core – sign up here to get an email when it releases (you'll also get a free guide on getting started with Shopify apps using ASP.NET MVC) – and I'm planning to include a chapter on using Let's Encrypt to get free SSL certificates for your Shopify app.

Since DotNet Core apps can be deployed natively on Ubuntu without using Mono, and because I like to host multiple small apps per server, I've ended up using Nginx with Let's Encrypt to easily serve free SSL certs for all of my Shopify apps running Asp.Net Core.

I've used Let's Encrypt in the past and it's been pretty simple. In fact, this website is using it right now!

Let's Encrypt on nozzlegear.com

The problem is that I've only ever used Let's Encrypt from within an app. In this website's case, I'm using the letsencrypt-express Node package for Express servers. That's far from ideal though, because there's only one port 443 (the SSL port) on your server. If you've got one app listening on port 443, then you can't have any other apps listening on port 443.

That's okayish, but I have so many tiny apps that I'm wasting a lot of money by spinning up $5 DigitalOcean servers for each one in an effort to get them all using SSL. I needed a proxy solution that would let me have as many apps as I want on one server, with all of them using SSL certificates from Let's Encrypt.

Nginx is the perfect answer to that, but getting it up and running with Let's Encrypt was more complicated than I had anticipated. I've written this small guide, mostly for myself but also in the hope that it may help others, to get you up and running with Nginx and Let's Encrypt.

This guide assumes that your server is running Ubuntu 16.04. Make sure you replace all instances of my-domain.com with your own domain throughout the rest of this guide. Also note that I may use Lex interchangebly with Let's Encrypt.

Step 1

Install the Lex Certbot to /opt/letsencrypt directory:

sudo apt update
sudo git clone https://github.com/certbot/certbot /opt/letsencrypt

Step 2

Create the /var/www/letsencrypt directory and give Nginx permission to use it:

mkdir -p /var/www/letsencrypt
# www-data is the Nginx username
sudo chgrp www-data /var/www/letsencrypt

Step 3

Create the Lex domain configuration file and open it for editing:

sudo mkdir -p /etc/letsencrypt/configs
sudo touch /etc/letsencrypt/configs/my-domain.com.conf
sudo vim /etc/letsencrypt/configs/my-domain.com.conf

Step 4

Paste the following configuration into the file. Make sure you change my-domain.com and me@my-domain.com:

# Just one single domain.
domains = my-domain.com

# Key size.
rsa-key-size = 4096 # Or 2048

# The current version of Let's Encrypt (as of May 3, 2017) is using this server.
server = https://acme-v01.api.letsencrypt.org/directory

# This email address will receive renewal reminders.
email = me@my-domain.com

# This will run as a cronjob, so turn off the ncurses UI.
text = True

# Place the certs in the /var/www/letsencrypt folder (under .well-known/acme-challenge/).
authenticator = webroot
webroot-path = /var/www/letsencrypt/

Step 5

Install Nginx and add the http and https rules for your app. We'll also create a rule to let the Lex certbot validate certificates in the next step.

sudo apt install nginx -y
sudo vim /etc/nginx/nginx.conf

In vim, editing /etc/nginx/nginx.conf:

events {
	worker_connections 1024;
}

http {
	upstream myAppName {
		# Replace the port with your app's localhost port.
		server 127.0.0.1:3000;
	}

	server {
		listen 80;
		server_name my-domain.com;

		location / {
			proxy_pass http://myAppName;
			proxy_redirect off;
			proxy_set_header Host $host;
			proxy-set-header X-Forwarded-For $proxy_add_x_forwarded_for;
		}

		# Let the Let's Encrypt certbot bypass your app and access the files it needs.
		location /.well-known/acme-challenge {
			root /var/www/letsencrypt;
		}
	}

	server {
		listen 443 ssl;
		server_name my-domain.com;
		ssl on;
		gzip on;
		ssl_stapling on;
		ssl_stapling_verify on;
		ssl_session_timeout 5m;
		# Note: Some tutorials may tell you to use cert.pem, which will work in most browsers but fails on e.g. Amazon Alexa server requests.
		# Alexa requests need the intermediary certs too, which cert.pem does not have. fullchain.pem does have them.
		ssl_certificate /etc/letsencrypt/live/my-domain.com/fullchain.pem;
		ssl_trusted_certificate /etc/letsencrypt/live/my-domain.com/fullchain.pem;
		ssl_certificate_key /etc/letsencrypt/live/my-domain.com/privkey.pem;

		location / {
			proxy_pass http://myAppName;
			proxy_redirect off;
			proxy_set_header Host $host;
			proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
		}
	}
}

Save the file, test the configuration and reload Nginx:

sudo nginx -t && sudo nginx -s reload

Step 6

Run the Lex certbot and create your domain certificates:

cd /opt/letsencrypt
./certbot-auto --config /etc/letsencrypt/configs/my-domain.com.conf certonly

If successful, you should see a message along the lines of "Congratulations! Your certificate and chain have been saved at $path".

Step 7

Start your app and try to access it at both http and https. If all of the previous steps were successful, you should be able to reach both. There should be no extra configuration necessary as far as your app is concerned; e.g. you don't need to create a http.server and https.server to use SSL in Node Express, you just need the http.server.

(However, in Node Express you should make sure you have Express server set to trust proxies with app.enable("trust proxy").)

If it doesn't work, make sure the port was correct in the upstream myAppName section of /etc/nginx/nginx.conf.

Automating renewal of Lex certs.

Lex certs are only valid for 90 days, after which they need to be renewed. The certbot doesn't support automatic renewals right now, but since we're running Ubuntu we can just set up a cron job to do it.

Create a sript named renew-letsencrypt.sh in a directory that cron has permissions for (e.g. your home directory at ~/ when running the cron job as your own user).

Remember to update this script with your domains whenever you add a new one!

vim ~/renew-letsencrypt.sh

In vim:

#!/bin/sh

mkdir -p /var/log/letsencrypt;
cd /opt/letsencrypt/;
./certbot-auto --non-interactive --keep-until-expiring --agree-tos --qui
et --config /etc/letsencrypt/configs/alexa.nozzlegear.com.conf certonly;

if [ $? -ne 0 ]
 then
        ERRORLOG=`tail /var/log/letsencrypt/letsencrypt.log`
        echo -e "The Let's Encrypt cert has not been renewed! \n \n" \
                 $ERRORLOG
 else
        nginx -s reload
fi

exit 0;

Finally, open crontab to add your cron job:

crontab -e -u yourUserName
# In crontab
0 0 1 JAN,MAR,MAY,JUL,SEP,NOV * ~/renew-letsencrypt.sh

And that's it! You've got an Ubuntu server running Nginx, serving SSL certificates with Let's Encrypt, and an automatic cron job that renews your certs every two months.

If you're interested in building Shopify apps, I'm writing an update to The Shopify Development Handbook that will walk you through every step using the new DotNet Core and Asp.Net Core. You can join the mailing list here to get an email as soon as it's launched. You'll also get a free guide which helps you get started building a Shopify application using the full .NET Framework and ASP.NET MVC.


Learn how to build rock solid Shopify apps with C# and ASP.NET!

Did you enjoy this article? I wrote a premium course for C# and ASP.NET developers, and it's all about building rock-solid Shopify apps from day one.

Enter your email here and I'll send you a free sample from The Shopify Development Handbook. It'll help you get started with integrating your users' Shopify stores and charging them with the Shopify billing API.

We won't send you spam. Unsubscribe at any time.