Managing SSL certificates for Nginx web server for staging/testing environment

Issuing certificates

Most straightforward and easy way to issue a certificate for a Nginx server is with certbot. Apart from native support for Nginx and Apache, you can pretty much use it with most popular unix-based OSes like Debian or Ubuntu.

Issuing a certificate is as easy as

certbot certonly -d '${BRANCH}.staging.com'
-d 'subdomain1.${BRANCH}.staging.com'
-d 'subdomain2.${BRANCH}.staging.com'
--webroot --webroot-path /var/www/subdomains/$BRANCH/public
--non-interactive;

This will issue a single certificate with the shortest name ('${branch}.staging.com' for given example) for all specified domains (-d $domain).

Firstly, you want to include all subdomains into one certificate. This is magnitudes faster than issuing separate certificates and creates one file, which is easily manageable in config files and while working with certbot.

Secondly, since there are going to be a lot of different deployments on one server, you probably want a one-time setup, so we don’t allow certbot to tweak or create new configuration files by passing certonly option.

Thirdly, we pass two webroot options because you probably do not want to stop webserver for each deployment and content that you serve can be changed (think updating merge request and redeploying branch).

Lastly, we pass --non-interactive option so certbot won’t ask for any prompts.

Configuring nginx .conf file for webroot plugin

With webroot plugin certbot performs HTTP challenge for files placed in $webroot/.well-known/acme-challenge/, so we need a separate piece of code to allow thi (you’re reading about certificates that means you want to serve content via https that means you want to deny http requests right?).

server {
    listen 80;
    server_name staging.com ~^(subdomain1.|subdomain2.|)((?<branch>.+).|)staging.com$;

    set $dir "test";
    if ($branch != "") {
        set $dir branches/${branch};
    }
 
    location /.well-known/ {
        autoindex on;
        alias /var/www/$dir/public/.well-known/;
    }

    location / {
        return 403;
    }
}

This will serve content from .well-known folder via HTTP, and return 403 for any other locations.

To use issued certificate, you can just copy configuration from nginx plugin into your configuration file. Make sure to properly setup dynamic certificate names for different domains.

server {
    #...
    # ssl
    ssl_certificate /etc/letsencrypt/live/$certname/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/$certname/privkey.pem;

    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
    ssl_ecdh_curve secp384r1;

    ssl_stapling on;
    ssl_stapling_verify on;
    #...
}

Testing configuration

Before issuing a real certificates, you want to perform dry runs. letsencrypt actually have a rate limit, about which personally I didn’t know before I ran into this issue. Just pass a --staging or --test-cert flags to test your configuration.

certbot certonly -d 'staging.com' -d 'subdomain1.staging.com' -d 'subdomain2.staging.com' --webroot --webroot-path /var/www/test/public --staging --non-interactive;

Don’t forget to revoke and delete issued certificate after success (notice how we still pass --staging flag).

certbot revoke --cert-name staging.com --delete-after-revoke --staging --non-interactive;

Renewing certificates

As with issuing certificates, you should dry run renewal process.

certbot renew --cert-name "${BRANCH}.staging.com" --dry-run --non-interactive;

If this succeeds, then your certificate is valid and you can successfully use it for HTTPS connections. Run following commands to add a cronjob.

SLEEPTIME=$(awk 'BEGIN{srand(); print int(rand()*(3600+1))}'); echo "0 0,12 * * * root sleep $SLEEPTIME && certbot renew -q" | sudo tee -a /etc/crontab > /dev/null

Personally I don’t do this since certificate’s lifetime is much longer than what lifetime of a branch deployment is.

Deleting certificates

As outlined in testing section, you can delete your certificates with the following script (don’t forget to revoke it!).

certbot revoke --cert-name $BRANCH.staging.com --delete-after-revoke --non-interactive;

Now you can easily manage certificates in your CI/CD pipelines (refer to this code snippet). Please, be careful and don’t omit dry runs and tests of your configuration before shipping to production. You can also read official user guide of certbot.

$ cd ..