Operations 25 min read

How Dangerous Is an HTTPS Certificate Expiration and How Ops Can Prevent It?

When an HTTPS certificate expires, browsers show warnings, users abandon sites, services become unavailable, and security is weakened, so this article explains the TLS fundamentals, the risks of expiration, real‑world outage cases, and provides step‑by‑step guidance on acquisition, deployment, automated renewal, monitoring, and best‑practice procedures for reliable certificate management.

Ops Community
Ops Community
Ops Community
How Dangerous Is an HTTPS Certificate Expiration and How Ops Can Prevent It?

Background and Problem

HTTPS certificate expiration can trigger browser security warnings, cause users to abandon the site, and in some cases make the service completely unavailable.

HTTPS Basics

Encryption Principle

HTTPS secures HTTP traffic with TLS. TLS 1.3 (released 2018) provides performance and security improvements over TLS 1.2.

Certificate Structure

Subject (domain, organization)

Issuer (CA information)

Public key

Validity period (Not Before / Not After)

Serial number

Signature

# View certificate details
openssl x509 -in certificate.crt -text -noout

# Example output (truncated)
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: 04:cd:3a:5b:f2:7c:8a:3e:9d:5e:8a:3c:1d:4e:8a:2b:3c
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C = US, O = Let's Encrypt, CN = R3
        Validity
            Not Before: Jan 15 00:00:00 2026 GMT
            Not After : Apr 15 00:00:00 2026 GMT
        Subject: CN = example.com
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
            RSA Public-Key: (2048 bit)

Certificate Chain

Browsers verify the end‑entity certificate, any intermediate certificates, and a trusted root certificate.

# Verify chain locally
openssl verify -CAfile chain.crt certificate.crt

# Show full chain from a remote server
openssl s_client -connect example.com:443 -showcerts

Risks of Expiration

Service Unavailability

Browsers display a security warning page.

Users must manually confirm; most close the page.

Mobile app APIs fail, breaking app functionality.

Data Security Risks

Expired certificates no longer provide full encryption.

Man‑in‑the‑middle attack surface increases.

Compliance frameworks such as PCI DSS require valid certificates.

Operational Pressure

Expiration often occurs outside working hours, generating night‑time alerts.

Emergency roll‑backs must be performed under time pressure.

Manual renewal can be error‑prone when rushed.

Real‑World Outage Example

A commerce platform received massive user complaints at 03:00 AM because its payment API handshake failed due to an expired certificate. The outage lasted two hours and caused losses exceeding one million dollars. The root cause was a manual renewal process without monitoring alerts.

Certificate Management Basics

Acquisition Methods

Commercial CAs (DigiCert, GlobalSign, Entrust) – high trust.

Let’s Encrypt – free, 90‑day certificates, fully automated.

Private CA – for internal services.

Let’s Encrypt Certificate Request

Install certbot

# Ubuntu/Debian
sudo apt update
sudo apt install certbot python3-certbot-nginx

# CentOS/RHEL
sudo dnf install certbot python3-certbot-nginx

# Verify version
certbot --version   # certbot 3.0.1

Request a Certificate

# Single domain
sudo certbot certonly --nginx -d example.com

# Multiple domains
sudo certbot certonly --nginx -d example.com -d www.example.com

# Webroot mode (requires pre‑configured webroot)
sudo certbot certonly --webroot -w /var/www/html -d example.com -d www.example.com

# DNS‑01 challenge (for environments where HTTP validation is impossible)
sudo certbot certonly --manual --preferred-challenges dns -d example.com

Certificate Locations

# List files in the live directory
ls -la /etc/letsencrypt/live/example.com/
# fullchain.pem – server + intermediate
# privkey.pem   – private key
# cert.pem      – server certificate
# chain.pem     – intermediate certificate

Deployment

Nginx Configuration

server {
    listen 443 ssl http2;
    server_name example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;
    ssl_stapling on;
    ssl_stapling_verify on;
    resolver 8.8.8.8 8.8.4.4 valid=300s;
    resolver_timeout 5s;
}

# Redirect HTTP to HTTPS
server {
    listen 80;
    server_name example.com;
    return 301 https://$host$request_uri;
}

Apache Configuration

<VirtualHost *:443>
    ServerName example.com
    SSLEngine on
    SSLCertificateFile /etc/letsencrypt/live/example.com/cert.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
    SSLCertificateChainFile /etc/letsencrypt/live/example.com/chain.pem
    SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
    SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256
</VirtualHost>

Verification

# Check dates
openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem -noout -dates

# Verify chain
openssl verify /etc/letsencrypt/live/example.com/fullchain.pem

# Remote check
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -dates -issuer -subject

Renewal

Let’s Encrypt certificates are valid for 90 days and must be renewed regularly.

# Renew all certificates
sudo certbot renew

# Renew a specific certificate
sudo certbot renew --cert-name example.com

# Dry‑run (no changes)
sudo certbot renew --dry-run

# Force renewal ignoring expiry
sudo certbot renew --force-renewal

Automated Certificate Management

certbot Auto‑Renew Mechanism

After installation certbot creates a systemd timer that runs twice daily.

# List timers
sudo systemctl list-timers | grep certbot

# View cron file (fallback)
sudo cat /etc/cron.d/certbot
# 0 */12 * * * root test -x /usr/bin/certbot -a ! -d /run/systemd/system && perl -e 'sleep int(rand(43200))' && certbot -q renew

# Check systemd timer status
sudo systemctl status certbot-renew.timer

Nginx Auto‑Deployment

The certbot nginx plugin can automatically modify the Nginx configuration during issuance.

# Issue and configure
sudo certbot --nginx -d example.com -d www.example.com
# The plugin performs:
# 1. Domain validation
# 2. Certificate issuance
# 3. Nginx config update
# 4. Renewal hook setup

Post‑Renew Reload

After renewal the web server must be reloaded.

# View renewal hook
sudo cat /etc/letsencrypt/renewal/example.com.conf

# Example snippet
[renewalparams]
account = xxxxxxxx
authenticator = nginx
installer = nginx
server = https://acme-v02.api.letsencrypt.org/directory
post_hook = systemctl reload nginx

Full Automation Script Example

#!/bin/bash
LOGFILE="/var/log/cert-renewal.log"
CERT_DIR="/etc/letsencrypt/live"

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a $LOGFILE
}

log "Starting certificate renewal check..."
sudo certbot renew --quiet --deploy-hook "systemctl reload nginx" 2>&1 | tee -a $LOGFILE

if [ $? -eq 0 ]; then
    log "Certificate renewal completed successfully"
    for cert in $(ls $CERT_DIR); do
        expiry=$(sudo openssl x509 -in $CERT_DIR/$cert/cert.pem -noout -enddate 2>/dev/null | cut -d= -f2)
        log "Certificate $cert expires: $expiry"
    done
else
    log "ERROR: Certificate renewal failed"
    # Integration with alerting (e.g., email, Slack) can be added here
fi

Certificate Monitoring Solutions

Expiry Monitoring Scripts

Local Script

#!/bin/bash
ALERT_DAYS=30
CERT_FILE="/etc/letsencrypt/live/example.com/cert.pem"
ALERT_EMAIL="[email protected]"

EXPIRY_DATE=$(sudo openssl x509 -in $CERT_FILE -noout -enddate | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s)
NOW_EPOCH=$(date +%s)
DAYS_UNTIL_EXPIRY=$(( (EXPIRY_EPOCH - NOW_EPOCH) / 86400 ))

echo "Certificate expires in $DAYS_UNTIL_EXPIRY days (on $EXPIRY_DATE)"
if [ $DAYS_UNTIL_EXPIRY -le $ALERT_DAYS ]; then
    echo "WARNING: Certificate will expire soon!" | mail -s "Certificate Expiry Alert" $ALERT_EMAIL
    exit 1
fi

Remote Check with OpenSSL

#!/bin/bash
DOMAIN=$1
ALERT_DAYS=${2:-30}

EXPIRY_DATE=$(echo | openssl s_client -servername $DOMAIN -connect $DOMAIN:443 2>/dev/null | openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
if [ -z "$EXPIRY_DATE" ]; then
    echo "ERROR: Cannot get certificate for $DOMAIN" >&2
    exit 1
fi
EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s)
NOW_EPOCH=$(date +%s)
DAYS_UNTIL_EXPIRY=$(( (EXPIRY_EPOCH - NOW_EPOCH) / 86400 ))

echo "$DOMAIN: expires in $DAYS_UNTIL_EXPIRY days (on $EXPIRY_DATE)"
if [ $DAYS_UNTIL_EXPIRY -le $ALERT_DAYS ]; then
    echo "ALERT: $DOMAIN certificate will expire in $DAYS_UNTIL_EXPIRY days" >&2
    exit 1
fi

Prometheus Monitoring

Use an SSL exporter to expose certificate expiry metrics.

# prometheus.yml snippet
scrape_configs:
  - job_name: 'ssl-certificate-exporter'
    static_configs:
      - targets:
        - example.com:443
        - api.example.com:443
    metrics_path: /probe
    params:
      module: [https_02]
    relabel_configs:
      - source_labels: [__address__]
        target_label: __param_target
      - source_labels: [__param_target]
        target_label: instance
      - target_label: __address__
        replacement: ssl-certificate-exporter:9119

Run the exporter:

docker run -d \
  --name ssl_exporter \
  -p 9119:9119 \
  prom/blackbox_exporter:latest

Alerting rules (expiring soon and expired):

groups:
  - name: certificate-expiry
    rules:
      - alert: CertificateExpiringSoon
        expr: ssl_cert_expires_in_seconds < 86400*30
        for: 1h
        labels:
          severity: warning
        annotations:
          summary: "Certificate {{ $labels.instance }} expiring in less than 30 days"
          description: "Certificate expires on {{ $value | humanizeDuration }}"
      - alert: CertificateExpired
        expr: ssl_cert_expires_in_seconds < 0
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "Certificate {{ $labels.instance }} has expired"
          description: "Certificate expired on {{ $labels.instance }}"

Grafana Dashboard

Key panels display remaining days, percentage of validity, countdown, and domain‑grouped lists.

{
  "panels": [
    {
      "title": "Certificate Expiry",
      "type": "stat",
      "targets": [{"expr": "ssl_cert_expires_in_seconds / 86400", "legendFormat": "{{ instance }}"}],
      "fieldConfig": {"defaults": {"thresholds": {"mode": "absolute", "steps": [{"value": 0, "color": "red"}, {"value": 7, "color": "orange"}, {"value": 30, "color": "green"}]}}}
    }
  ]
}

Cloud Provider Monitoring

AWS Certificate Manager – notifications 30/45/60/90 days before expiry.

Alibaba Cloud SSL – expiry reminders.

Tencent Cloud SSL – automatic renewal.

Certificate Management Tools

acme.sh

# Install acme.sh
curl https://get.acme.sh | sh -s [email protected]

# Issue a certificate
.acme.sh/acme.sh --issue -d example.com -d www.example.com --nginx /etc/nginx/nginx.conf

# Install the certificate and reload Nginx
.acme.sh/acme.sh --install-cert -d example.com \
  --key-file /etc/nginx/ssl/key.pem \
  --fullchain-file /etc/nginx/ssl/fullchain.pem \
  --reloadcmd "systemctl reload nginx"

Vault PKI

# Enable PKI secrets engine
vault secrets enable pki

# Generate a root CA
vault write pki/root/generate/internal common_name="example.com Internal CA" ttl=87600h

# Create a role for example.com
vault write pki/roles/example-dot-com allowed_domains="example.com" allow_subdomains=true max_ttl=72h

# Issue a certificate
vault read pki/issue/example-dot-com common_name="app.example.com"

cert‑manager (Kubernetes)

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: [email protected]
    privateKeySecretRef:
      name: letsencrypt-prod-account-key
    solvers:
    - http01:
        ingress:
          class: nginx
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: example-com-tls
  namespace: production
spec:
  secretName: example-com-tls
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  dnsNames:
  - example.com
  - www.example.com
  duration: 2160h   # 90 days
  renewBefore: 360h   # renew 15 days early

Private Certificate Authority

Build a Private CA

# Generate CA private key
openssl genrsa -aes256 -out ca.key 4096

# Create self‑signed CA certificate (10 years)
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 \
  -out ca.crt -subj "/CN=Internal Root CA/O=Example Inc"

# Distribute CA cert to servers
sudo cp ca.crt /usr/local/share/ca-certificates/internal-ca.crt
sudo update-ca-certificates

Issue Internal Certificates

# Generate service key
openssl genrsa -out internal.key 2048

# Create CSR
openssl req -new -key internal.key -out internal.csr -subj "/CN=internal.example.com/O=Example Inc"

# Sign with private CA
openssl x509 -req -in internal.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out internal.crt -days 365 -sha256

# Verify
openssl verify -CAfile ca.crt internal.crt

Common Issue Handling

Incomplete Certificate Chain

# Wrong: use cert.pem only
ssl_certificate /etc/letsencrypt/live/example.com/cert.pem;
# Correct: use fullchain.pem
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;

Private Key Permissions

# Ensure 600 permissions and root ownership
sudo chmod 600 /etc/letsencrypt/live/example.com/privkey.pem
sudo chown root:root /etc/letsencrypt/live/example.com/privkey.pem

OCSP Stapling Issues

# Disable temporarily if verification fails
ssl_stapling off;
ssl_stapling_verify off;

Renewal Failures

# Dry‑run to see the error
sudo certbot renew --dry-run

# Typical causes and fixes:
# 1. Web server not running – start Nginx
sudo systemctl start nginx

# 2. DNS resolution problems – verify with dig
dig example.com

# 3. Port 80 occupied – free the port or switch to DNS‑01 challenge

# 4. Let's Encrypt rate limits – wait or use --staging for testing
sudo certbot renew --staging

Best Practices

Standardize on Let’s Encrypt unless a special requirement exists.

Set certificate validity to 90 days and schedule renewal 30 days before expiry.

Store certificates under /etc/letsencrypt/live/{domain}/ and keep private keys at permission 600 owned by root.

Configure dual alerts (30 days and 7 days before expiry) that include domain, remaining days, expiry date, and remediation steps.

Automate every step – issuance, deployment, renewal, and post‑renew reload.

Maintain audit logs of certificate actions and perform periodic disaster‑recovery drills.

Prepare a manual renewal playbook and keep backup copies of historic certificates for quick rollback.

Conclusion

HTTPS certificate management is a critical operational responsibility. Expiration can cause user‑facing errors, service outages, and security gaps. Understanding TLS fundamentals, automating acquisition and renewal with tools such as certbot, acme.sh, or cert‑manager, and implementing proactive monitoring and alerting are essential to keep services reliable and secure.

MonitoringAutomationoperationsTLSHTTPScertificate expiration
Ops Community
Written by

Ops Community

A leading IT operations community where professionals share and grow together.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.