How to fully automate renewing of Let’s Encrypt certificates for multiple sites with Ruby and Let’s Encrypt ACMEv2 protocol
Let’s Encrypt has announced in the beginning of year, that it will no longer support ACMEv1 protocol for certificate renewal after June 1. 2020. At my work I run 4 Rails application instances with 12 web sites (domains) and have been using Let’s Encrypt certificates for years. They are automatically renewed every month. All I get is a mail indicating that certificates have been renewed.
There are enough differences from ACMEv1 to ACMEv2 protocol, that I had to go back to school and rewrite the whole procedure. It took me few hours to figure it all out and maybe it will save some time for you.
Requirements
All examples have been tested and they run on Ubuntu Linux. Since Ruby runs the same on all platforms, there shouldn't be any problem running it on other platforms.
Ruby interpreter must be installed. If not already installed it can be installed by entering this command in console:
acme-client gem is also required. Install it by entering this command in console:
Register with Let’s Encrypt certification authority
First we have to register with Let’s Encrypt. We do this by generating private key and registering key with Let’s Encrypt. We need to register only once. Save code below to register.rb and run it with
require 'acme-client'
require 'openssl'
client_key = OpenSSL::PKey::RSA.new(4096)
client = Acme::Client.new(private_key: client_key, directory: 'https://acme-v02.api.letsencrypt.org/directory')
account = client.new_account(contact: 'mailto:mail@drgcms.org', terms_of_service_agreed: true)
File.write("lets-encrypt.key", client_key.to_pem )
At the end of the procedure, key used to register account, is saved to file. It will be used for future conversations with certification authority.
Renew certificate
Below is the code for certificate renewal. Save it to renew_certificate.rb and run it with:
require 'acme-client'
require 'openssl'
apps = { 'drgcms' => { dir: '/path_to/drgcms', domains: %w[www.drgcms.org tulips.drgcms.org] } } client_key = OpenSSL::PKey::RSA.new( File.read('lets-encrypt.key') ) client = Acme::Client.new(private_key: client_key, directory: 'https://acme-v02.api.letsencrypt.org/directory') apps.each do| app, domains | p '',"Renewing APP: #{app}" order = client.new_order(identifiers: domains[:domains]) order.authorizations.each do |authorization| p ['validating', authorization.domain] challenge = authorization.http # write challange data to challenge.filename FileUtils.mkdir_p( File.join( domains[:dir], 'public', File.dirname( challenge.filename ) ) ) File.write( File.join( domains[:dir], 'public', challenge.filename), challenge.file_content ) # validate single domain challenge.request_validation while challenge.status == 'pending' p ['challenge.status', challenge.status] sleep(2) challenge.reload end p challenge.status # => 'valid' end # get certificate private_key = OpenSSL::PKey::RSA.new(4096) csr = Acme::Client::CertificateRequest.new(private_key: private_key, names: domains[:domains]) order.finalize(csr: csr) while order.status == 'processing' sleep(1) challenge.reload end # save certificate and private key if (certificate = order.certificate) File.write("#{app}-pkey.pem", private_key.to_pem ) File.write("#{app}-cert.pem", certificate) else send_mail "Error renewing Lets Encrypt certificates for application #{app}" end end # move certificates and restart web service system "mv *.pem /etc/ssl/nginx" system 'service nginx restart' send_mail 'Lets Encrypt certificates renewed succesfully'
First we have apps variable, which defines single application name (drgcms) and holds two options. Path to application root (:dir) and domains list for the application (:domains).
After that, we introduce program to service and get client variable for communicating with service.
Now we make authorization loop. We provide certificate for each application by placing a certificate order. On every order each domain must be authorized. Authorization can be done either by dns or http. Our example uses http authorization.
Http authorization validation is done by putting challenge filename with challenge data into application’s public directory. Certificate authority checks, if challenge filename exists and it contains provided data.
When all domains are validated, we create new private key and make certificate request. Again when everything is OK, certificate and private key are saved to file system.
At the end, if everything is OK, I move all certificate files to /etc/ssl/nginx directory, restart nginx service and send information mail, that certificates have been renewed.
That’s it. Don’t forget that if you are still using ACMEv1 protocol your certificate renewal procedure will fail after june 1.