How to Build a Private Certificate Authority for Internal HTTPS/SSL Validation


3 views

Those browser warnings for self-signed certificates become a real productivity killer in development environments. While Let's Encrypt works great for public-facing sites, internal tools and staging environments often need a more flexible solution where you control the root of trust.

Before we dive into OpenSSL commands, let's understand the moving parts:

Root CA Certificate (the ultimate trust anchor)
Intermediate CA (optional but recommended)
Server Certificates (end-entity certs)
Certificate Revocation List (CRL)

First, create a configuration file (root-ca.cnf):

[ req ]
default_bits        = 4096
default_md          = sha256
distinguished_name  = req_distinguished_name
x509_extensions     = v3_ca

[ req_distinguished_name ]
countryName             = Country Name (2 letter code)
stateOrProvinceName     = State or Province Name
localityName            = Locality Name
0.organizationName      = Organization Name
organizationalUnitName  = Organizational Unit Name
commonName              = Common Name
emailAddress            = Email Address

[ v3_ca ]
subjectKeyIdentifier    = hash
authorityKeyIdentifier  = keyid:always,issuer
basicConstraints        = critical, CA:true
keyUsage                = critical, digitalSignature, cRLSign, keyCertSign

Generate the root key and certificate:

openssl genrsa -aes256 -out root-ca.key 4096
openssl req -x509 -new -nodes -key root-ca.key -sha256 -days 3650 \
  -out root-ca.crt -config root-ca.cnf

For better security hygiene, never issue end-entity certificates directly from your root CA. Create an intermediate:

openssl genrsa -aes256 -out intermediate-ca.key 4096
openssl req -new -key intermediate-ca.key -out intermediate-ca.csr \
  -config intermediate-ca.cnf
openssl x509 -req -in intermediate-ca.csr -CA root-ca.crt \
  -CAkey root-ca.key -CAcreateserial -out intermediate-ca.crt \
  -days 1825 -sha256 -extfile intermediate-ca.ext

Now let's create a certificate for internal-service.example.com:

openssl genrsa -out internal-service.key 2048
openssl req -new -key internal-service.key -out internal-service.csr \
  -config csr.cnf
openssl x509 -req -in internal-service.csr -CA intermediate-ca.crt \
  -CAkey intermediate-ca.key -CAcreateserial -out internal-service.crt \
  -days 365 -sha256 -extfile server-cert.ext

For Windows systems (PowerShell):

Import-Certificate -FilePath root-ca.crt -CertStoreLocation Cert:\LocalMachine\Root

For Linux (Debian/Ubuntu):

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

For Chrome/Chromium browsers:

# On Linux
cp root-ca.crt ~/.pki/nssdb/
certutil -d sql:~/.pki/nssdb -A -t "C,," -n "MyCompany CA" -i root-ca.crt

# On Windows
certutil -addstore -f "ROOT" root-ca.crt

Consider using configuration management tools:

# Ansible example
- name: Install root CA certificate
  win_certificate_store:
    file_path: "{{ root_ca_path }}"
    store_location: LocalMachine
    store_name: Root
    state: present

html

Building an Enterprise PKI: Step-by-Step Guide to Creating Your Own Root CA for Internal HTTPS

When managing internal services across an organization, browser security warnings for self-signed certificates become a constant nuisance. A properly configured private CA lets you:

  • Issue trusted certificates for internal domains (dev.example.com, vpn.corp.local)
  • Automate certificate lifecycle management
  • Implement certificate revocation through CRL/OCSP
  • Maintain consistent security policies across all services

For a production-ready CA, you'll need:

# Infrastructure Requirements
- Dedicated Linux server (physical preferred)
- Hardware Security Module (HSM) for key storage
- Backup procedures for CA keys
- Network segregation from production

Generate the root CA key with maximum security (4096-bit RSA with SHA-256):

openssl genrsa -aes256 -out private/ca.key.pem 4096
chmod 400 private/ca.key.pem

# Create root certificate
openssl req -config openssl.cnf \
  -key private/ca.key.pem \
  -new -x509 -days 7300 -sha256 -extensions v3_ca \
  -out certs/ca.cert.pem

Never issue directly from the root. Create an intermediate CA:

# Generate intermediate key
openssl genrsa -aes256 \
  -out intermediate/private/intermediate.key.pem 4096

# Create CSR
openssl req -config intermediate/openssl.cnf -new -sha256 \
  -key intermediate/private/intermediate.key.pem \
  -out intermediate/csr/intermediate.csr.pem

# Sign with root CA
openssl ca -config openssl.cnf -extensions v3_intermediate_ca \
  -days 3650 -notext -md sha256 \
  -in intermediate/csr/intermediate.csr.pem \
  -out intermediate/certs/intermediate.cert.pem

Define certificate constraints in openssl.cnf:

[ server_cert ]
basicConstraints = CA:FALSE
nsCertType = server
nsComment = "Enterprise Internal Certificate"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth

Distribute your root certificate through:

  • Group Policy (Windows)
  • MDM profiles (macOS/iOS)
  • Puppet/Ansible (Linux)
  • Chrome Enterprise policies

Example using certbot with your CA:

certbot certonly --manual \
  --preferred-challenges dns \
  --manual-auth-hook ./authenticator.sh \
  --manual-cleanup-hook ./cleanup.sh \
  -d *.dev.example.com \
  --cert-path /etc/ssl/private/ \
  --key-path /etc/ssl/private/

Critical operational considerations:

# Check certificate expiration
openssl x509 -noout -dates -in cert.pem

# Generate CRL
openssl ca -config openssl.cnf \
  -gencrl -out crl/root-ca.crl.pem

# OCSP responder setup
openssl ocsp -index index.txt \
  -port 2560 -text -sha256 \
  -CA certs/ca-chain.cert.pem \
  -rkey private/ocsp.key.pem \
  -rsigner certs/ocsp.cert.pem
  • Store root CA keys offline (air-gapped system)
  • Implement certificate transparency logging
  • Rotate intermediate CA keys annually
  • Audit certificate usage monthly