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 websites (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 a 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 the console:
acme-client Ruby gem is also required. Install it by entering this command in the console:
Register with Let’s Encrypt certification authority
First, we have to register with Let’s Encrypt. We do this by generating a private key and registering the 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, the key used to register the 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 a 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 the 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.