Certificate Management


Introduction

TLS certificate management is a critical operational responsibility. Expired certificates cause service outages, security warnings, and loss of user trust. Modern certificate management leverages the ACME protocol and Let's Encrypt to automate issuance and renewal at scale.

Let's Encrypt

Let's Encrypt is a free, automated, and open certificate authority (CA) that provides DV certificates trusted by all major browsers.




# Install Certbot (Let's Encrypt client)


sudo apt install certbot python3-certbot-nginx




# Obtain certificate with webroot authentication


sudo certbot certonly --webroot \


-w /var/www/example.com -d example.com \


-w /var/www/api.example.com -d api.example.com \


--email admin@example.com \


--agree-tos \


--non-interactive




# Obtain certificate with DNS challenge (for wildcards)


sudo certbot certonly --manual \


--preferred-challenges dns \


-d example.com -d *.example.com





ACME Protocol

The Automated Certificate Management Environment (ACME) protocol automates certificate issuance, renewal, and revocation.




import josepy as jose


from acme import client, messages


from cryptography import x509


from cryptography.hazmat.primitives import hashes




class ACMEClient:


def __init__(self, directory_url, email):


self.directory_url = directory_url


self.email = email


self.net = client.ClientNetwork(


jose.JWKRSA(key=rsa_private_key),


user_agent="my-acme-client/1.0"


)


self.directory = messages.Directory.from_json(


self.net.get(directory_url).json()


)


self.acme = client.ClientV2(self.directory, self.net)




def register_account(self):


"""Register ACME account with Let's Encrypt."""


terms = self.directory.meta.terms_of_service


registration = self.acme.new_account(


messages.NewRegistration(


key=self.net.key,


terms_of_service_agreed=True,


contact=[f"mailto:{self.email}"]


)


)


return registration




def request_certificate(self, domain, csr_pem):


"""Request certificate issuance."""


# Create authorization


order = self.acme.new_order(csr_pem)




# Complete challenges for each identifier


for auth in order.authorizations:


# HTTP-01 or DNS-01 challenge


challenge = auth.body.challenges[0]


self.respond_challenge(challenge)




# Finalize order


order = self.acme.finalize_order(order, csr_pem)


return order.fullchain_pem





Automated Renewal

Certificate renewal should be fully automated with monitoring and alerting.




# Certbot systemd timer for automatic renewal


# /etc/systemd/system/certbot-renewal.service


[Unit]


Description=Certbot Renewal




[Service]


Type=oneshot


ExecStart=/usr/bin/certbot renew --quiet --pre-hook "systemctl reload nginx"


ExecStartPost=/usr/bin/systemctl reload nginx




# /etc/systemd/system/certbot-renewal.timer


[Unit]


Description=Run certbot renewal twice daily




[Timer]


OnCalendar=*-*-* 00:00,12:00


RandomizedDelaySec=3600


Persistent=true




[Install]


WantedBy=timers.target








# Enable timer


sudo systemctl enable certbot-renewal.timer


sudo systemctl start certbot-renewal.timer




# Test renewal process


sudo certbot renew --dry-run





Certificate Monitoring

Monitor certificate expiration to catch renewal failures before they cause outages.




import ssl


import datetime


import socket


from typing import Dict, List




class CertificateMonitor:


def __init__(self, warning_days: int = 30, critical_days: int = 7):


self.warning_days = warning_days


self.critical_days = critical_days




def check_certificate(self, hostname: str, port: int = 443) -> Dict:


context = ssl.create_default_context()


context.check_hostname = True




with socket.create_connection((hostname, port), timeout=10) as sock:


with context.wrap_socket(sock, server_hostname=hostname) as ssock:


cert = ssock.getpeercert()




# Parse expiration


expires = datetime.datetime.strptime(


cert['notAfter'], '%b %d %H:%M:%S %Y %Z'


)


remaining = (expires - datetime.datetime.utcnow()).days




# Check issuer


issuer = dict(x[0] for x in cert['issuer'])




# Check SANs


sans = [san[1] for san in cert.get('subjectAltName', [])]




return {


'hostname': hostname,


'subject': cert['subject'],


'issuer': issuer.get('organizationName', 'Unknown'),


'expires': expires.isoformat(),


'remaining_days': remaining,


'sans': sans,


'serial': cert.get('serialNumber'),


'status': self._get_status(remaining),


}




def _get_status(self, remaining_days: int) -> str:


if remaining_days <= 0:


return 'EXPIRED'


elif remaining_days <= self.critical_days:


return 'CRITICAL'


elif remaining_days <= self.warning_days:


return 'WARNING'


return 'OK'




def monitor_domains(self, domains: List[str]) -> List[Dict]:


results = []


for domain in domains:


try:


result = self.check_certificate(domain)


results.append(result)




if result['status'] in ('CRITICAL', 'EXPIRED'):


self.alert(result)


except Exception as e:


results.append({


'hostname': domain,


'status': 'ERROR',


'error': str(e)


})


return results








# OpenSSL-based certificate check


echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | \


openssl x509 -noout -enddate -subject -issuer




# Mass certificate check


for domain in $(cat domains.txt); do


expires=$(echo | openssl s_client -connect $domain:443 -servername $domain 2>/dev/null | \


openssl x509 -noout -enddate | cut -d= -f2)


remaining=$(( ($(date -d "$expires" +%s) - $(date +%s)) / 86400 ))


echo "$domain: expires in $remaining days ($expires)"


done





Certificate Revocation




# Check OCSP status


openssl ocsp -issuer chain.pem -cert cert.pem \


-url $(openssl x509 -in cert.pem -noout -ocsp_uri) \


-header "Host" $(openssl x509 -in cert.pem -noout -ocsp_uri | cut -d/ -f3) \


-CAfile root.pem




# CRL check


curl -O http://crl.example.com/intermediate.crl


openssl crl -in intermediate.crl -noout -text | grep -A1 "Serial Number"





Conclusion

Modern certificate management means automation. Use Let's Encrypt with Certbot for domain-validated certificates, implement ACME for custom automation, set up systemd timers for renewal, and deploy monitoring that alerts days or weeks before expiration. Never rely on manual renewal processes — they fail under pressure and at scale.