SSL/TLS Best Practices: HTTPS Configuration and Certificate Management

TL;DR

The #1 SSL/TLS best practice is enforcing TLS 1.2+ only, which means turning off TLS 1.0 and 1.1. Certificates are free from Let's Encrypt and renew themselves, so there's no excuse left for a manual one. Turn on HSTS, use modern cipher suites, and check the result against SSL Labs. Then set an expiry alert: a lapsed certificate takes more sites down than any attack does.

"HTTPS isn't optional. An expired certificate is worse than no certificate at all, because it tells users you stopped paying attention."

Best Practice 1: Use TLS 1.2 or Higher 3 min

Old TLS versions aren't merely dated, they're broken, and a client that offers one will happily use it. Your server decides what's allowed.

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

Almost every HTTPS outage is the same outage: somebody meant to renew and didn't. Automate it once and the problem stops existing.

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

A redirect still lets the first request go out over plain HTTP, which is the request an attacker on the same network wants. HSTS tells the browser never to try that again.

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

HSTS only helps visitors who've already been to your site once. The redirect is what catches everyone else.

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

Auto-renewal fails quietly. A DNS change, a moved webroot, a rate limit, and the timer keeps running while nothing renews. Alert on the certificate itself, not on the renewal job.

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

Your config file says what you intended. Only a test says what you deployed.

  • 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.

Further Reading

Put these practices into action with our step-by-step guides.

Test Your SSL Configuration

Check your TLS settings and certificate health.

Best Practices

SSL/TLS Best Practices: HTTPS Configuration and Certificate Management