How to Build a Secure Mini CA with a Raspberry Pi and Yubikey

Create a small certificate authority for your home network by running Step-CA on a Raspberry Pi; with a Yubikey for private key security.

Introduction

If you’re someone like me who runs a lot of servers and applications in your own network, but hates seeing security warnings from every web UI using a self-signed certificate, then hosting your own Certificate Authority is the best solution! Sure, you could simply get free trusted certificates from LetsEncrypt, but that requires using public domains and doesn’t provide half the fun and bragging rights of conquering the complex world of PKI (Public Key Infrastructure) with your own private CA. Not to mention it’s another cool use for that spare Raspberry Pi you have kicking around.

A proper CA will protect its private keys via an HSM (Hardware Security Module), but those are typically highly expensive enterprise appliances. Luckily we can make similar use of a Yubikey for a small-scale CA such as this.

For this project we will use the open-source application step-ca, developed by the nice people at Smallstep. They have their own guide published to their blog here, which I originally followed. I created this guide to apply my own style & context, and address some of the challenges I encountered.

Prerequisites

You’ll need the following hardware for this project:

Personal Identity Verification (PIV) is a standard originally created by the U.S. government for secure digital identity and authentication using smart cards. Since it utilizes the X.509 standard and we only need a couple of keys stored, we can use it as a rudimentary HSM (Hardware Security Module) to protect the private keys for our CA. A proper HSM can store thousands of keys and is MUCH more expensive.

Once you’re prepared, let’s SSH into your Raspberry Pi and get started!

Install Yubikey Manager

First you’ll need to install ykman, the YubiKey Manager CLI. This will allow us to interface with the Yubikey.

First let’s install the yubikey-manager package. It should be available in the default repositories.

sudo apt install -y yubikey-manager

Usually this should be all there is to it, but recent changes to the pcscd smart card service mean that we can only run ykman with sudo by default. This will be a problem later, so we need to configure proper access. First, create a group named yubico and add your user to it.

sudo groupadd yubico
sudo usermod -a -G yubico $USER

Next, create a file under /etc/polkit-1/rules.d named 67-yubikey.rules.

sudo nano /etc/polkit-1/rules.d/67-yubikey.rules

Copy the following config into the file and save it. This will grant users in the yubico group access to the smart card service.

polkit.addRule(function(action, subject) {
        if (action.id == "org.debian.pcsc-lite.access_card" &&
                subject.isInGroup("yubico")) {
                return polkit.Result.YES;
        }
});
polkit.addRule(function(action, subject) {
        if (action.id == "org.debian.pcsc-lite.access_pcsc" &&
                subject.isInGroup("yubico")) {
                return polkit.Result.YES;
        }
});

Now you should be able to run the ykman command to view your Yubikey info. Make sure that it shows PIV enabled for USB on your device. On mine I have previously disabled all other services.

$ ykman info
Device type: YubiKey 5C NFC
Serial number: 16486082
Firmware version: 5.2.7
Form factor: Keychain (USB-C)
Enabled USB interfaces: CCID
NFC transport is enabled

Applications	USB          	NFC          
Yubico OTP  	Disabled     	Disabled
FIDO U2F    	Disabled     	Disabled
FIDO2       	Disabled     	Disabled
OATH        	Disabled     	Disabled
PIV         	Enabled      	Disabled
OpenPGP     	Disabled     	Disabled
YubiHSM Auth	Not available	Not available

Install Go

Since step-ca is written in the Go language, we will need to install Go in order to build it.

First fetch the tarball.

curl -LO https://go.dev/dl/go1.25.1.linux-arm64.tar.gz

Unpack it to usr/local.

sudo tar -C /usr/local -xzf go1.25.1.linux-arm64.tar.gz

Add the binary to your PATH variable.

echo "export PATH=\$PATH:/usr/local/go/bin" >> .profile

Load the updated variable to your current shell session with source.

source .profile

Now test that it works by checking the Go version.

$ go version
go version go1.25.1 linux/arm64

Install Step Applications

Install Step-CA

The step-ca application is the actual server application for the CA. It can more easily be installed via repository, but since its support for Yubikeys is experimental, we will need to build it from source.

First, download the tarball, unpack it, and enter the certificates directory that is created.

curl -LO https://github.com/smallstep/certificates/archive/refs/tags/v0.28.4.tar.gz
tar -xvzf v0.28.4.tar.gz
cd certificates-0.28.4

Ensure that necessary dependencies are installed.

sudo apt-get install -y libpcsclite-dev gcc make pkg-config

Now run the following commands to build the application. Each one will take some time to run, so be patient.

make bootstrap
make build GOFLAGS=""

Once that is completed, copy the step-ca binary to /user/local/bin.

sudo cp bin/step-ca /usr/local/bin

Now we’ll use setcap to set necessary capabilities on the binary.

sudo setcap CAP_NET_BIND_SERVICE=+eip /usr/local/bin/step-ca

Now step-ca should be ready to run. We can test it by checking the version.

$ step-ca version
Smallstep CA/0.28.4 (linux/arm64)
Release Date: 2025-09-29 16:26 UTC

Install Step CLI

The step application is the client-side command line application used control and interface with step-ca. We can quickly download and install the prebuilt binary.

First return to your home directory.

cd

Download and unpack the tarball.

curl -LO https://dl.smallstep.com/gh-release/cli/gh-release-header/v0.28.7/step_linux_0.28.7_arm64.tar.gz
tar xvzf step_linux_0.28.7_arm64.tar.gz

Copy the binary to /usr/local/bin.

sudo cp step_0.28.7/bin/step /usr/local/bin

Test it by checking the version.

$ step version
Smallstep CLI/0.28.7 (linux/arm64)
Release Date: 2025-07-14 05:36 UTC

Configure your CAs

Now it is time to create and configure your Certificate Authorities. As is the recommended practice for most PKI implementations, we will create a Root CA keypair and an Intermediate CA keypair. The Root CA will sign the Intermediate CA’s public key (certificate), and the Intermediate CA will then be used to sign issued leaf certificates. But because we are using the Yubikey to secure the private keys for these CAs, the process becomes a little more complicated. The process will go as follows.

  1. The CAs will be generated and stored on the backup USB drive.
  2. The CA certificates and keys will be imported into the Yubikey.
  3. The CA certificates will be copied to the /root directory of the system.
  4. New CAs will be generated on the system, but only to create the configuration files.
  5. The original CA certificates stored in /root will replace the new certificates, and the new private keys will be deleted.
  6. The system will be configured to utilize the original private keys stored in the Yubikey.

For a paranoid level of security, you may want to perform the first 3 steps with the Raspberry Pi disconnected from the network.

Generate CAs on Backup USB Drive

First we will want to connect and mount the backup USB drive. Use the fdisk or lsblk commands to find the drive number. If there are no other USB storage devices connected, it will most likely be /dev/sda. Also make sure it has a partition with a compatible file system configured. In my example, I will be mounting /dev/sda1 to /mnt.

sudo mount /dev/sda1 /mnt

Enter the /mnt directory and create a new directory named ca.

cd /mnt
mkdir ca

Configure the STEPPATH variable to point to this directory. This will be a temporary configuration to generate the CA certificates on the backup USB drive.

export STEPPATH=/mnt/ca

Next we’ll use step ca init to generate our CAs in the ca directory that we created. Change the name to whatever suits you, as this will appear on all issued certificates.

step ca init --pki --name="Hacksanity" --deployment-type standalone

Generate a secure password for the CA keys and keep it somewhere safe. Also save the root fingerprint displayed, as it will be needed later.

Choose a password for your CA keys.
✔ [leave empty and we'll generate one]: █

Generating root certificate... done!
Generating intermediate certificate... done!

✔ Root certificate: /mnt/ca/certs/root_ca.crt
✔ Root private key: /mnt/ca/secrets/root_ca_key
✔ Root fingerprint: 3073240re32cf89b850d0e4b33b3ee5890bd264a2bb3f8076f855169fcd034af
✔ Intermediate certificate: /mnt/ca/certs/intermediate_ca.crt
✔ Intermediate private key: /mnt/ca/secrets/intermediate_ca_key

FEEDBACK ???? ????
  The step utility is not instrumented for usage statistics. It does not phone
  home. But your feedback is extremely valuable. Any information you can provide
  regarding how you’re using `step` helps. Please send us a sentence or two,
  good or bad at feedback@smallstep.com or join GitHub Discussions
  https://github.com/smallstep/certificates/discussions and our Discord 
  https://u.step.sm/discord.

Import CAs to Yubikey

First, make sure that the pcscd service is enabled and running. This is the smart card service that manages communication with the Yubikey.

sudo systemctl enable pcscd
sudo systemctl start pcscd
systemctl status pcscd

First we will import the root CA certificate and key. For this you will need the PIN for your Yubikey as well as the password previously created for the CA key.

ykman piv certificates import 9a /mnt/ca/certs/root_ca.crt
ykman piv keys import 9a /mnt/ca/secrets/root_ca_key

Now do the same for the Intermediate CA.

ykman piv certificates import 9c /mnt/ca/certs/intermediate_ca.crt
ykman piv keys import 9c /mnt/ca/secrets/intermediate_ca_key

Now you should be able to view the CA certificates on your Yubikey.

ykman piv info
PIV version:              5.2.7
PIN tries remaining:      3
Management key algorithm: TDES
Management key is stored on the YubiKey, protected by PIN.
CHUID: 3019d4e739da739ced39ce739d836858210842108421c84210c3eb34101b713b784b1e45809df915954de49dee350832303330303130313e00fe00
CCC:   No data available
Slot 9A (AUTHENTICATION):
  Private key type: EMPTY
  Public key type:  ECCP256
  Subject DN:       CN=Hacksanity Root CA,O=Hacksanity
  Issuer DN:        CN=Hacksanity Root CA,O=Hacksanity
  Serial:           00:00:00:00:f9:b7:49:ff:4d:e3:70:9a:25:dc:7d:8d:8f:af:e4:13
  Fingerprint:      3873240ce32cf89b850d0e4b33b3ee5890bd268a2bb3f8078f855869fcb034af
  Not before:       2025-11-23T20:23:32+00:00
  Not after:        2035-11-21T20:23:32+00:00

Slot 9C (SIGNATURE):
  Private key type: EMPTY
  Public key type:  ECCP256
  Subject DN:       CN=Hacksanity Intermediate CA,O=Hacksanity
  Issuer DN:        CN=Hacksanity Root CA,O=Hacksanity
  Serial:           00:00:00:00:03:b7:82:0e:22:cf:e7:2a:99:97:34:2e:f1:b5:26:90
  Fingerprint:      ce2d428792ecbbb4eca24a0f0436b362e0179ffb6b5b70d7a7c0c3f3fcc397f1
  Not before:       2025-11-23T20:23:33+00:00
  Not after:        2035-11-21T20:23:33+00:00

Copy out Certificate Files

Next we will copy the root and intermediate CA certificates to the /root directory temporarily. The private keys stay on the backup USB drive and Yubikey.

sudo cp /mnt/ca/certs/intermediate_ca.crt /mnt/ca/certs/root_ca.crt /root

Return to your home directory and unmount the USB drive.

cd
sudo umount /mnt

Store it somewhere safe, as it contains the backup copies of your CA private keys.

Generate New CA

First we will create a user named step. The command below will also create a group with the same name and add the user to it, configure it as a system user, set its home folder to /etc/step-ca, and configure the shell to /bin/false to prevent login with the user.

$ sudo useradd --user-group --system --home /etc/step-ca --shell /bin/false step
sudo passwd -l step

We’ll also need to add this user to the yubico group that we created previously, so that it can work with the Yubikey.

sudo usermod -a -G yubico step

Create a new directory under /etc named step-ca, then configure the STEPPATH variable to point to it.

sudo mkdir /etc/step-ca
export STEPPATH=/etc/step-ca

Now we are going to run step ca init again, but we are not going to use the CA certificates and keys that that are generated; as we will replace them with the CAs that we previously created. This step is simply to generate the configuration. Replace the name, hostname, IP address, and provisioner email examples with your own.

sudo --preserve-env step ca init --name="Hacksanity CA" \
    --dns="ca.hacksanity.local,172.16.0.3" --address=":443" \
    --provisioner="email@hacksanity.com" \
    --deployment-type standalone \
    --remote-management

Create a password for the provisioner. This will be used to administer the CA and manually request certificates from it. For security, it is best to keep it different from the password that was previously created for the CA keys.

Choose a password for your CA keys and first provisioner.
✔ [leave empty and we'll generate one]: 

Generating root certificate... done!
Generating intermediate certificate... done!

✔ Root certificate: /etc/step-ca/certs/root_ca.crt
✔ Root private key: /etc/step-ca/secrets/root_ca_key
✔ Root fingerprint: a63d82bab806c3aff77f601f6d798eff409ga97b102177ay2335bf24e8fc41c0
✔ Intermediate certificate: /etc/step-ca/certs/intermediate_ca.crt
✔ Intermediate private key: /etc/step-ca/secrets/intermediate_ca_key
badger 2025/12/06 18:00:57 INFO: All 0 tables opened in 0s
badger 2025/12/06 18:00:57 INFO: Storing value log head: {Fid:0 Len:30 Offset:2956}
badger 2025/12/06 18:00:57 INFO: [Compactor: 173] Running compaction: {level:0 score:1.73 dropPrefixes:[]} for level: 0
badger 2025/12/06 18:00:57 INFO: LOG Compact 0->1, del 1 tables, add 1 tables, took 11.789781ms
badger 2025/12/06 18:00:57 INFO: [Compactor: 173] Compaction for level: 0 DONE
badger 2025/12/06 18:00:57 INFO: Force compaction on level 0 done
✔ Database folder: /etc/step-ca/db
✔ Default configuration: /etc/step-ca/config/defaults.json
✔ Certificate Authority configuration: /etc/step-ca/config/ca.json
✔ Admin provisioner: email@hacksanity.com (JWK)
✔ Super admin subject: step

Your PKI is ready to go. To generate certificates for individual services see 'step help ca'.

FEEDBACK ???? ????
  The step utility is not instrumented for usage statistics. It does not phone
  home. But your feedback is extremely valuable. Any information you can provide
  regarding how you’re using `step` helps. Please send us a sentence or two,
  good or bad at feedback@smallstep.com or join GitHub Discussions
  https://github.com/smallstep/certificates/discussions and our Discord 
  https://u.step.sm/discord.

Replace CA Certificates

Now we’ll move our CA certificates that we previously copied to /root and overwrite the certificates that were just generated.

sudo mv /root/root_ca.crt /root/intermediate_ca.crt /etc/step-ca/certs

We’ll also remove the generated private keys, since we’ll be using the ones stored on the Yubikey.

sudo rm -rf /etc/step-ca/secrets

Configure Step-CA for Yubikey

Next we’ll configure step-ca to use the Yubikey as the path to the keys. To do this, edit the ca.json with a text editor of your choice.

sudo nano /etc/step-ca/config/ca.json

Change the top part of the config as below. We’ll change the private key file path to the Yubikey slot where we imported the private key for the intermediate CA certificate. We’ll also add lines to specify the KMS (Key Management Service) type and pin. The default Yubikey pin is shown in the example. Be sure to change it to your own.

{
        "root": "/etc/step-ca/certs/root_ca.crt",
        "federatedRoots": [],
        "crt": "/etc/step-ca/certs/intermediate_ca.crt",
        "key": "yubikey:slot-id=9c",
        "kms": {
            "type": "yubikey",
            "pin": "123456"
        },
        "address": ":443",
...

Test the CA

It’s time to try out our our CA. First let’s do a recursive chown on the /etc/step-ca directory to ensure the step user has rights to all files.

sudo chown -R step:step /etc/step-ca

Next we’ll manually start the service using our config file.

sudo -u step step-ca /etc/step-ca/config/ca.json

The output will include a Root Fingerprint. Copy and save this, as it will be needed in the next step. The service is running in the current terminal session, so do not end it.

badger 2026/01/19 16:24:20 INFO: All 1 tables opened in 1ms
badger 2026/01/19 16:24:20 INFO: Replaying file id: 0 at offset: 2986
badger 2026/01/19 16:24:20 INFO: Replay took: 7.185µs
2026/01/19 16:24:20 Building new tls configuration using step-ca x509 Signer Interface
2026/01/19 16:24:21 Starting Smallstep CA/ (linux/arm64)
2026/01/19 16:24:21 Documentation: https://u.step.sm/docs/ca
2026/01/19 16:24:21 Community Discord: https://u.step.sm/discord
2026/01/19 16:24:21 Config file: /etc/step-ca/config/ca.json
2026/01/19 16:24:21 The primary server URL is https://ca.hacksanity.lan:443
2026/01/19 16:24:21 Root certificates are available at https://ca.hacksanity.lan:443/roots.pem
2026/01/19 16:24:21 Additional configured hostnames: 172.16.253.3
2026/01/19 16:24:21 X.509 Root Fingerprint: 0c4ac4f3c09d01865ae5249b2a30418789c62700c1739f4fdb1bf3a005d7a8d6
2026/01/19 16:24:21 Serving HTTPS on :443 ...

In a new terminal session for your Raspberry Pi, run the step ca bootstrap command and specify your URL and Root Fingerprint saved from the previous session. This will download the root certificate from the certificate authority and configure the current environment to use it. You can also run it on any other system that has step-cli installed and network access to your CA.

step ca bootstrap --ca-url "https://ca.hacksanity.local" --fingerprint 0c4ac4f3c09d01865ae5249b2a30418789c62700c1739f4fdb1bf3a005d7a8d6

Now you can use the step ca certificate command to manually request a certificate. After running the previous bootstrap command to configure your environment, you no longer need to specify the CA URL or root fingerprint.

step ca certificate "test.hacksanity.local" test.crt test.key
✔ Provisioner: administrator@hacksanity.com (JWK) [kid: Z__uo94-QXAtwumLLdMhO6uUG-G85S_mpNmsgejBCww]
Please enter the password to decrypt the provisioner key: 
✔ CA: https://ca.hacksanity.local
✔ Certificate: test.crt
✔ Private Key: test.key

Note: If you examine the certificate that we just issued here, you’ll find that the validity period is just 24 hours. This is the default validity period for step-ca, since the PKI industry is generally moving towards shorter certificate lifecycles and automation. In practice you’ll want to set a longer validity period or use an automated protocol such as ACME for renewals. The step command that we just used can also be configured for automation. See: https://smallstep.com/docs/step-ca/renewal/

Configure a Systemd Daemon

Now we’ll create a service for step-ca managed by systemd.

The original guide on Smallstep’s blog configures the step-ca service to start and stop with insertion and removal of the Yubikey. I was unable to get this to work reliably, so I have utilized their standard documentation here.

Create and edit a systemd unit file named step-ca.service.

$ sudo nano /etc/systemd/system/step-ca.service

Copy/paste the following contents into the file.

[Unit]
Description=step-ca service
Documentation=https://smallstep.com/docs/step-ca
Documentation=https://smallstep.com/docs/step-ca/certificate-authority-server-production
After=network-online.target
Wants=network-online.target
StartLimitIntervalSec=30
StartLimitBurst=3
ConditionFileNotEmpty=/etc/step-ca/config/ca.json
ConditionFileNotEmpty=/etc/step-ca/password.txt

[Service]
Type=simple
User=step
Group=step
Environment=STEPPATH=/etc/step-ca
WorkingDirectory=/etc/step-ca
ExecStart=/usr/local/bin/step-ca config/ca.json --password-file password.txt
ExecReload=/bin/kill --signal HUP $MAINPID
Restart=on-failure
RestartSec=5
TimeoutStopSec=30
StartLimitInterval=30
StartLimitBurst=3

; Process capabilities & privileges
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
SecureBits=keep-caps
NoNewPrivileges=yes

; Sandboxing
ProtectSystem=full
ProtectHome=true
RestrictNamespaces=true
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
PrivateTmp=true
PrivateDevices=true
ProtectClock=true
ProtectControlGroups=true
ProtectKernelTunables=true
ProtectKernelLogs=true
ProtectKernelModules=true
LockPersonality=true
RestrictSUIDSGID=true
RemoveIPC=true
RestrictRealtime=true
SystemCallFilter=@system-service
SystemCallArchitectures=native
MemoryDenyWriteExecute=true
ReadWriteDirectories=/etc/step-ca/db

[Install]
WantedBy=multi-user.target

Save the file and then run a systemctl daemon-reload.

sudo systemctl daemon-reload

Enable and start the service.

sudo systemctl enable --now step-ca

Check the status of the service to ensure it started successfully.

sudo systemctl status step-ca

If there are any issues, check the logs for the service for further details.

sudo journalctl --follow --unit=step-ca

Add an ACME Provisioner

Now that our CA is up and running, step-ca provides many different options for issuing certificates via provisioners. ACME is a popular example, so we’ll add an ACME provisioner here using the command below. In my example, hacksanity-acme is the name I have chosen for the name of the provisioner. You can use any name you want.

step ca provisioner add hacksanity-acme --type acme --admin-name step

That’s it. Now we can use ACME clients to submit certificate requests to the ACME URL for the provisioner, which will be https://{ca-host}/acme/{provisioner-name}/directory. In my example, it will be:

https://ca.hacksanity.lan/hacksanity-acme/acme/directory

Secure your CA

If you intend to make serious use of your CA, it will be important to protect it from any authorized access. The most effective way to do this is to enable the firewall and allow only the step-ca service through it. This also means disabling SSH access, so any future config changes will require access to your Pi via a keyboard and monitor.

First let’s create an application file for UFW (Uncomplicated Firewall) for the step-ca service.

sudo nano /etc/ufw/applications.d/step-ca-server

Put the following config and save the file.

[step-ca]
title=Smallstep CA
description=step-ca is an online X.509 and SSH Certificate Authority
ports=443/tcp

Now set the firewall to allow step-ca and enable it.

sudo ufw allow step-ca
sudo ufw enable

Finish!

If you made it this far successfully, congrats! This is not a simple topic and I hope my guide has been helpful to you. Have fun!