Let's Encrypt on App Engine

UPDATE: As of September, 2017, Google App Engine will manage SSL certificates for you.


Let's Encrypt is a fantastically convenient way to get SSL certificates for your website without paying a bunch of money or resorting to a self-signed certificate. It's also pretty easy to set up.

Easy as it is to set up, the instructions don't really explain much about setup for App Engine. It turns out to be pretty straightforward.

Prepare Your App

We need to modify our app.yaml file and set up a static route for the let's encrypt challenge/response data. Add the following to the beginning of the handlers section of your app.yaml file:

- url: /\.well-known/acme-challenge/(.+)
  static_files: letsencrypt/\1
  upload: letsencrypt/(.+)
  mime_type: text/plain

Also create a "letsencrypt" folder in your app directory.

Fire up Let's Encrypt

Starting from the instructions on the Let's Encrypt GitHub repo, let's retrieve and fire up letsencrypt. Outside your app folder, in a separate terminal:

git clone https://github.com/letsencrypt/letsencrypt
cd letsencrypt
./letsencrypt-auto -a manual certonly

We want to run in manual mode and generate certs because there isn't currently a plugin for handling app engine.

Let's Encrypt will ask you for a list of domains. After you enter the target domains, it will show you a series of challenge response requests:

Make sure your web server displays the following content at
http://www.example.com/.well-known/acme-challenge/{ABC} before continuing:

{ABC}.{DEF}

If you don't have HTTP server configured, you can run the following
command on the target server (as root):

mkdir -p /tmp/letsencrypt/public_html/.well-known/acme-challenge
cd /tmp/letsencrypt/public_html
printf "%s" {ABC}.{DEF} > .well-known/acme-challenge/{ABC}
# run only once per server:
$(command -v python2 || command -v python2.7 || command -v python2.6) -c \
"import BaseHTTPServer, SimpleHTTPServer; \
s = BaseHTTPServer.HTTPServer(('', 80), SimpleHTTPServer.SimpleHTTPRequestHandler); \
s.serve_forever()"
Press ENTER to continue

In this case, {ABC} is the challenge and {ABC}.{DEF} is the response.

Don't press enter yet.

Prepare the challenge/response

Switch to the terminal in your app directory and run a modified version of the printf command letsencrypt suggests:

printf "%s" {ABC}.{DEF} > letsencrypt/{ABC}

Now, re-upload your app to app engine:

appcfg.py update .

Finish building the certificate

Now go back and press enter in letsencrypt-auto.

Repeat preparing the challenge/response and pressing enter until you've finished running through all of your requested domains.

Hopefully you should see a message to the effect of:

- Congratulations! Your certificate and chain have been saved at
  /etc/letsencrypt/live/www.example.com/fullchain.pem. Your cert will expire
  on YYYY-MM-DD. To obtain a new version of the certificate in the
  future, simply run Let's Encrypt again.
- If you like Let's Encrypt, please consider supporting our work by:

  Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
  Donating to EFF:                    https://eff.org/donate-le

Upload your certificates

Fire up the cloud console. Go to App Engine > Settings > SSL Certificates and click Upload a new certificate. Give your certificate a name; I like letsencrypt-YYYYMMDD where YYYYMMDD is the issuing date (today).

Now, in your terminal, retrieve the public key:

cat /etc/letsencrypt/live/www.example.com/fullchain.pem

And paste the output into the public key box in the cloud console.

If you run into a permission error, try calling the command with sudo.

Now, in your terminal, retrieve the private key and convert to RSA PEM format:

openssl rsa -inform pem -in /etc/letsencrypt/live/www.example.com/privkey.pem -outform pem

And paste the output into the box in the cloud console.

Ditto on possibly needing sudo.

You should now be able to enable this certificate for all of your app's domains.