Configure popular ACME clients to use a private CA with the ACME protocol
The Automated Certificate Management Environment (ACME) protocol radically simplifies TLS deployment.
With ACME, endpoints can obtain TLS certificates on their own, automatically.
step-ca works with any ACME-compliant (specifically, ACMEv2; RFC8555) client.
About this tutorial
Learn how to configure popular ACME clients to get certificates from step-ca.
Examples include copy/paste code blocks and specific commands for nginx, certbot, and more.
When complete, you will have a fully functioning ACME configuration using a private certificate authority.
Estimated effort: Reading time ~7 mins, Lab time ~20 to 60 mins.
Open source - For step-ca, this tutorial assumes you have initialized and started up an instance using the steps in Getting Started. Additionally, you'll need to configure your CA with an ACME provisioner. Run step ca provisioner add acme --type ACME, and restart your CA.
For example, an ACME provisioner named ACME on the host ca.internal has the directory URL:
ACME challenge type
You'll need to select the ACME challenge type.
Communication between an ACME client and server uses HTTPS.
Many clients will validate the server’s TLS certificate using the public root certificates in your system’s default trust store.
Some clients will let you pass a CA certificate bundle into the client.
Clients will validate the server’s HTTPS certificate using the public root certificates in your system’s default trust store.
When you’re connecting to Let’s Encrypt, it’s a public certificate authority and its root certificate is already in your system’s default trust store.
Your internal root certificate isn’t, so HTTPS connections from ACME clients to step-ca will fail.
There are two ways to address this challenge.
Explicitly configure your ACME client to trust step-ca’s root certificate, or
Add the step-ca root certificate to your system’s default trust store.
step provides a helper command to do the latter:
step certificate install
If you are using your certificate authority for TLS in production, explicitly configuring your ACME client to only trust your root certificate is a better option.
You will see how this method works with an example below.
You can find several other examples here.
If you are simulating Let’s Encrypt in pre-production, installing your root certificate is a more realistic simulation of production.
Once your root certificate is installed, no additional client configuration is necessary.
A Word of Caution
Adding a root certificate to your system’s trust store is a global operation. Certificates issued by your CA will be trusted everywhere, including in many web browsers.
With most ACME clients, you can configure how often you want to renew your certificates.
Choose a renewal period that is two-thirds of the entire certificate's lifetime, so that you'll have enough time to fix any renewal issues before it's too late.
sudo is required in certbot's standalone
mode so it can listen on
port 80 to complete the http-01 challenge. If you already have a webserver
running you can use webroot
mode instead. With the
appropriate plugincertbot also supports the dns-01 challenge for most popular DNS providers.
Deeper integrations with nginx
and apache can even configure
your server to use HTTPS automatically (we'll set this up ourselves later). All
of this works with step-ca.
You can renew all of the certificates you've installed using cerbot by running:
More subtly, certbot's default renewal job is tuned for Let's Encrypt's 90
day certificate lifetimes: it's run every 12 hours, with actual renewals
occurring for certificates within 30 days of expiry. By default, step-ca
issues certificates with much shorter 24 hour lifetimes. The cron entry
above accounts for this by running certbot renew every 15 minutes. You'll
also want to configure your domain to only renew certificates when they're
within a few hours of expiry by adding a line like:
renew_before_expiry =8 hours
to the top of your renewal configuration (e.g., in /etc/letsencrypt/renewal/foo.internal.conf).
acme.sh is another popular command-line ACME client. It's written completely in shell (bash, dash, and sh compatible) with very few dependencies.
To get a certificate from step-ca using acme.sh you need to:
Point acme.sh at your ACME directory URL using the --server flag
Tell acme.sh to trust your root certificate using the --ca-bundle flag
Renewals are slightly easier since acme.sh remembers to use the right root certificate. It can also remember how long you'd like to wait before renewing a certificate. Unfortunately, the duration is specified in days (via the --days flag) which is too coarse for step-ca's default 24 hour certificate lifetimes. So the easiest way to schedule renewals with acme.sh is to force them at a reasonable frequency, like every 8 hours, via cron:
win-acme (wacs.exe) is a popular ACME client for Windows.
To use win-acme with step-ca, you'll need to do the following:
Add your root CA certificate (root_ca.crt) to the Windows trust store.
Change the ACMEv2 endpoint used by win-acme (in the settings.json file that comes with the program) to point to your CA's ACME provisioner (eg. https://ca.internal/acme/acme/). Or pass the --baseuri flag with your ACME provisioner's endpoint.
We recommend using the tls-alpn-01 challenge type to prove ownership.
Caddy is an HTTP/2 web server with automatic HTTPS powered by an integrated ACME client.
In addition to serving static websites, Caddy is commonly used as a TLS-terminating API gateway proxy.
Caddy comes with its own ACME server and by default it will generate an internal CA and issue certificates to itself.
But, you can configure Caddy to use a local step-ca instance to obtain certificates.
Here's a Caddyfile global config block.
Add this to the top of your Caddyfile to get certificates from ca.internal for all configured domains:
With this code, you are telling Nginx to listen on port 443 using TLS, with a certificate and private key stored on disk.
Other resources provide a more thorough explanation of NGINX's various TLS configuration options.
We can start an HTTP server using python and check our work with curl:
The server is configured to verify client certificates if they are sent.
That means the server is configured to support mutual TLS.
The handler checks whether a client certificate was provided, and responds with a personalized greeting if one was.
With a few tweaks to this code you can implement robust access control.
There are other good options for programmatic ACME in Go.
The certmagic package builds on lego and offers higher level, easier to use abstractions.
The x/crypto/acme package is lower level and offers more control, but it currently implements a pre-standardization draft version of ACME that doesn’t work with step-ca.
Traefik is a modern reverse-proxy with integrated support for ACME. It's designed primarily to handle ingress for a compute cluster, dynamically routing traffic to microservices and web applications.
It's easy to get a certificate from step-ca in Traefik v2, using the tls-alpn-01 ACME challenge type.
Most importantly, Traefik will need to trust your root CA certificate. Either use the LEGO_CA_CERTIFICATES environment variable to provide the full path to your root_ca.crt when running traefik, or install your root certificate in your system's trust store by running step certificate install root_ca.crt.
In your Traefik static configuration, you'll need to add a certificatesResolvers block: