Create and Manage a Certificate Authority (CA) via Bash

Create and Manage a Certificate Authority (CA) via Bash

Introduction

Managing your own internal Certificate Authority (CA) is a common requirement for DevOps teams, platform engineers, municipalities, and enterprise IT departments. An internal CA allows organizations to issue and manage certificates for internal services, Kubernetes clusters, VPNs, appliances, and development environments without relying on public CAs.

This guide explains how to create and manage a simple internal CA using Bash and OpenSSL on Linux systems.

The documentation covers:

  • Creating a Root CA
  • Generating private keys
  • Issuing server certificates
  • Signing CSRs
  • Revoking certificates
  • Generating Certificate Revocation Lists (CRLs)
  • Automating tasks with Bash

Target audience:

  • DevOps Engineers
  • Reliability Engineers
  • Infrastructure Teams
  • IT Security Administrators

Problem

Organizations often need certificates for:

  • Internal web applications
  • API gateways
  • Kubernetes ingress
  • VPN servers
  • Monitoring systems
  • Device authentication

Using public Certificate Authorities for internal systems can be:

  • Expensive
  • Operationally complex
  • Incompatible with private/internal DNS
  • Difficult to automate at scale

Without a centralized CA process, teams often end up with:

  • Self-signed certificates everywhere
  • Expired certificates
  • No revocation process
  • Poor visibility into certificate lifecycle

Solution

We will build a lightweight OpenSSL-based Certificate Authority using Bash.

The setup includes:

  • Root CA structure
  • Certificate database
  • Certificate issuance scripts
  • Revocation support
  • Automation examples

Step 1 — Create CA Directory Structure

Create a secure directory structure for the CA.

mkdir -p ~/myCA/{certs,crl,newcerts,private,csr}
chmod 700 ~/myCA/private

touch ~/myCA/index.txt
echo 1000 > ~/myCA/serial
echo 1000 > ~/myCA/crlnumber

Directory overview:

DirectoryPurpose
certsIssued certificates
crlRevocation lists
newcertsOpenSSL database
privatePrivate keys
csrCertificate Signing Requests

Step 2 — Create OpenSSL Configuration

Create openssl.cnf.

nano ~/myCA/openssl.cnf

Example configuration:

[ ca ]
default_ca = CA_default

[ CA_default ]
dir               = /home/user/myCA
certs             = $dir/certs
crl_dir           = $dir/crl
new_certs_dir     = $dir/newcerts
database          = $dir/index.txt
serial            = $dir/serial
RANDFILE          = $dir/private/.rand

private_key       = $dir/private/ca.key.pem
certificate       = $dir/certs/ca.cert.pem

crlnumber         = $dir/crlnumber
crl               = $dir/crl/ca.crl.pem

default_md        = sha256
name_opt          = ca_default
cert_opt          = ca_default
default_days      = 825
preserve          = no
policy            = policy_strict

[ policy_strict ]
countryName             = optional
stateOrProvinceName     = optional
organizationName        = optional
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

[ req ]
default_bits        = 4096
distinguished_name  = req_distinguished_name
string_mask         = utf8only
default_md          = sha256

[ req_distinguished_name ]
commonName = Common Name
commonName_max = 64

Step 3 — Generate Root CA Private Key

Generate the Root CA private key.

openssl genrsa -aes256 \
  -out ~/myCA/private/ca.key.pem 4096

Secure the key:

chmod 400 ~/myCA/private/ca.key.pem

Example output:

Generating RSA private key, 4096 bit long modulus
........................................++++
................................++++
e is 65537 (0x10001)

Step 4 — Create Root CA Certificate

Generate the self-signed Root CA certificate.

openssl req -config ~/myCA/openssl.cnf \
  -key ~/myCA/private/ca.key.pem \
  -new -x509 -days 3650 -sha256 \
  -extensions v3_ca \
  -out ~/myCA/certs/ca.cert.pem

Verify:

openssl x509 -noout -text \
  -in ~/myCA/certs/ca.cert.pem

Placeholder Screenshot

[Screenshot Placeholder]
OpenSSL Root CA certificate details shown in terminal

Step 5 — Generate a Server Certificate

Create a private key for the server.

openssl genrsa \
  -out server.example.local.key.pem 2048

Generate a CSR.

openssl req -new -sha256 \
  -key server.example.local.key.pem \
  -out ~/myCA/csr/server.example.local.csr.pem

Step 6 — Sign the Certificate

Use the CA to sign the CSR.

openssl ca -config ~/myCA/openssl.cnf \
  -extensions server_cert \
  -days 825 \
  -notext \
  -md sha256 \
  -in ~/myCA/csr/server.example.local.csr.pem \
  -out ~/myCA/certs/server.example.local.cert.pem

Verify the certificate:

openssl verify \
  -CAfile ~/myCA/certs/ca.cert.pem \
  ~/myCA/certs/server.example.local.cert.pem

Expected output:

server.example.local.cert.pem: OK

Step 7 — Revoke a Certificate

If a certificate is compromised or no longer needed:

openssl ca -config ~/myCA/openssl.cnf \
  -revoke ~/myCA/certs/server.example.local.cert.pem

Example output:

Revoking Certificate 1000.
Database updated

Step 8 — Generate a CRL

Generate a Certificate Revocation List.

openssl ca -config ~/myCA/openssl.cnf \
  -gencrl \
  -out ~/myCA/crl/ca.crl.pem

Inspect the CRL:

openssl crl -in ~/myCA/crl/ca.crl.pem \
  -noout -text

Step 9 — Automate Certificate Issuance with Bash

Example script: issue-cert.sh

#!/bin/bash

DOMAIN=$1

if [ -z "$DOMAIN" ]; then
  echo "Usage: $0 domain"
  exit 1
fi

openssl genrsa \
  -out ${DOMAIN}.key.pem 2048

openssl req -new \
  -key ${DOMAIN}.key.pem \
  -out ~/myCA/csr/${DOMAIN}.csr.pem \
  -subj "/CN=${DOMAIN}"

openssl ca -batch \
  -config ~/myCA/openssl.cnf \
  -in ~/myCA/csr/${DOMAIN}.csr.pem \
  -out ~/myCA/certs/${DOMAIN}.cert.pem

echo "Certificate issued:"
echo "Key: ${DOMAIN}.key.pem"
echo "Certificate: ~/myCA/certs/${DOMAIN}.cert.pem"

Make executable:

chmod +x issue-cert.sh

Usage:

./issue-cert.sh api.internal.local

Placeholder Screenshot

[Screenshot Placeholder]
Automatic certificate issuance script execution

Step 10 — Best Practices

Protect the Root CA

Store the Root CA private key securely:

  • Offline if possible
  • Encrypted backups
  • Limited access permissions
  • Hardware Security Module (HSM) if available

Use an Intermediate CA

For production environments:

  • Keep Root CA offline
  • Use an Intermediate CA for daily operations
  • Limit blast radius of compromise

Monitor Expiration Dates

Automate expiration monitoring:

openssl x509 -enddate -noout \
  -in certificate.pem

Example:

notAfter=Aug 20 12:00:00 2028 GMT

Use SAN Certificates

Modern TLS clients require Subject Alternative Names (SAN).

Example CSR config:

subjectAltName = DNS:api.internal.local,DNS:api

Enable CRL or OCSP

For enterprise environments:

  • Publish CRLs centrally
  • Consider OCSP responders
  • Integrate revocation checking in applications

Common Troubleshooting

”TXT_DB error number 2”

Occurs when duplicate subject names exist.

Solution:

unique_subject = no

“unable to load config info”

Verify:

openssl version -d

Ensure correct config path:

-config ~/myCA/openssl.cnf

Certificate Verification Failed

Check:

  • Correct CA chain
  • SAN configuration
  • Expiration dates
  • System trust store

Conclusion

Using Bash and OpenSSL to manage an internal CA provides:

  • Full certificate lifecycle control
  • Internal PKI automation
  • Reduced dependency on public CAs
  • Better operational visibility

This lightweight approach works well for:

    * Labs * Internal enterprise infrastructure * Kubernetes clusters * VPN deployments * Development environments

For larger environments, consider integrating with:

Was this helpful?