Self-hosting PKI with Step-CA

April 23, 2024

This article will receive updates in the next few weeks

There are many reasons to self-host your PKI instead of just using Letsencrypt or the like. Maybe your infrastructure doesn't have direct access to the internet (and DNS0 challenges can be messy to set up), or maybe you just don't want the world to know what's on your network. It's easy to forget that every publicly issued certificate ends up in a public Certificate Transparency log, like this.

Luckily it's pretty easy to self-host your PKI these days. You can easily set up an ACME-server and continue to use the modern tools you've gotten used to, like certbot, dehydrated, or tinyacme.

The solution I've gone for is step-ca from Smallstep. Smallstep delivers solutions for certificate management, and step-ca is their open source solution for this.

step-ca is an online Certificate Authority (CA) for secure, automated X.509 and SSH certificate management. It's the server counterpart to step CLI. It is secured with TLS, and it offers several configurable certificate provisioners, flexible certificate templating, and pluggable database backends to suit a wide variety of contexts and workflows. It employs sane default algorithms and attributes, so you don't have to be a security engineer to use it securely.


Installation is pretty straight forward, and they support a variety of platforms.

I've done this on OpenBSD, but I'll add inn steps for Debian/Ubuntu later.

We start off by installing step-ca and step-cli.

$ doas pkg_add -i step-ca step-cli

And that's it. step-ca is now installed. There's an rc-config in /etc/rc.d, and a new user (_step-ca) was automagically added.


Setting things up now could be as easy as running step ca init as the right user, but this sets up a root certificate that is valid for ten years.

I think ten years might be the worst expirationtime one can choose. It's long enough that you've definitely forgotten how you set things up, and short enough that things are probably still using it when it expires. Either choose something short enough that you'll easily be able to rotate it when it expires, or something long enough that you won't have to worry. I chose the latter, and went for 100+ years.

First cd into /var/step-ca, create folders for secrets and certificates, and create a random password for the intermediate certificate that will be used to sign other certificates.

$ doas su -l -s /bin/sh _step-ca
$ cd /var/step-ca
$ mkdir /var/step-ca/{certs,secrets}
$ chmod 700 /var/step-ca/{certs,secrets}
$ tr -cd '[:alnum:]_-' < /dev/urandom | fold -w 64 | head -n1 > secrets/intermediate_ca_pw
$ chmod 640 secrets/intermediate_ca_pw

I've placed the intermediate certificate password in a file so that I don't have to type it in every time I start the ACME-server. The root certificate will have a different password, and will have to be typed in any time you want to use it.

Now, let's create the root certificate.

$ STEPPATH=/var/step-ca step certificate create --not-after 999999h --profile root-ca my-CA certs/root_ca.crt secrets/root_ca_key

Type in a good password, and write it down somewhere!

$ export STEPPATH=/var/step-ca
$ step ca init --root certs/root_ca.crt --key secrets/root_ca_key
- password from above
- Select "standalone"
- name the CA
- hostname for ACME server
- IP and port to bind to. I chose
- Name "first provisioner". Dunno what this is. chose jwk
- password for intermediate key - copy from secrets/intermediate_ca_pw
- would you like to overwrite? yes

The last question where it asks if you'd like to overwrite is about the root certificate. It's asking if it can copy the root-certificate into the certs/-folder, where it already is. Just answer yes.

Now it's time to set up some provisioners. This is various ways for step-ca to sign your certificates, like ACME. I'm setting up these to issue certificates that's valid, by default, for 60 days, and max 90 days.

$ step ca provisioner add acme --type ACME"
$ step ca provisioner update jwk --x509-max-dur=2160h --x509-default-dur=1440h"
$ step ca provisioner update acme --x509-max-dur=2160h --x509-default-dur=1440h"

The jwk-one is for manual signing.

That's pretty much it. Now we just have to enable the service, and start it.

$ exit # <- if you're still logged in as _step-ca
$ doas rcctl enable step_ca
$ doas rcctl set step_ca flags --password-file=secrets/intermediate_ca_pw
$ doas rcctl start step_ca

And install the new root-certificate

$ doas sh -c "echo '\n### step-ca local root\n' >> /etc/ssl/cert.pem"
$ doas sh -c "openssl x509 -in /var/step-ca/certs/root_ca.crt -text >> /etc/ssl/cert.pem"

Oh, and you might have to create a certificate for your ACME-server. This is where that jwk-certificate comes in handy!

# doas -u _step-ca sh -c "env STEPPATH=/var/step-ca step ca certificate --offline"

Adding the root certificate to your devices

Add certificate on linux:

On macOS:

On iOS:

Using the new ACME-server

certbot certonly --server --noninteractive --agree-tos --email -d

Debian / Ubuntu

There aren't repo packages for Debian/Ubuntu, so there you'll have to download the package and install manually. Luckily, this isn't complicated:

$ wget
$ sudo dpkg -i step-cli_amd64.deb
$ wget
$ sudo dpkg -i step-ca_amd64.deb

More is coming here.