🔒 Security Advanced ⏱️ 25 min

Building a Private PKI: Root CA to Certificates

A step-by-step guide to bootstrapping a private PKI, from generating a Root CA to signing leaf certificates for internal microservices using OpenSSL.

By Victor Robin

Introduction

In a zero-trust architecture, you cannot blindly trust the network. Every internal service-to-service communication—from the API to the Database, or NATS to the Workers—must be authenticated and encrypted. A Private Public Key Infrastructure (PKI) allows us to issue our own TLS certificates, giving us full control over trust chains without the cost or constraints of public CAs.

Why Build a Private PKI:

  • Mutual TLS (mTLS): Authenticate both client and server (e.g., Worker ↔ Broker).
  • Cost: Issue unlimited certificates for internal domains (*.bluerobin.local) for free.
  • Control: Set custom expirations and policies suited for high-velocity DevOps.

What We’ll Build

In this guide, we will implement a 2-tier PKI hierarchy. You will learn how to:

  1. Bootstrap trust: Generate a secure Root Certificate Authority (CA).
  2. Delegate authority: Create an Intermediate CA for daily signing operations.
  3. Issue certificates: Sign leaf certificates for our internal NATS and PostgreSQL endpoints.
  4. Establish trust: Install the Root CA in OS trust stores.

Architecture Overview

We use a hierarchical trust model to protect the “keys to the kingdom.”

flowchart TD
    RootCA[("Root CA\n(Offline)")]
    InterCA[("Intermediate CA\n(Online/Issuer)")]
    Leaf1["*.bluerobin.local\n(Web Server)"]
    Leaf2["postgres.data-layer\n(Database)"]
    Leaf3["nats-client\n(Worker Node)"]

    RootCA -->|Signs| InterCA
    InterCA -->|Signs| Leaf1
    InterCA -->|Signs| Leaf2
    InterCA -->|Signs| Leaf3

    classDef primary fill:#7c3aed,stroke:#fff,color:#fff
    classDef secondary fill:#06b6d4,stroke:#fff,color:#fff
    classDef db fill:#f43f5e,stroke:#fff,color:#fff
    classDef warning fill:#fbbf24,stroke:#fff,color:#fff

    class Leaf1,Leaf3 primary
    class RootCA,InterCA secondary
    class Leaf2 db

Section 1: The Root CA

The Root CA is the anchor of trust. Its private key is the most sensitive file in your infrastructure. If compromised, an attacker can impersonate any service.

Generating the Root Key and Certificate

# 1. Generate the Root Private Key (AES-256 encrypted)
openssl genrsa -aes256 -out root-ca.key 4096

# 2. Create the Root Certificate (valid for 10 years)
openssl req -config root-ca.conf \
    -key root-ca.key \
    -new -x509 -days 3650 -sha256 -extensions v3_ca \
    -out root-ca.crt \
    -subj "/C=US/O=BlueRobin/CN=BlueRobin Root CA"

Section 2: The Intermediate CA

Using the Root CA for every daily task exposes it to risk. Instead, we sign an Intermediate CA.

# 1. Generate Intermediate Key
openssl genrsa -out intermediate-ca.key 4096

# 2. Create CSR (Certificate Signing Request)
openssl req -config intermediate-ca.conf -new -sha256 \
    -key intermediate-ca.key \
    -out intermediate-ca.csr

# 3. Sign the Intermediate CSR with the Root CA
openssl ca -config root-ca.conf -extensions v3_intermediate_ca \
    -days 1825 -notext -md sha256 \
    -in intermediate-ca.csr \
    -out intermediate-ca.crt

Now, we chain them together to create the full chain file provided to clients:

cat intermediate-ca.crt root-ca.crt > ca-chain.pem

Section 3: Issuing Leaf Certificates

Now our web services or databases can request certificates signed by the Intermediate CA.

# 1. Generate Leaf Key for PostgreSQL
openssl genrsa -out postgres.key 2048

# 2. Create CSR
openssl req -new -key postgres.key -out postgres.csr \
    -subj "/C=US/O=BlueRobin/CN=postgres.bluerobin.local"

# 3. Sign with Intermediate CA
openssl x509 -req -in postgres.csr \
    -CA intermediate-ca.crt -CAkey intermediate-ca.key -CAcreateserial \
    -out postgres.crt -days 365 -sha256

Section 4: Testing & Trust

For your local machine to trust these certificates, the root-ca.crt must be added to the trust store.

macOS

sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain root-ca.crt

Linux (Ubuntu/Debian)

sudo cp root-ca.crt /usr/local/share/ca-certificates/bluerobin-root.crt
sudo update-ca-certificates

Verification:

openssl verify -CAfile ca-chain.pem postgres.crt
# Output: postgres.crt: OK

Conclusion

We have successfully built a PKI backbone. This infrastructure enables us to enforce hostssl in PostgreSQL and secure NATS streams, forming the bedrock of our internal security posture.

Next Steps: