Let's Encrypt Is in Public Beta: Free Certificates for Anyone Who Automates
Last Thursday, December 3, Let’s Encrypt entered Public Beta. You no longer need an invitation to get a free, browser trusted certificate. If you run a small site and have been putting off HTTPS because certificates cost money, expire at the worst moment, or involve a confusing dance with a CSR and a validation email, this is the week to look again.
Who is behind it
Let’s Encrypt is run by the Internet Security Research Group (ISRG), a California public benefit corporation recognized as a 501(c)(3) tax exempt organization. Its stated mission is to reduce the financial, technological and educational barriers to secure communication on the Internet. The project was announced in November 2014 with Mozilla, Cisco, Akamai, the Electronic Frontier Foundation, IdenTrust and researchers at the University of Michigan working through ISRG. Since then the Internet Society signed on as a Gold sponsor in October, and on the same day as the beta launch ISRG announced that Facebook is a Gold sponsor too. The Linux Foundation handles administrative work like fundraising, finance and HR, which lets ISRG’s own staff stay focused on its mission.
The timeline has moved quickly. The Limited Beta started on September 12, ISRG announced its first certificate two days later, and in October IdenTrust cross signed Let’s Encrypt’s intermediate certificates. That cross signature is why these certificates already work in all major browsers, even though ISRG only applied to the browser root programs in September. According to the beta announcement, the Limited Beta issued over 26,000 certificates.
The account is a key pair
The part that makes Let’s Encrypt different is not the price, it’s the protocol. Issuance runs over ACME, which is being standardized in the IETF. The current working group draft is draft-ietf-acme-acme-01, dated October 4, and it’s surprisingly readable.
In ACME there are no usernames or passwords. Your account is an account key pair. The client generates it the first time you run it, registers it with the CA along with contact information, and signs every request after that with the private key. The CA identifies you purely by the public key.
Once you have an account, the workflow looks a lot like a traditional CA where you add domains to an account. The difference is how you prove a domain is yours. You ask the CA for an authorization for example.com, and it answers with a set of challenges, which are different ways to show you control that name. Among the challenges the draft defines are provisioning an HTTP resource, configuring a TLS server using SNI, and adding a DNS record. When one checks out, your account key becomes authorized for that domain, and from then on a certificate request is just a CSR sent inside a message signed by that key.
How http-01 proves you control the domain
The http-01 challenge is the easiest one to picture. The CA sends a random token with at least 128 bits of entropy. The client builds what the draft calls a key authorization: the token, a dot, and a base64 encoded SHA-256 thumbprint of the account’s public key. It writes that string to a file on the site at /.well-known/acme-challenge/ followed by the token, then tells the CA it’s ready by sending the same key authorization in a signed message.
The CA then fetches http://example.com/.well-known/acme-challenge/<token> over plain HTTP. The draft says the challenge has to be completed over HTTP rather than HTTPS, because many web servers hand the default HTTPS virtual host to some low privilege tenant in ways that aren’t obvious. The response has to be text/plain or have no content type, and the body has to match the token and the account key. If it does, the authorization is valid.
Putting the key thumbprint in the file is the clever bit. The security considerations in the draft walk through an attacker in the middle who registers their own account and tries to get the real domain owner to satisfy a challenge on the attacker’s behalf. Because the file contents are bound to one specific account key, a file provisioned for your key doesn’t validate anyone else’s.
The other challenge the client uses today is tls-sni-01, where the server presents specially generated self signed certificates for names ending in .acme.invalid. That’s what the client’s Apache plugin and its standalone mode on port 443 rely on, but for most people http-01 is the one to understand.
The official client
The client ISRG points to is simply called the Let’s Encrypt client, and the command is letsencrypt. Version 0.1.0 was tagged on December 2. It’s Python, it runs on Unix-like systems with Python 2.6 or 2.7, and its own README describes it as beta software that should be tested thoroughly before use on production systems. Only a handful of systems (Debian experimental, Arch Linux and FreeBSD) have packages so far, so the recommended route everywhere else is the letsencrypt-auto wrapper, which installs dependencies, sets up a virtual environment and updates itself:
git clone https://github.com/letsencrypt/letsencrypt
cd letsencrypt
./letsencrypt-auto --help
The client is built around plugins. Authenticators get certificates and installers put them into a server’s config. According to the 0.1.0 user guide, the Apache plugin does both if you run Apache 2.4 on a Debian based system with libaugeas0 1.0 or later. Nginx support exists but is marked very experimental and isn’t included in letsencrypt-auto. The standalone plugin runs its own little web server on port 80 or 443, so you may need to stop yours. The webroot plugin writes the challenge file into the document root of a server that’s already running, and the manual plugin gives you the steps to do by hand.
For a small site on nginx or anything else without an installer, webroot is the one I’d reach for, since nothing has to go down:
./letsencrypt-auto certonly --webroot -w /var/www/example -d example.com -d www.example.com
The first run creates the account and asks for an email address and agreement to the Subscriber Agreement (--email and --agree-tos answer those up front). Your files land in /etc/letsencrypt/live/example.com/, and the guide says to point the server at them there instead of copying them: fullchain.pem for nginx’s ssl_certificate, privkey.pem for the key. It also warns that your server has to serve files from hidden directories, which is worth checking if you block dotfiles.
Why only 90 days
Every Let’s Encrypt certificate lasts 90 days, and ISRG explained why in a November post, Why ninety-day lifetimes for certificates? The first reason is damage control: a stolen key or a mis-issued certificate stops being useful sooner. The second is that short lifetimes push people toward automation, and ISRG’s view is that the Web won’t move to HTTPS if administrators keep renewing by hand. The post recommends renewing every 60 days, which leaves a month of slack if something breaks, and adds that ISRG may consider even shorter lifetimes once renewal tools are mature.
Here’s the catch for this week. Renewal isn’t finished in the client yet. The 0.1.0 guide says to renew you run the client again with the same values, and that if you’re sure it won’t prompt for anything you can put the command in cron, running more often than every 90 days (it suggests monthly). It also says the team is working hard on automating renewal. The --renew-by-default flag makes the client replace an existing certificate for the same names without asking. So in practice that means running your exact command by hand with --text, --email, --agree-tos and --renew-by-default until it finishes without questions, then scheduling it, and reloading your web server afterwards so it serves the new files. The CA also emails the address on your account when a certificate is close to expiring, which makes a good safety net but a bad plan.
Being careful with it
Protect the account key. It lives under /etc/letsencrypt/accounts, in a directory the client creates with mode 700, and after registering the client tells you to make a secure backup of /etc/letsencrypt. Anyone holding that key can do anything your account is authorized to do, including requesting and revoking certificates for your domains. It’s also the reason to give an email address. The help text for --register-unsafely-without-email warns that without one, losing the key or having the account compromised means losing access to the account for good.
Test against staging. The client’s example config file points at a staging server, https://acme-staging.api.letsencrypt.org/directory, and you can do the same with --server on the command line while you work out your webroot paths and cron job.
That matters because there are rate limits. The client’s ACME library already knows a rateLimited error for too many requests of a given type, and Boulder’s rate limit code, the open source CA software behind Let’s Encrypt, defines limits on total certificates, certificates per name, registrations per IP and pending authorizations per account. Certificates per name are counted by base domain, so every subdomain of example.com shares one bucket. The only thresholds in the repository are in its test configuration, and I haven’t found official production numbers published, so assume they’re tight during the beta and don’t burn through issuance debugging a script against the real CA.
The takeaway
Let’s Encrypt removes cost from HTTPS, and ACME removes the human from proving you own a domain. The trade is that you’re now responsible for automation, with a young client whose renewal story is still being written. For a small site that’s a good deal: use webroot or the Apache plugin, practice on staging, back up /etc/letsencrypt, and put renewal on a schedule well inside 90 days.