LUNAROPS · OPERATIONAL UPLINK 100% UPTIME 1,247d POSTS 893 JEFF.MOON@LUNAROPS.DEV UTC --:--:--

Managing SSL Certificates

ssltlscertificatesletsencrypt

SSL/TLS certificates enable HTTPS. Here’s everything you need to know about managing them.

Certificate Types

Domain Validated (DV)

  • Verifies domain ownership only
  • Cheapest/free (Let’s Encrypt)
  • Good for most sites

Organization Validated (OV)

  • Verifies organization exists
  • More trust indicators
  • Good for businesses

Extended Validation (EV)

  • Extensive verification
  • Green bar (historically)
  • Expensive, decreasing value

Certificate Anatomy

-----BEGIN CERTIFICATE-----
MIIFazCCBFOgAwIBAgISA...
-----END CERTIFICATE-----

Contains:

  • Subject (your domain)
  • Issuer (who signed it)
  • Validity period
  • Public key
  • Signature

Let’s Encrypt with Certbot

Free, automated certificates:

Installation

1
2
3
4
5
# Ubuntu/Debian
apt install certbot

# With nginx plugin
apt install python3-certbot-nginx

Get Certificate

1
2
3
4
5
6
7
8
# Standalone (stops web server)
certbot certonly --standalone -d example.com -d www.example.com

# Webroot (no downtime)
certbot certonly --webroot -w /var/www/html -d example.com

# Nginx plugin (automatic config)
certbot --nginx -d example.com -d www.example.com

Auto-Renewal

1
2
3
4
5
# Test renewal
certbot renew --dry-run

# Cron job (usually auto-installed)
0 0,12 * * * certbot renew --quiet

Certificate Files

Let’s Encrypt creates:

/etc/letsencrypt/live/example.com/
├── cert.pem        # Your certificate
├── chain.pem       # Intermediate certificates
├── fullchain.pem   # cert.pem + chain.pem (use this)
└── privkey.pem     # Private key (keep secret!)

Nginx Configuration

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
server {
    listen 443 ssl;
    http2 on;
    server_name example.com;

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

    # Strong SSL settings
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;

    # OCSP stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
}

Wildcard Certificates

Cover all subdomains:

1
2
3
# Requires DNS challenge
certbot certonly --manual --preferred-challenges dns \
    -d example.com -d *.example.com

You’ll need to add a TXT record to your DNS.

Automated with DNS Provider

1
2
3
4
# Cloudflare example
certbot certonly --dns-cloudflare \
    --dns-cloudflare-credentials ~/.cloudflare.ini \
    -d example.com -d *.example.com

Checking Certificates

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# View certificate details
openssl x509 -in cert.pem -text -noout

# Check expiration
openssl x509 -in cert.pem -noout -dates

# Check remote server
echo | openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -dates

# Test SSL configuration
# https://www.ssllabs.com/ssltest/

Common Issues

Certificate Chain Incomplete

SSL_ERROR_UNKNOWN_CA_ALERT

Fix: Use fullchain.pem, not just cert.pem.

Certificate Expired

1
2
3
4
5
# Check expiry
openssl x509 -in cert.pem -noout -enddate

# Force renewal
certbot renew --force-renewal

Key Mismatch

1
2
3
4
# Verify key matches certificate
openssl x509 -noout -modulus -in cert.pem | md5sum
openssl rsa -noout -modulus -in key.pem | md5sum
# Should match

Monitoring Expiration

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#!/bin/bash
DOMAIN="example.com"
DAYS=30

expiry=$(echo | openssl s_client -connect $DOMAIN:443 2>/dev/null | \
         openssl x509 -noout -enddate | cut -d= -f2)
expiry_epoch=$(date -d "$expiry" +%s)
now_epoch=$(date +%s)
days_left=$(( (expiry_epoch - now_epoch) / 86400 ))

if [ $days_left -lt $DAYS ]; then
    echo "Certificate expires in $days_left days!"
    # Send alert
fi

Docker with SSL

1
2
3
4
5
6
7
8
services:
  nginx:
    image: nginx
    ports:
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - /etc/letsencrypt:/etc/letsencrypt:ro

Summary

  1. Use Let’s Encrypt (free, automated)
  2. Enable auto-renewal
  3. Use fullchain.pem for the certificate
  4. Monitor expiration
  5. Test your SSL configuration

SSL certificates used to be complex and expensive. Now there’s no excuse not to use HTTPS everywhere.

Comments