{"id":1556,"date":"2015-11-09T11:00:15","date_gmt":"2015-11-09T10:00:15","guid":{"rendered":"https:\/\/www.bytopia.dk\/blog\/?p=1556"},"modified":"2017-10-13T09:06:49","modified_gmt":"2017-10-13T07:06:49","slug":"automatic-renewal-of-lets-encrypt-issued-certificates","status":"publish","type":"post","link":"https:\/\/www.bytopia.dk\/blog\/2015\/11\/09\/automatic-renewal-of-lets-encrypt-issued-certificates\/","title":{"rendered":"Automatic renewal of Let&#8217;s Encrypt issued certificates"},"content":{"rendered":"<p><a href=\"https:\/\/letsencrypt.org\/\">Let&#8217;s Encrypt<\/a> is a new Certificate Authority that is automated and free to use. This makes it possible to obtain domain validated (DV) certificates for your domain names as no cost!<\/p>\n<p>I registered for the closed beta, and got a few of my domain names authorised. This meant that I could now obtain certificates for them. The process was fairly simple. I chose to use the webroot authentication method, which means that the Let&#8217;s Encrypt client should be able to place files under my webroot to prove that I control the domain.<\/p>\n<p>Since my webroot is \/var\/www I created the \/var\/www\/acme\/.well-known\/acme-challenge folders for the webroot plugin to use. My web server is Nginx so I placed the following code in the server blocks for the domains I want to obtain certificates for and reloaded the Nginx configuration.<\/p>\n<pre><code class=\"nginx\">location ~ ^\/\\.well-known\/acme-challenge {\r\n  root \/var\/www\/acme;\r\n  default_type application\/jose+json;\r\n}\r\n<\/code><\/pre>\n<p>Now it was time to install and run the Let&#8217;s Encrypt client to obtain the certificates. When prompted I used \/var\/www\/acme as the webroot path.<\/p>\n<pre><code class=\"bash\">git clone https:\/\/github.com\/letsencrypt\/letsencrypt\r\ncd letsencrypt\r\n.\/letsencrypt-auto --agree-dev-preview --server https:\/\/acme-v01.api.letsencrypt.org\/directory certonly\r\n<\/code><\/pre>\n<p>So far so good. I now had my certificates under \/etc\/letsencrypt\/live and could configure Nginx to use them. Great! For those who are curious this is my SSL configuration for Nginx:<\/p>\n<pre><code class=\"nginx\">ssl_certificate \/etc\/letsencrypt\/live\/example.com\/fullchain.pem;\r\nssl_certificate_key \/etc\/letsencrypt\/live\/example.com\/privkey.pem;\r\nssl_session_cache shared:SSL:10m;\r\nssl_session_timeout 5m;\r\nssl_protocols TLSv1 TLSv1.1 TLSv1.2;\r\nssl_prefer_server_ciphers on;\r\nssl_ciphers \"ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-ECDSA-AES256-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA\";\r\nssl_ecdh_curve secp384r1;\r\nssl_dhparam \/etc\/ssl\/dhparams.pem;\r\nadd_header Strict-Transport-Security max-age=31536000;\r\n<\/code><\/pre>\n<p>The certificates are valid for 90 days, so they need to be renewed fairly often. I wanted to automate this process, so I wrote the following script. Basically it checks all certificates under \/etc\/letsencrypt\/live and if one is about to expire in less than 4 weeks it is automatically  renewed. I named this script <strong>certrenew<\/strong>.<\/p>\n<pre><code class=\"bash\">#!\/bin\/bash\r\n\r\nif [ ! -d \/etc\/letsencrypt\/live ]; then\r\n  exit 1\r\nfi\r\n\r\nfunction issueCert {\r\n  \/root\/.local\/share\/letsencrypt\/bin\/letsencrypt certonly --renew-by-default --agree-dev-preview --server https:\/\/acme-v01.api.letsencrypt.org\/directory --authenticator webroot --webroot-path \/var\/www\/acme $1\r\n}\r\n\r\nexitcode=1\r\nwhile IFS= read -r -d '' cert; do\r\n  if ! openssl x509 -noout -checkend $((4*7*86400)) -in \"${cert}\"; then\r\n    subject=\"$(openssl x509 -noout -subject -in \"${cert}\" | grep -o -E 'CN=[^ ,]+' | tr -d 'CN=')\"\r\n    subjectaltnames=\"$(openssl x509 -noout -text -in \"${cert}\" | sed -n '\/X509v3 Subject Alternative Name\/{n;p}' | sed 's\/\\s\/\/g' | tr -d 'DNS:' | sed 's\/,\/ \/g')\"\r\n    domains=\"-d ${subject}\"\r\n    for name in ${subjectaltnames}; do\r\n      if [ \"${name}\" != \"${subject}\" ]; then\r\n        domains=\"${domains} -d ${name}\"\r\n      fi\r\n    done\r\n    issueCert \"${domains}\"\r\n    exitcode=0\r\n  fi\r\ndone < <(find \/etc\/letsencrypt\/live -name cert.pem -print0)\r\n\r\nexit ${exitcode}\r\n<\/code><\/pre>\n<p>The script <a href=\"https:\/\/www.bytopia.dk\/static\/certrenew\">can be downloaded here<\/a>.<\/p>\n<p>To automate everything I run the following script via cron every day. It calls the certrenew script and if certificates are renewed it reloads the Nginx configuration after the renewal process. This will produce output so you will get an e-mail from the cron daemon when certificates are renewed. If you don't what that then just send the output of certrenew to \/dev\/null.<\/p>\n<pre><code class=\"bash\">#!\/bin\/bash\r\n\r\nif \/usr\/local\/bin\/certrenew; then\r\n  \/usr\/sbin\/nginx -t && systemctl reload nginx\r\nfi\r\n\r\nexit 0\r\n<\/code><\/pre>\n<p>That's it. Fully automated renewal of Let's Encrypt certificates \ud83d\ude42<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Let&#8217;s Encrypt is a new Certificate Authority that is automated and free to use. This makes it possible to obtain domain validated (DV) certificates for your domain names as no cost! I registered for the closed beta, and got a few of my domain names authorised. This meant that I could now obtain certificates for [&hellip;]<\/p>\n","protected":false},"author":3,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"ngg_post_thumbnail":0,"footnotes":""},"categories":[4,20],"tags":[],"class_list":["post-1556","post","type-post","status-publish","format-standard","hentry","category-computer","category-english"],"_links":{"self":[{"href":"https:\/\/www.bytopia.dk\/blog\/wp-json\/wp\/v2\/posts\/1556","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.bytopia.dk\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.bytopia.dk\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.bytopia.dk\/blog\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/www.bytopia.dk\/blog\/wp-json\/wp\/v2\/comments?post=1556"}],"version-history":[{"count":20,"href":"https:\/\/www.bytopia.dk\/blog\/wp-json\/wp\/v2\/posts\/1556\/revisions"}],"predecessor-version":[{"id":1580,"href":"https:\/\/www.bytopia.dk\/blog\/wp-json\/wp\/v2\/posts\/1556\/revisions\/1580"}],"wp:attachment":[{"href":"https:\/\/www.bytopia.dk\/blog\/wp-json\/wp\/v2\/media?parent=1556"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.bytopia.dk\/blog\/wp-json\/wp\/v2\/categories?post=1556"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.bytopia.dk\/blog\/wp-json\/wp\/v2\/tags?post=1556"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}