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.
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:
| Directory | Purpose |
|---|---|
| certs | Issued certificates |
| crl | Revocation lists |
| newcerts | OpenSSL database |
| private | Private keys |
| csr | Certificate 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: