SSL/TLS Best Practices: HTTPS Configuration and Certificate Management

Share

TL;DR

The #1 SSL/TLS best practice is enforcing TLS 1.2+ only (disable TLS 1.0/1.1). Get free certificates from Let's Encrypt with auto-renewal. Enable HSTS to force HTTPS. Use modern cipher suites and test your configuration with SSL Labs. Certificate expiration is the most common cause of HTTPS outages.

"HTTPS is not optional. An expired certificate is worse than no certificate at all-it tells users you stopped caring about their security."

Best Practice 1: Use TLS 1.2 or Higher 3 min

Older TLS versions have known vulnerabilities:

VersionStatusRecommendation
TLS 1.3CurrentEnable (preferred)
TLS 1.2AcceptableEnable (compatibility)
TLS 1.1DeprecatedDisable
TLS 1.0DeprecatedDisable
SSL 3.0InsecureDisable
Nginx TLS configuration
# /etc/nginx/conf.d/ssl.conf

# Only allow TLS 1.2 and 1.3
ssl_protocols TLSv1.2 TLSv1.3;

# Modern cipher suites
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;

# Prefer server ciphers
ssl_prefer_server_ciphers off;  # Let client choose for TLS 1.3

# Session settings
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;

# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;

Best Practice 2: Automate Certificate Management 5 min

Certificate expiration causes outages. Automate renewal:

Let's Encrypt with Certbot
# Install Certbot
sudo apt install certbot python3-certbot-nginx

# Get certificate with auto-configuration
sudo certbot --nginx -d example.com -d www.example.com

# Auto-renewal is enabled by default
# Check with:
sudo systemctl status certbot.timer

# Test renewal
sudo certbot renew --dry-run

# Manual renewal (if needed)
sudo certbot renew
AWS Certificate Manager (ACM)
# Terraform: ACM certificate with auto-renewal
resource "aws_acm_certificate" "main" {
  domain_name               = "example.com"
  subject_alternative_names = ["*.example.com"]
  validation_method         = "DNS"

  lifecycle {
    create_before_destroy = true
  }
}

# DNS validation record
resource "aws_route53_record" "cert_validation" {
  for_each = {
    for dvo in aws_acm_certificate.main.domain_validation_options : dvo.domain_name => {
      name   = dvo.resource_record_name
      record = dvo.resource_record_value
      type   = dvo.resource_record_type
    }
  }

  zone_id = aws_route53_zone.main.zone_id
  name    = each.value.name
  type    = each.value.type
  records = [each.value.record]
  ttl     = 60
}

Best Practice 3: Enable HSTS 2 min

HTTP Strict Transport Security prevents downgrade attacks:

HSTS configuration
# Nginx
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

# Apache
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"

# Express.js with Helmet
import helmet from 'helmet';

app.use(helmet.hsts({
  maxAge: 31536000,       // 1 year
  includeSubDomains: true,
  preload: true,
}));

# Vercel (vercel.json)
{
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        {
          "key": "Strict-Transport-Security",
          "value": "max-age=31536000; includeSubDomains; preload"
        }
      ]
    }
  ]
}

HSTS Preload Warning: Before enabling the preload directive, ensure all subdomains support HTTPS. Once your domain is in the preload list, HTTP access is permanently blocked in browsers. This cannot be easily undone.

Best Practice 4: Redirect HTTP to HTTPS 2 min

Force all traffic to use HTTPS:

HTTP to HTTPS redirect
# Nginx
server {
  listen 80;
  listen [::]:80;
  server_name example.com www.example.com;

  # Redirect all HTTP to HTTPS
  return 301 https://$server_name$request_uri;
}

# Apache
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

# Express.js middleware
function requireHTTPS(req, res, next) {
  if (req.headers['x-forwarded-proto'] !== 'https' &&
      process.env.NODE_ENV === 'production') {
    return res.redirect(301, `https://${req.hostname}${req.url}`);
  }
  next();
}

app.use(requireHTTPS);

Best Practice 5: Monitor Certificate Expiration 10 min

Set up alerts before certificates expire:

Certificate expiration monitoring
#!/bin/bash
# Check certificate expiration

DOMAIN="example.com"
WARN_DAYS=30

EXPIRY=$(echo | openssl s_client -servername $DOMAIN -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 $WARN_DAYS ]; then
  echo "WARNING: Certificate for $DOMAIN expires in $DAYS_LEFT days"
  # Send alert
  curl -X POST "https://hooks.slack.com/..." \
    -d "{\"text\": \"SSL cert for $DOMAIN expires in $DAYS_LEFT days\"}"
fi

# Prometheus/Grafana approach
# Use blackbox exporter to monitor SSL
- job_name: 'ssl'
  metrics_path: /probe
  params:
    module: [http_2xx]
  static_configs:
    - targets:
      - https://example.com
  relabel_configs:
    - source_labels: [__address__]
      target_label: __param_target
    - target_label: __address__
      replacement: blackbox-exporter:9115

Best Practice 6: Test Your Configuration 5 min

Regularly verify your SSL/TLS setup:

  • Test with SSL Labs (aim for A+ rating)
  • Check for certificate chain issues
  • Verify all subdomains have valid certs
  • Test mixed content (HTTP resources on HTTPS pages)
  • Verify HSTS is working correctly
Command-line SSL testing
# Check certificate details
openssl s_client -connect example.com:443 -servername example.com

# Check TLS versions supported
nmap --script ssl-enum-ciphers -p 443 example.com

# Check certificate chain
openssl s_client -connect example.com:443 -showcerts

# Test specific TLS version
openssl s_client -connect example.com:443 -tls1_2
openssl s_client -connect example.com:443 -tls1_3

# Check for common vulnerabilities
testssl.sh example.com

External Resources: For comprehensive SSL/TLS guidance, see the Let's Encrypt Documentation for free certificate automation and the OWASP Transport Layer Security Cheat Sheet for industry-standard TLS configuration recommendations.

Should I use a wildcard certificate?

Wildcard certs (*.example.com) are convenient but require careful key protection since one compromised key affects all subdomains. Use them for internal services or when you have many subdomains. For public-facing sites, individual certs are often safer.

Do I need an EV (Extended Validation) certificate?

No. Browsers no longer display EV certificates differently from standard DV (Domain Validation) certificates. A free Let's Encrypt certificate provides the same encryption. EV only matters if you need organizational identity verification for compliance.

How do I handle certificate rotation?

For auto-renewed certs (Let's Encrypt, ACM), rotation is automatic. For manual certs, deploy new certs before old ones expire, test, then remove old certs. Use certificate pinning only when absolutely necessary, as it makes rotation harder.

Test Your SSL Configuration

Check your TLS settings and certificate health.

Start Free Scan
Best Practices

SSL/TLS Best Practices: HTTPS Configuration and Certificate Management