Running your own Certificate Authority (CA) with OpenSSL gives you complete control over the certificates issued to your internal services, enabling mutual TLS, encrypted internal APIs, and custom certificate policies without relying on commercial CAs for private infrastructure. On RHEL 8, OpenSSL is pre-installed and the system trust store is managed through the ca-trust framework, making it straightforward to add your CA so that all tools using the system store — including curl, wget, and most browsers — will trust certificates you sign. In this tutorial you will build a two-tier CA hierarchy (root CA + intermediate CA), sign a server certificate, generate a Certificate Revocation List, and install your root CA into the RHEL 8 system trust store.
Prerequisites
- RHEL 8 server with a non-root sudo user
openssl1.1.1 or later (ships with RHEL 8:openssl version)ca-certificatespackage installed (usually present by default)- Understanding that private keys generated here must be stored securely; use a dedicated air-gapped machine for a real root CA
Step 1 — Create the Root CA Directory Structure
A well-organised CA directory keeps certificates, keys, CRL files, and configuration clearly separated. Create the standard layout under /etc/ssl/myCA.
mkdir -p /etc/ssl/myCA/{certs,private,crl,newcerts,intermediate}
chmod 700 /etc/ssl/myCA/private
cd /etc/ssl/myCA
# Create the serial file (OpenSSL increments this for each signed cert)
echo 1000 > /etc/ssl/myCA/serial
# Create the CRL number file
echo 1000 > /etc/ssl/myCA/crlnumber
# Create the certificate index database
touch /etc/ssl/myCA/index.txt
Step 2 — Configure openssl.cnf for the Root CA
Create a dedicated OpenSSL configuration file for the root CA. This avoids accidentally using system-wide defaults and makes the CA self-contained.
cat > /etc/ssl/myCA/openssl-root.cnf << 'EOF'
[ ca ]
default_ca = CA_default
[ CA_default ]
dir = /etc/ssl/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
certificate = $dir/certs/ca.crt
crlnumber = $dir/crlnumber
crl = $dir/crl/ca.crl
crl_extensions = crl_ext
default_crl_days = 30
default_md = sha256
name_opt = ca_default
cert_opt = ca_default
default_days = 3650
preserve = no
policy = policy_strict
[ policy_strict ]
countryName = match
stateOrProvinceName = match
organizationName = match
organizationalUnitName = optional
commonName = supplied
emailAddress = optional
[ req ]
default_bits = 4096
default_md = sha256
distinguished_name = req_distinguished_name
string_mask = utf8only
x509_extensions = v3_ca
[ req_distinguished_name ]
countryName = Country Name (2 letter code)
stateOrProvinceName = State or Province Name
localityName = Locality Name
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
[ v3_intermediate_ca ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical,CA:true,pathlen:0
keyUsage = critical,digitalSignature,cRLSign,keyCertSign
[ crl_ext ]
authorityKeyIdentifier = keyid:always
[ server_cert ]
basicConstraints = CA:FALSE
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
keyUsage = critical,digitalSignature,keyEncipherment
extendedKeyUsage = serverAuth
EOF
Step 3 — Create the Root CA Key and Self-Signed Certificate
Generate a 4096-bit RSA key and a 10-year self-signed root certificate. The root CA certificate will be distributed to clients so they trust the entire chain.
cd /etc/ssl/myCA
# Generate the root CA private key (protect this file above all else)
openssl genrsa -aes256 -out private/ca.key 4096
chmod 400 private/ca.key
# Create the self-signed root CA certificate (valid 3650 days)
openssl req -config openssl-root.cnf
-key private/ca.key
-new -x509 -days 3650
-sha256 -extensions v3_ca
-out certs/ca.crt
# Verify the certificate
openssl x509 -noout -text -in certs/ca.crt | grep -A2 "Basic Constraints"
Enter the subject fields when prompted. The Organization Name must match exactly in subordinate certificates when policy_strict is in effect.
Step 4 — Create an Intermediate CA
An intermediate CA signs end-entity certificates, keeping the root CA key offline. If the intermediate is compromised, only it needs to be revoked — the root remains intact.
mkdir -p /etc/ssl/myCA/intermediate/{certs,private,crl,csr,newcerts}
chmod 700 /etc/ssl/myCA/intermediate/private
echo 2000 > /etc/ssl/myCA/intermediate/serial
echo 2000 > /etc/ssl/myCA/intermediate/crlnumber
touch /etc/ssl/myCA/intermediate/index.txt
# Generate the intermediate key
openssl genrsa -aes256 -out intermediate/private/intermediate.key 4096
chmod 400 intermediate/private/intermediate.key
# Generate a CSR for the intermediate CA
openssl req -config openssl-root.cnf -new -sha256
-key intermediate/private/intermediate.key
-out intermediate/csr/intermediate.csr
# Sign the intermediate CSR with the root CA
openssl ca -config openssl-root.cnf
-extensions v3_intermediate_ca
-days 1825 -notext -md sha256
-in intermediate/csr/intermediate.csr
-out intermediate/certs/intermediate.crt
# Create the certificate chain file
cat intermediate/certs/intermediate.crt certs/ca.crt
> intermediate/certs/ca-chain.crt
Step 5 — Sign a Server Certificate
Using the intermediate CA, issue a certificate for an internal server. Create a CSR with a Subject Alternative Name (SAN) — modern clients require SAN; a Common Name alone is no longer sufficient.
# Generate server private key
openssl genrsa -out intermediate/private/server.key 2048
chmod 400 intermediate/private/server.key
# Create a minimal config snippet with SAN for the CSR
cat > /tmp/server-ext.cnf << 'EOF'
[req]
req_extensions = v3_req
distinguished_name = req_distinguished_name
[req_distinguished_name]
[v3_req]
subjectAltName = @alt_names
[alt_names]
DNS.1 = server.internal.example.com
IP.1 = 192.168.1.10
EOF
# Generate the CSR
openssl req -new -sha256
-key intermediate/private/server.key
-subj "/C=US/ST=California/O=MyOrg/CN=server.internal.example.com"
-config /tmp/server-ext.cnf
-out intermediate/csr/server.csr
# Sign with the intermediate CA
openssl x509 -req -days 365 -sha256
-CA intermediate/certs/intermediate.crt
-CAkey intermediate/private/intermediate.key
-CAserial intermediate/serial
-extfile /tmp/server-ext.cnf -extensions v3_req
-in intermediate/csr/server.csr
-out intermediate/certs/server.crt
# Verify the chain
openssl verify -CAfile intermediate/certs/ca-chain.crt
intermediate/certs/server.crt
Step 6 — Generate a CRL and Add the Root CA to the System Trust Store
Generate an initial (empty) CRL for the root CA, then add the root CA certificate to RHEL 8’s system-wide trust store so all applications using the system store trust your CA automatically.
# Generate the initial CRL for the root CA
openssl ca -config openssl-root.cnf
-gencrl -out crl/ca.crl
# Inspect the CRL
openssl crl -noout -text -in crl/ca.crl | head -20
# Add the root CA to the RHEL 8 system trust store
cp certs/ca.crt /etc/pki/ca-trust/source/anchors/myCA-root.crt
update-ca-trust extract
# Verify the CA is now trusted
trust list | grep -A3 "MyOrg"
# Test that curl trusts a server cert signed by this CA
curl -v https://server.internal.example.com/ 2>&1 | grep "SSL connection"
To revoke a certificate in the future, run openssl ca -config openssl-root.cnf -revoke <cert.crt> followed by openssl ca -config openssl-root.cnf -gencrl -out crl/ca.crl to regenerate the CRL, then distribute the updated CRL to your services.
Conclusion
You have built a complete two-tier PKI on RHEL 8: a root CA with a hardened directory structure and strict OpenSSL configuration, an intermediate CA for day-to-day signing, a server certificate with proper Subject Alternative Names, an initial CRL, and the root CA registered in the RHEL 8 system trust store. This infrastructure enables encrypted internal services, mutual TLS authentication, and certificate-based identity without depending on external certificate authorities.
Next steps: How to Configure Mutual TLS for Nginx on RHEL 8, How to Automate Certificate Renewal with Certbot on RHEL 8, and How to Install HashiCorp Vault for Secrets Management on RHEL 8.