“What’s wrong with Certbot’s builtin NGINX plugin?”
The NGINX plugin for Certbot is not ideal for many reasons:
- NGINX config files must be accessed modified by Certbot.
- Unnecessary and redundant directives are added to config files, clutter, poor formatting, etc. It also modifies existing directives.
- The plugin is not included by default on many operating systems such as OpenBSD.
- No good solution for managing multiple certificates.
Using the plugin this way also generally contributes to a lack of understanding on the user’s end. In regards to how Certbot functions as well as how to configure SSL in NGINX.
Many of these criticisms also apply to the other Certbot plugins; they all over-complicate and obscure the actual mechanisms of certificate validation/retrieval.
Certbot also, by default, runs in an interactive mode which makes it a nightmare for those who would rather automate the process. This solution addresses that problem as well, although you could run it in unattended mode with a few flags using either method.
The correct method – Saying no to plugins
Certbot’s certonly mode is the solution. It allows for creating certificates manually without accessing your NGINX configuration.
To use it with NGINX, you will need to do the following:
- Setup a directory for and configure NGINX to serve Certbot’s
acme-challenge
files on port 80. Certbot must have read/write access to this directory, while NGINX only needs read access. - Configure NGINX to serve the Let’s Encrypt certificates generated by certbot.
- Run
certbot certonly
to obtain certificates, and renew withcertbot renew
.
Tutorial
1. Completing ACME challenges
1.1. Background
ACME (Automatic Certificate Management Environment) is the protocol that Let’s Encrypt uses for issuing, managing, and renewing certificates. Basically, it allows them to verify that you own whatever domain you’re trying to get a certificate for. A challenge must be successfully completed before obtaining or renewing a certificate for each domain name (or subdomain) on the certificate. Certbot will run the ACME client automatically, and more or less manage the whole process.
There are several different ACME authentication methods. By default Certbot uses the HTTP-01 challenge, which I will also demonstrate as it is the most straightforward method. You can read more the different challenge types here: Let’s Encrypt Challenge Types.
You can use the DNS-01 challenge if you need a wildcard certificate (eg. *.example.com), but the renewal process more complicated. I may write about and link to it in the future if I ever decide to self-host a name server.
The HTTP-01 method simply involves serving a temporary token file at a specific route on your server.
This route will look something like http://<YOUR_DOMAIN>/.well-known/acme-challenge/<TOKEN>
.
The route to this token is obfuscated by default.
A token will be generated for each domain name on the certificate.
The ACME challenge is completed once the ACME server (Let’s Encrypt) is able to verify the all tokens, and Certbot will delete the token files.
1.2. Basic Procedure in NGINX
Create a directory for the ACME tokens (called the ‘webroot’).
/var/www/certs
is what Certbot uses by default.
Then serve this directory on port 80
in your NGINX config.
You can serve the ACME tokens using this directive on all of your web servers:
|
|
To simplify this, save that directive as acme-challenge
in your /etc/nginx
, and include it like this:
|
|
This is how I configure my ACME challenges in NGINX.
All other routes only listen on 443
, so it is simpler to handle the ACME challenges on default_server
block and try to redirect all other http
requests to https
.
Although, you could also include acme-challenge
in all of your server
blocks that require a certificate (or even in your ssl-params
file, created in the next step) if you wanted.
2. Configuring SSL in NGINX
2.1. NGINX’s Certificate Catch 22
Before configuring SSL in NGINX, certificates must be obtained.
Without a certificate, NGINX will disallow the use of port 443
entirely.
Even worse, if an invalid certificate or certificate path is configured, NGINX will crash outright reporting an ‘invalid configuration’.
This leaves two options:
- Obtain valid certificates before configuring SSL in NGINX.
- First create fake (self-signed) certificates to make NGINX happy, then obtain the real certificates.
Option A is simpler, but option B has the benefit of allowing you to obtain new certificates without modifying your NGINX configuration. That may seem trivial until you want to create a setup that is deployed with code eg. docker compose or Ansible.
Since I want to encourage you to use option B, I will proceed with configuring NGINX prior to obtaining the certificates.
2.2. NGINX SSL Parameters
Create a new file in /etc/nginx
called ssl-params
containing:
|
|
ssl_certificate
and ssl_certificate_key
will be the path to your fullchain.pem
and privkey.pem
respectively.
include ssl-params
can then be used in all your secured server blocks as such:
|
|
2.3. Create Self-Signed Certificates
As mentioned above, NGINX will get very upset with you if either your ssl_certificate
or ssl_certificate_key
is invalid or does not exist.
To remedy this, you can use the following script to create a self-signed certificate keypair to be replaced by the valid keys once they are obtained:
|
|
NGINX will happily serve these, so now you can proceed to obtaining the Let’s Encrypt certificates.
3. Obtain The Certificates – For Real This Time
3.1. Initial Obtain
Start your NGINX server and ensure that you can access each domain properly.
Then, run the following certbot certonly
command:
(I put it in script form so that you can save it)
|
|
This will create your certificates in an unattended mode, and overwrite the self-signed certificates with the new ones.
NGINX should automatically start serving the new certificates, but you may need to restart NGINX if the old certificates were cached.
3.2. Renewing Existing Certificates
Create the following as a cronjob:
0 0 * * * certbot renew
This will renew your existing certificate every 24 hours, ensuring that it will not expire.
Certbot will only replace your certificate when the renew
command is ran within a two week or so window of expiration.
3.3. Expanding Existing Certificates
Existing certificates can be expanded to include more domains if desired.
However, it is often easier to simply overwrite the old certificate with the new one using the certbot certonly
command you used to create your certificates initially.