Howto: easy cert generation and renewal with nginx

After playing with the letsencrypt client, I found a nice way to automate certificates generation and renewal. I am using nginx here, but any webserver can work. This method does not involve any proxying to the letsencrypt command or automated nginx config modification, so it is quite safe.

Latest version will be available in this gist : https://gist.github.com/renchap/c093702f06df69ba5cac#file-readme-md


Prerequisites : the letsencrypt CLI tool

This method allows your to generate and renew your Lets Encrypt certificates with 1 command. This is easily automatable to renew each 60 days, as advised.

You need nginx to answer on port 80 on all the domains you want a certificate for. Then you need to serve the challenge used by letsencrypt on /.well-known/acme-challenge.
Then we invoke the letsencrypt command, telling the tool to write the challenge files in the directory we used as a root in the nginx configuration.

I redirect all HTTP requests on HTTPS, so my nginx config looks like :

server {
  listen              80;
  listen              [::]:80;
  server_name         example.net example.org;
  location '/.well-known/acme-challenge' {
  default_type "text/plain";
    root        /tmp/letsencrypt-auto;
  }

  location / {
    return              301 https://$server_name$request_uri;
  }
}

This approatch allows me do no longer needing to do any nginx config change if I add a new domain and use server_name *;, I create a new certificate with the needed hostname, and add the new vhost for this domain listening on 443 only using the newly generated certificate.

Then, to generate your initial certificate for those domains :

$ export DOMAINS="-d example.net -d example.org"
$ export DIR=/tmp/letsencrypt-auto
$ mkdir -p $DIR && letsencrypt certonly --server https://acme-v01.api.letsencrypt.org/directory -a webroot --webroot-path=$DIR --agree-dev-preview $DOMAINS
$ service nginx reload

The command will output the path to the signed certificate, and you can add it to your nginx configuration as usual. The private key is located in the same directory than the generated fullchain.pem

A Lets Encrypt cert is valid for 90 days, it is recommended to renew every 60 days. Automation is needed here to avoid any expired certificate !
To renew your certificate (in a cron job for example), call the same command with a --renew arg :

$ export DOMAINS="-d example.net -d example.org"
$ export DIR=/tmp/letsencrypt-auto
$ mkdir -p $DIR && letsencrypt --renew certonly --server https://acme-v01.api.letsencrypt.org/directory -a webroot --webroot-path=$DIR --agree-dev-preview $DOMAINS
$ service nginx reload

You can also get a duplicate certificate by using the same command again, with a --duplicate arg.

15 Likes

thanks for that, i also did something similar for webroot authentication as it's only way to get multi-domain SAN ssl certificates via webroot https://community.centminmod.com/posts/20018/

not too familiar with duplicate flag, what situations would require a duplicate certificate ? if using several local balanaced web servers served over LE ssl https ?

A duplicate allows you to use use a different private key, and be able to revoke this specific key if needed.
A common use-case is to provide a duplicate to an external party / server that handles one of your domains (sub-domains usually with a wildcard), or if you need to use your certificate in different hardware and want to be able to revoke only one of them if it is compromised.

1 Like

nice didn’t know you could do that with letsencrypt client, so you duplicate ssl certificate for CDN https custom SSL usage :slight_smile:

oh will duplicate rewrite that symlink to live domain ssl ? or it gets it’s own symlink separate files ?

Yes, this would be a valid use-case. So you can revoke your CDN cert if they get breached, and not the other certificates you use for this domain.

A duplicate creates a brand new certificate, named for example “example.net-0001” and not example.net

3 Likes

thanks @renchap for clarification :slight_smile:

Renaud, you mention that we need to have the cli tool installed. Is this what you get when you clone into the GitHub repo?

letsencrypt-auto??

I am using FreeBSD, so I only needed to run pkg install py27-letsencrypt

Here is an (untested by me) repository with install scripts for various systems : https://github.com/kennwhite/install-letsencrypt

1 Like

Thank you for all the help Renaud, I did end up being successful using the standalone installation. I prefer this for the time being until it’s automated. I did post details on the my original post, if it can help others. Once again thank you!

to make this a complete guide I would like to add how you could setup your https server as well:

server {
        listen 443 ssl;

        root /var/www/yourpath;
        index index.php index.html index.htm;

        # Make site accessible from http://localhost/
        server_name yourdomain.com www.yourdomain.com;

        # if you are using hhvm, otherwhise include your standard php config or whatever
        include hhvm.conf;

        location / {
             try_files $uri $uri/ /index.php$is_args$args;
        }

        ssl on;

        ssl_certificate /etc/letsencrypt/live/yourdomain.com/cert.pem;
        ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;

        ssl_stapling on;
        ssl_stapling_verify on;
        ssl_trusted_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;

        ssl_session_timeout 5m;
}
1 Like

see also https://github.com/letsencrypt/letsencrypt/issues/1566 to fully automate command

1 Like

Did someone post a complete guide on how to set up the server? I do not know how or where to do this. I’m using AWS elastic beanstalk.

I found a complete guide on Setting up a WordPress site on AWS which uses nginx on an EC2. The SSL is through sslmate but it would be great if someone forked the Gist to work with LetsEncrypt. I’d buy that person a beer for sure.

Nice one!

On top of this, I’ve added to my crontab this:

* 3 2 1-12 * bash -l -c "mkdir -p /tmp/letsencrypt-auto && /root/letsencrypt/letsencrypt-auto certonly -d MAINDOMAIN -d SUBDOMAIN-1 -d SUBDOMAIN-2 --agree-tos --renew-by-default -a webroot --webroot-path=\"/tmp/letsencrypt-auto\" && service nginx reload"

So it renews every month, second day of each month, two o’clock :smiley:

I recommend looking at this script.

Combine with the following crontab entry:

30 2 * * 1 /usr/local/sbin/le-renew-webroot >> /var/log/le-renewal.log

Resource: DigitalOcean Let’s Encrypt article

I have a letsencrypt certificate for nginx set for mydomain.com and www.mydomain.com and I would like to use that certificate for static.mydomain.com. When renewing, is it possible to add “static” subdomain that has not been specified when the certificate was first set up. If not, then how to proceed? Thanks.

@iyedb, did you use webroot authentication to obtain the certificate?

If you run certbot certonly --force-renew -d mydomain.com -d www.mydomain.com -d static.mydomain.com, it should perform a renewal replacing the existing cert with a new one that includes coverage for static.mydomain.com as well (although I’m not sure whether you need to explicitly specify the webroot for the new domain with -w).

@schoen Thank you. The command I used was: ./letsencrypt-auto certonly -a webroot --webroot-path=/usr/share/nginx/html -d example.com -d www.example.com.
So this:
certbot certonly --force-renew --webroot -w /var/www/example -d example.com -d www.example.com -w /var/www/static -d static.example.com should do it I guess. Fantastic!

For future reference, this works. renew a certificate adding a new subdomain with a different webroot.

I’m glad it worked properly for you, and thanks for documenting your experience here.