Certificate Operations Manual

15 min read
On this page

Identify the certificate under inspection: issued by the CA, installed on disk, loaded by the service or received by the client. These can differ. HTTPS validation also requires matching identity, time, chain and trust.

TaskStart here
Browser certificate errorInspect the served certificate
Renewed but still expiredRenewal to deployment
Create CSR / local certificateNames, keys and trust

SNI selects the site; hostname verification checks identity

Live endpoint: verify handshake and identity

# Connect to the site without suppressing validation failures
openssl s_client -connect example.com:443 \
  -servername example.com -verify_hostname example.com \
  -verify_return_error -showcerts </dev/null

-connect sets TCP destination, -servername SNI, -verify_hostname name checking, and -verify_return_error aborts on validation failure. -showcerts prints the supplied chain. Inspect verification and stderr.

For a specific edge IP, change -connect but retain the real SNI/hostname. Use a controlled CAfile for private trust rather than disabling verification.

Disk file: inspect metadata and expiry

# Read the first certificate’s names, issuer, and dates
openssl x509 -in fullchain.pem -noout -subject -issuer -dates -ext subjectAltName

A fullchain normally starts with the leaf, then intermediates. This parses only the first certificate; it does not validate the chain. Use the service’s actual file.

# Check whether expiry falls within the next 24 hours
openssl x509 -in fullchain.pem -noout -checkend 86400

Exit 0 means no expiry in that window; inspect errors/results otherwise. The interval is seconds and says nothing about name or trust.

Keep verification errors visible during the complete handshake check. For a private CA provide an intended trusted -CAfile. -servername alone does not verify hostname. checkend 86400 tests expiry within the next day, not just expiry now, and does not replace identity, trust-chain or notBefore checks.

CheckFailure evidenceFix location
SAN / hostnameConnection name absent from SANIssue correct identity or fix the URL
notBefore / notAfterClock error, not yet valid or expiredCorrect clock / renew and deploy
ChainMissing intermediate or wrong chainInstall the proper fullchain
TrustClient does not trust the rootUse an appropriate CA or managed trust, not disabled verification

Renewal completes at the client

Certificate deployment chain CA issues certificate Install at service path Service loads new content Client checks fingerprint

Match installed and served certificates

# Read the configured on-disk certificate identity
openssl x509 -in /etc/ssl/example/fullchain.pem -noout -serial -fingerprint -sha256

Record serial and SHA-256 fingerprint, then compare the same endpoint/SNI.

# Extract only the live leaf fingerprint for comparison
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null |
  openssl x509 -noout -serial -fingerprint -sha256

This metadata extraction suppresses handshake diagnostics and is not a trust test. Run full validation afterward; mismatches may identify a CDN/LB or another instance.

The pipeline extracts identity only, not trust; use the earlier validation command. If disk and served certificates differ, inspect configured paths, permissions, reload hooks and TLS termination at load balancers/CDNs. Verify all relevant edge nodes.

Choose one certificate owner

acme.sh, Certbot, and Caddy automatic HTTPS are alternatives. Identify the existing owner and prepare destination permissions; do not apply all clients to one certificate.

# List issued names and key types as the acme.sh owner
acme.sh --list

The next step assumes an issued ECC certificate. Complete the appropriate HTTP/DNS challenge first if absent; install-cert is not issue.

# Install the issued certificate at stable service paths
acme.sh --install-cert -d example.com --ecc \
  --key-file /etc/ssl/example/key.pem \
  --fullchain-file /etc/ssl/example/fullchain.pem \
  --reloadcmd 'systemctl reload caddy'

Use this reload command only for manually managed Caddy certificates and an account permitted to write/reload. Omit --ecc for RSA. Verify permissions, fingerprint, reload logs, and live TLS.

Certbot: inspect existing certificates or issue first

# List Certbot certificate names, domains, and paths
certbot certificates

Cert Name need not equal a domain. Use the listed name for --cert-name and inspect plugins separately.

For first-time webroot issuance, an existing HTTP server must expose /.well-known/acme-challenge/ from /var/www/acme on public port 80, with correct A/AAAA. These steps do not apply to Caddy automatic HTTPS.

# Test the HTTP-01 path against the staging CA
sudo certbot certonly --webroot -w /var/www/acme -d example.com --dry-run

-w is the server webroot and -d the requested name. Follow account prompts. Dry-run creates no deployable production certificate; fix path/DNS failures first.

# Issue after testing, then configure the service separately
sudo certbot certonly --webroot -w /var/www/acme -d example.com

certonly does not install server configuration. Inspect actual paths, configure/validate/reload the service, then verify TLS from a client.

# Test existing renewal configuration and challenges
sudo certbot renew --dry-run

Success does not prove deployment. Verify scheduling and deploy hooks; dry-run hook execution depends on options such as --run-deploy-hooks and version behavior.

The acme.sh example assumes an issued ECC certificate, prepared paths/permissions and a valid deployment reload command; omit --ecc for RSA. Serve the stable install-cert paths, not internal working files. Choose one renewal owner; verify installed Certbot DNS plugins and scoped credentials.

Caddy automatic HTTPS normally owns issuance/renewal; see Caddy. Use external renewal when manual certificates are intentional. Diagnose challenge paths, delegation, TXT and authorization before retrying; use staging/dry-run for tests. Cloud load balancers additionally need certificate binding.

Create a SAN request and compare public keys

New key and CSR: protect output first

# Restrict new private-key file permissions in this shell
umask 077

Use a controlled directory with unused filenames; do not overwrite an existing server.key.

# Create a private key and CSR with the intended SANs
openssl req -new -newkey rsa:3072 -noenc \
  -keyout server.key -out server.csr \
  -subj '/CN=example.com' \
  -addext 'subjectAltName=DNS:example.com,DNS:www.example.com'

OpenSSL 3 -noenc leaves the key unencrypted; use only with planned filesystem/deployment protection. Omit it for encrypted storage and plan unlocking. SAN lists actual identities; CN is not a substitute.

# Check the CSR signature, SANs, and public-key parameters
openssl req -in server.csr -noout -verify -text

-verify checks the CSR signature, not CA issuance or acceptance of every SAN. Submit only the CSR; retain the private key.

Compare public-key hashes after issuance

# Hash the public key derived from the private key
openssl pkey -in server.key -pubout -outform DER | openssl dgst -sha256

Only the public-key hash is shown; compare with the same DER encoding below.

# Hash the public key in the issued certificate
openssl x509 -in server.crt -pubkey -noout |
  openssl pkey -pubin -outform DER | openssl dgst -sha256

server.crt is the issued leaf. Matching hashes prove key pairing, not SAN, validity, chain, or client trust.

OpenSSL 3 example. -noenc leaves the private key unencrypted, requiring protected storage. A CSR contains no private key. Compare public-key digests after issuance; equality is not trust. A self-signed localhost certificate needs appropriate SANs and explicit trust. Key compromise requires replacement and CA revocation, not just chmod.

OpenSSL s_client · OpenSSL x509 · acme.sh installation · Certbot

Deployment runbook: WildWind CLB certificate renewal and binding.