Modern Internet is full of encryption. In many ways, using encryption is still optional, although non-encrypted communication of any form is getting rarer every day. There are factors that contribute to this trend. As a specific example, some top-level domains, like .app or .dev, the mainstream browsers ship with the forced HTTPS mode hard-coded for these selected TLDs.

To make HTTPS work, a browser and a webserver first need to perform a key exchange. This exchange is referred to as a TLS handshake. After a successful handshake, the browser and the webserver can communicate securely, meaning anyone eavesdropping on the communication can only see garbage, unless they can actually decrypt the communication.

For a webserver to be able to perform the TLS handshake, it needs a certificate, which is used for a public key encryption. The certificate was traditionally bought from a Certificate Authority (CA), until the non-profit CA called LetsEncrypt started providing certificates for free, so we all can have nice things (secure communication with the webserver).

ACME and Certbot

ACME stands for Automated Certificate Management Environment and provides a protocol enabling any webserver sitting under an actual domain name to obtain the certificate from LetsEncrypt at no cost.

The official client implementing the ACME protocol is called Certbot and is written in Python. It's a powerful client, but it has it's share of issues as well. Because it is a sort of a swiss-knife, it tries to handle many tasks. By it's nature, it is a little bit heavy on the dependencies. I specifically do not like it adds lines into Nginx configuration files by default. Another problem I had was on Ubuntu machine. When 20.04 came out, the repositories was slower to catch up and I had to do manual patches of the certbot's code, which is not a pleasant experience. This is also the reason I am experimenting with Arch as a server.

Certbot is not the only available client speaking the ACME protocol. Heck, the ACME protocol is available as RFC8555 and anyone can even obtain the certificate from LetsEncrypt manually by following it. There are also many 3rd party clients that automate the process available already.

Enter acme.sh

One of such clients is called acme.sh an as it's name suggest is a Shell script with (almost) no dependencies. This fact alleviates the problem of slow repository update almost entirely, because one can always just use git to obtain the latest version, regardless of where the host operating system repositories do. Acme.sh page cites:

It's probably the easiest & smartest shell script to automatically issue & renew the free certificates from Let's Encrypt.

Let's see if this statement holds onto it's message.

Setup

Start by setting-up the DNS record type A or CNAME for sub.example.com pointing to the public IP address of the host where these steps are going to be applied. DNS records can be set any time, but it can take time till nameservers to propagate the changes, so it is better to do it first.

This guide assumes becoming a superuser:

su -

Install acme.sh and Nginx, or alternatively nginx-mainline:

pacman -S --needed acme.sh nginx

Make sure there is nothing listening on port 443 used for HTTPS:

ss -tuna | grep :443

If there is something running there already, stop it.

Issue the certificate

The next step makes use of the Application-Layer Protocol Negotiation (ALPN), which is the initial part of the TLS handshake mentioned above. Acme.sh is capable of issuing a certificate using ALPN mode. The certificates are installed into /root/.acme.sh/sub.example.com/:

acme.sh --issue --alpn -d sub.example.com

Now pick or choose a location where you put your certificates, examples:

  • /etc/ssl/certs is used by OpenSSL
  • /etc/letsencrypt/live is used by Certbot
  • /etc/nginx/ssl preferred by some users

If the certs are being used solely by Nginx, the /etc/nginx/ssl is a good choice:

mkdir /etc/nginx/ssl

It is important to set the right permissions for this folder to protect the private key:

chmod 700 /etc/nginx/ssl

The folder has to be owned by the root user.

Configure Nginx

Now copy the generated certificates there, pay attention to reloadcmd:

acme.sh --install-cert -d sub.example.com  \
  --key-file '/etc/nginx/ssl/sub.example.com.key' \
  --fullchain-file '/etc/nginx/ssl/sub.example.com.cer' \
  --reloadcmd "systemctl force-reload nginx"

Important: make sure to check the permissions. The /etc/nginx/ssl folder should have 700, .cer files should have 644 and .key file should have 600. Everything should be owned by root. Note that the ownership and permissions are preserved automatically when the certificates are renewed.

find /etc/nginx/ssl -printf "%m %f\n"
700 ssl
600 sub.example.com.key
644 sub.example.com.cer

Add the relevant data under the server block in the Nginx config. Not all configuration directives are offered in the example below, just the most relevant ones. Consider consulting the Nginx documentation on HTTPS.

server {
    listen 443 ssl;
    server_name sub.example.com;
    ssl_certificate     /etc/nginx/ssl/sub.example.com.cer;
    ssl_certificate_key /etc/nginx/ssl/sub.example.com.key;
    ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers         HIGH:!aNULL:!MD5;
}

Lastly, start and enable Nginx service:

systemctl enable nginx.service --now

Now access the page via the browser to check if HTTPS is working.

Note: when HTTPS served via Nginx works, consider switching to obtaining the certificate via Nginx mode, because certificate renewal via ALPN will not work anymore as Nginx is already listening on the port 433. The reason it was used in the first place is that it does not require any dependencies as opposed to standalone mode requiring socat). The second reason is that the Nginx validation mode may require a working Nginx configuration in the first place, so it is better to start safe.

acme.sh --issue --nginx -d sub.example.com

This is a 41th post of #100daystooffload.