Tightening Security on GitLab Pages

How to enable HTTPS for secure connections to your Jekyll blog hosted on GitLab Pages with a custom domain, using a free TLS certificate from Let’s Encrypt.

HTTPS is important, even for static sites. I assume you’re already on board with that.1

Naturally, this blog should also use HTTPS then.

GitLab Pages automatically uses HTTPS if you are running your page on their domains (i.e. <username>.gitlab.io). But if you’re using a custom domain, you have to bring your own certificate to make connections secure.

While GitLab has some help articles on this, getting my own TLS certificate set up with my page was not as easy as I would have liked. So I collected my steps in this article, hoping it will be useful to the next person trying the same thing.

The information in this article is based mainly on GitLab’s docs and the Tutorial linked from there, as well as the documentation of Let’s Encrypt and certbot.

What You Need Before Starting

If you want to follow these steps, I’ll assume you have the following set up and working:

  • a GitLab Pages site using Jekyll, which is built with GitLab’s CI,
  • a custom domain (can be a top domain or a subdomain), already set up with your GitLab Pages site, and
  • a local computer (Mac or Linux) on which we will create the certificate.

Note: I’m using a Mac for this, but other than the installation method for certbot, Linux should work just the same.

What We Are Doing

Our goal is to set up a custom TLS certificate for the GitLab Pages site. We are going to get such a certificate from Let’s Encrypt, a services that provides free TLS certificates with automated provisioning.

Let’s Encrypt provides a command-line tool called certbot, with which you can request new certificates and renew existing ones.

certbot is usually intended to be run on the machine that also hosts the web server, but GitLab Pages provides no shell acces, so that’s not an option for us. Luckily, certbot also has a manual mode to manage certificates for a different machine, which is what we’re going to do.

Let’s Get Securin’

  • First of all, we’re going to need certbot.

    • If you don’t already have it, download and install it.
    • For macOS, Homebrew makes this as easy as running brew install certbot in a terminal.
  • Next, create a folder to work in. You can name it whatever you like; we’re going to refer to it as <cert>. Inside this folder, create three folders: config, logs, and work-dir.

    • You’re going to need to keep the contents of this <cert>, so don’t put it in /tmp or the like.
    • Other than that, it doesn’t really matter where you put <cert>. Inside your user folder works, for example.
    • Why do we do this? Since certbot is intended to be used on the web server machine, it keeps its data in /etc/letsencrypt by default (see its docs). Writing to this folder requires superuser privileges, which we don’t really need for our purposes. Making certbot work in a folder that we own eliminates this need.
  • Side Note: Please remember to use Let’s Encrypt’s staging environment to try things, so you don’t accidentally hit their rate limits.

  • Open a terminal in <cert>.

  • Run certbot certonly --work-dir=work-dir/ --logs-dir=logs/ --config-dir=config/ -a manual -d <custom_domain> --staging, replacing <custom_domain> with the actual domain you want to use.

    • Note: There’s no need to create an account with Let’s Encrypt beforehand. certbot prompts you to enter your email address and creates an account on the fly. The credentials for this will be stored on the config folder we gave it, which you should back up afterwards (I’ll remind you later).
  • certbot runs interactively and will prompt you with a few questions. Read and answer them until it tells you to create a file:

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Create a file containing just this data:
    And make it available on your web server at this URL:
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Press Enter to Continue
  • Leave this terminal window open and waiting for the moment.

  • In the local clone of your Jekyll page, create a plain file with the following contents based on certbots’s instructions:

    layout: null
    permalink: /.well-known/acme-challenge/-X12Aa37zsmEoAavGa-biE5rtiw9n64A_DOmngOGSFw/index.html
    • You can call the file whatever you want, since the output filename is determined by the permalink we specify. I used lets-encrypt-token.html, for example.
    • Specifying the layout “null” in the YAML frontmatter tells Jekyll not to generate anything around the contents of this file. Instead, it just writes a plain text file with the given content at the given permalink URL.
    • In contrast to Gitlab’s tutorial, we need to use a permalink URL like /.well-known/acme-challenge/<challenge>/index.html instead of /.well-known/acme-challenge/<challenge>.html.
    • Why? GitLab Pages does not serve the contents of HTML (or text) files at URLs without file extension (at least not anymore), but certbot does not use a file extension when fetching the URL.
    • Luckily, certbot does follow HTTP redirects, so we can use this little trick of making a folder <challenge> with an index.html instead of a file called <challenge>.html.
  • If you want, you can compile your Jekyll site locally and test if the file shows up.

  • Commit the change and push, then wait for the GitLab CI build to finish.

    • Note: Sometimes it may take a few minutes for changes to be visible.
    • If the challenge token is not available after several minutes, pause at this point and double-check your build and deployment setup, and fix it if necessary.
  • Once you manually confirmed that the challenge token is available at the URL that certbot expects, return to the certbot terminal window and press Enter.

  • Success!

    • This should be it — you should get a success message from certbot.
    • certbot tells you where you can find the certificate and private key files.
    • It also tells you to make regular backups of its config folder, because that contains both the certificate files and your Let’s Encrypt account credentials! Now is a good time to do just that.
  • Unfortunately, the certificate we just created is based on Let’s Encrypt’s staging system, so it’s not actually trusted and therefore cannot be used to test it on GitLab Pages.

  • Therefore, re-generate the certificate with the same steps, but without the --staging switch in certbot’s invocation.

    • Note: If you followed these instructions and have successfully created a staging certificate (and used the same domain for it), certbot asks if you really want to replace this certificate (because it’s not expiring yet). Since the staging certificate is of no further use, you can confirm to replace it.
  • Once that is done, you can now add the certificate to your GitLab Pages site as described in the docs.

    • In the “Certificate (PEM)” field, place the contents of the fullchain.pem file that certbot generated.
    • In the “Key (PEM)” field, place the contents of the privkey.pem file that certbot generated.
  • Note: You may have to trigger another GitLab CI deployment (and wait a little while) to make these changes take effect.

Well done! Connections to your blog are now secure(r)!

What’s next

As you may have read, Let’s Encrypt certificates expire after 90 days. This means that we’re going to have to renew them regularly.

Being the lazy programmer that I am, I don’t want to do the above dance every 2-3 months when this comes up. So I’m likely going to automate this at some point.2

If and when that happens, I might just write another article about it! So… stay tuned, I guess? 😄

Update: I have since published a post on automating renewal of Let’s Encrypt certificates with GitLab CI!


  1. If you want to know more, I recommend the section Why should I care about HTTPS? of GitLab’s documentation and the Electronic Frontier Foundation’s article Encrypting the Web).
  2. There are a one or two tools that purport to automate generation and renewal of Let’s Encrypt certificates for GitLab Pages sites, but none of them looked both simple enough and trustworthy enough to me to jump right on them. Most likely, I’m going to use them as inspiration and set up my own tooling.

Content © Copyright 2021. Patrick Lehner. All Rights Reserved.
Design based on Chalk by Nielsen Ramon.