How to Set Up a Certificate Authority with OpenSSL on RHEL 7

A Private Certificate Authority (CA) is essential infrastructure for any organization that needs to issue and manage TLS certificates for internal services, VPNs, mutual TLS authentication, or code signing — without relying on commercial CAs or exposing internal hostnames to the public internet. OpenSSL, which ships with RHEL 7, provides all the tools needed to build a complete two-tier CA hierarchy: a root CA that is kept offline as the trust anchor, and an intermediate CA that performs day-to-day certificate issuance. This guide walks through building that hierarchy from scratch, signing server certificate signing requests (CSRs), and managing certificate revocation lists (CRLs) — all using the standard OpenSSL toolkit available on RHEL 7.

Prerequisites

  • RHEL 7 system with root or sudo access
  • OpenSSL 1.0.2 or later (verify with openssl version)
  • Adequate disk space for CA data (a few hundred megabytes is typically sufficient)
  • A clear understanding of your internal domain naming scheme
  • Physical or logical isolation for the root CA system is strongly recommended in production
# Verify OpenSSL is installed and check version
openssl version -a

# Ensure the openssl package is up to date
yum update -y openssl

Step 1: Create the Root CA Directory Structure

A well-organized directory structure makes CA operations predictable and auditable. The OpenSSL CA command relies on several specific files and directories by convention. Create the entire structure for the root CA under /root/ca/:

# Create the root CA directory structure
mkdir -p /root/ca/{certs,crl,newcerts,private}
chmod 700 /root/ca/private

# Create required tracking files
touch /root/ca/index.txt
echo 1000 > /root/ca/serial
echo 1000 > /root/ca/crlnumber

# Verify the structure
ls -la /root/ca/

Directory and file purposes:

  • certs/ — stores issued certificates
  • crl/ — stores Certificate Revocation Lists
  • newcerts/ — OpenSSL stores a copy of each issued cert here, named by serial number
  • private/ — stores private keys (mode 700, root access only)
  • index.txt — plain text database of issued certificates
  • serial — tracks the next serial number to assign
  • crlnumber — tracks the CRL sequence number

Step 2: Create the Root CA OpenSSL Configuration

OpenSSL uses a configuration file (openssl.cnf) to define CA behavior, certificate extensions, and distinguished name defaults. Create a comprehensive configuration for the root CA:

cat > /root/ca/openssl.cnf << 'EOF'
[ ca ]
default_ca = CA_default

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

# The root key and root certificate
private_key       = $dir/private/ca.key.pem
certificate       = $dir/certs/ca.cert.pem

# Certificate Revocation List settings
crlnumber         = $dir/crlnumber
crl               = $dir/crl/ca.crl.pem
crl_extensions    = crl_ext
default_crl_days  = 30

# SHA-256 is the minimum recommended digest
default_md        = sha256

name_opt          = ca_default
cert_opt          = ca_default
default_days      = 375
preserve          = no
policy            = policy_strict

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

[ policy_loose ]
countryName             = optional
stateOrProvinceName     = optional
localityName            = optional
organizationName        = optional
organizationalUnitName  = optional
commonName              = supplied
emailAddress            = optional

[ req ]
default_bits        = 4096
distinguished_name  = req_distinguished_name
string_mask         = utf8only
default_md          = sha256
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

countryName_default             = US
stateOrProvinceName_default     = California
0.organizationName_default      = Example Corp
organizationalUnitName_default  = IT Security

[ 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

[ usr_cert ]
basicConstraints       = CA:FALSE
nsCertType             = client, email
nsComment              = "OpenSSL Generated Client Certificate"
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid,issuer
keyUsage               = critical, nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage       = clientAuth, emailProtection

[ server_cert ]
basicConstraints       = CA:FALSE
nsCertType             = server
nsComment              = "OpenSSL Generated Server Certificate"
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid,issuer:always
keyUsage               = critical, digitalSignature, keyEncipherment
extendedKeyUsage       = serverAuth

[ crl_ext ]
authorityKeyIdentifier = keyid:always

[ ocsp ]
basicConstraints       = CA:FALSE
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid,issuer
keyUsage               = critical, digitalSignature
extendedKeyUsage       = critical, OCSPSigning
EOF

Step 3: Generate the Root CA Private Key and Self-Signed Certificate

The root CA key is the most sensitive item in the entire PKI. It must be protected with a strong passphrase and, in production, stored on offline hardware. Generate a 4096-bit RSA key encrypted with AES-256:

cd /root/ca

# Generate the root CA private key (you will be prompted for a passphrase)
openssl genrsa -aes256 -out private/ca.key.pem 4096
chmod 400 private/ca.key.pem

# Generate the self-signed root CA certificate (valid for 20 years)
openssl req -config openssl.cnf 
    -key private/ca.key.pem 
    -new -x509 -days 7300 
    -sha256 
    -extensions v3_ca 
    -out certs/ca.cert.pem

chmod 444 certs/ca.cert.pem

# Verify the root certificate
openssl x509 -noout -text -in certs/ca.cert.pem | head -40

When prompted for the Distinguished Name, enter values consistent with your organization. The root CA certificate will be the trust anchor that you distribute to all clients and servers.

Step 4: Create the Intermediate CA

An intermediate CA signs day-to-day certificates on behalf of the root CA. If the intermediate CA is compromised, you can revoke it and issue a new one without invalidating your entire PKI trust chain. Create a separate directory for the intermediate CA:

mkdir -p /root/ca/intermediate/{certs,crl,csr,newcerts,private}
chmod 700 /root/ca/intermediate/private
touch /root/ca/intermediate/index.txt
echo 1000 > /root/ca/intermediate/serial
echo 1000 > /root/ca/intermediate/crlnumber

# Create an openssl.cnf for the intermediate CA (copy and modify root's)
cp /root/ca/openssl.cnf /root/ca/intermediate/openssl.cnf

# Edit the intermediate config to point to its own directory
sed -i 's|dir               = /root/ca$|dir               = /root/ca/intermediate|' 
    /root/ca/intermediate/openssl.cnf
sed -i 's|private_key.*=.*$|private_key       = $dir/private/intermediate.key.pem|' 
    /root/ca/intermediate/openssl.cnf
sed -i 's|certificate.*=.*$|certificate       = $dir/certs/intermediate.cert.pem|' 
    /root/ca/intermediate/openssl.cnf

# Generate the intermediate CA key
openssl genrsa -aes256 -out /root/ca/intermediate/private/intermediate.key.pem 4096
chmod 400 /root/ca/intermediate/private/intermediate.key.pem

# Generate the intermediate CA CSR
openssl req -config /root/ca/intermediate/openssl.cnf 
    -new -sha256 
    -key /root/ca/intermediate/private/intermediate.key.pem 
    -out /root/ca/intermediate/csr/intermediate.csr.pem

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

chmod 444 /root/ca/intermediate/certs/intermediate.cert.pem

# Verify the intermediate certificate
openssl x509 -noout -text -in /root/ca/intermediate/certs/intermediate.cert.pem | grep -E "Issuer|Subject|Not"

# Create the certificate chain bundle (intermediate + root)
cat /root/ca/intermediate/certs/intermediate.cert.pem 
    /root/ca/certs/ca.cert.pem > /root/ca/intermediate/certs/ca-chain.cert.pem
chmod 444 /root/ca/intermediate/certs/ca-chain.cert.pem

Step 5: Sign Server Certificate Signing Requests

When a server administrator needs a TLS certificate for a service, they generate a private key and CSR on their server and send you the CSR. You then sign it with the intermediate CA. Here is the complete workflow, simulating both sides:

# On the server (simulated here — in practice, done on the target server)
openssl genrsa -out /tmp/webserver.key.pem 2048
chmod 400 /tmp/webserver.key.pem

# Generate a CSR with Subject Alternative Names for modern browsers
openssl req -config /root/ca/intermediate/openssl.cnf 
    -key /tmp/webserver.key.pem 
    -new -sha256 
    -out /tmp/webserver.csr.pem 
    -subj "/C=US/ST=California/O=Example Corp/CN=www.example.com"

# Sign the CSR with the intermediate CA (on the CA server)
openssl ca -config /root/ca/intermediate/openssl.cnf 
    -extensions server_cert 
    -days 375 -notext -md sha256 
    -in /tmp/webserver.csr.pem 
    -out /root/ca/intermediate/certs/webserver.cert.pem

# Verify the signed certificate
openssl x509 -noout -text -in /root/ca/intermediate/certs/webserver.cert.pem

# Verify the chain
openssl verify -CAfile /root/ca/intermediate/certs/ca-chain.cert.pem 
    /root/ca/intermediate/certs/webserver.cert.pem

Step 6: Generate and Publish a Certificate Revocation List

If a certificate is compromised or an employee leaves, you must revoke it. Clients check the CRL (or OCSP responder) to confirm a certificate has not been revoked. Generate and update CRLs from both the root and intermediate CA:

# Revoke a certificate (specify the cert file, not the serial)
openssl ca -config /root/ca/intermediate/openssl.cnf 
    -revoke /root/ca/intermediate/certs/webserver.cert.pem

# Generate an updated CRL from the intermediate CA
openssl ca -config /root/ca/intermediate/openssl.cnf 
    -gencrl 
    -out /root/ca/intermediate/crl/intermediate.crl.pem

# Generate a CRL from the root CA as well
openssl ca -config /root/ca/openssl.cnf 
    -gencrl 
    -out /root/ca/crl/ca.crl.pem

# Inspect the CRL
openssl crl -in /root/ca/intermediate/crl/intermediate.crl.pem -noout -text

# Convert CRL to DER format for distribution (some systems require DER)
openssl crl -in /root/ca/intermediate/crl/intermediate.crl.pem 
    -outform DER 
    -out /root/ca/intermediate/crl/intermediate.crl

# Combine root and intermediate CRLs for full chain verification
cat /root/ca/intermediate/crl/intermediate.crl.pem 
    /root/ca/crl/ca.crl.pem > /root/ca/intermediate/crl/chain.crl.pem

Step 7: Distributing the CA Certificate to Clients

For the certificates you issue to be trusted automatically, clients must have the root CA certificate installed in their system or application trust store. On RHEL 7:

# Copy the CA certificate to the system trust store
cp /root/ca/certs/ca.cert.pem /etc/pki/ca-trust/source/anchors/example-corp-ca.crt

# Update the system trust store
update-ca-trust extract

# Verify the CA is trusted
trust list | grep "Example Corp"

# Test TLS verification against an internal server using the CA chain
openssl s_client -connect www.internal.example.com:443 
    -CAfile /root/ca/intermediate/certs/ca-chain.cert.pem 
    -brief

For non-RHEL clients such as Debian/Ubuntu, copy the certificate to /usr/local/share/ca-certificates/ and run update-ca-certificates. For Java applications, import the CA certificate into the JVM truststore using keytool.

Building your own Certificate Authority with OpenSSL on RHEL 7 gives you complete control over your internal PKI without dependence on commercial providers or exposure of internal hostnames to external DNS. The two-tier root/intermediate hierarchy protects your root key by keeping it offline while enabling efficient day-to-day certificate operations through the intermediate CA. As your infrastructure grows, this foundation supports advanced use cases including mutual TLS authentication for microservices, client certificate authentication for VPN access, and automated certificate management using tools like Certbot with an ACME-compatible internal CA such as Step-CA or HashiCorp Vault’s PKI secrets engine.