NC Logo UseToolSuite
Web Security

SSL/TLS Certificates: What Developers Should Know

A developer-focused guide to SSL/TLS certificates — how they work, how to set them up, how to debug common issues, and why your staging environment keeps showing certificate warnings.

Necmeddin Cunedioglu Necmeddin Cunedioglu

Practice what you learn

SSL Certificate Checker

Try it free →

Last month, a production site went down because the SSL certificate had expired. Not because nobody knew it was expiring — the renewal email had been going to an engineer who’d left the company six months earlier. The certificate had been manually provisioned, there was no monitoring on the expiry date, and the first sign of trouble was customers seeing “Your connection is not private” in their browsers.

This kind of incident is entirely preventable. But preventing it requires understanding how certificates actually work, not just knowing how to click “Enable SSL” in your hosting dashboard.

How TLS Works (The 60-Second Version)

When your browser connects to https://example.com, a TLS handshake happens before any data is exchanged:

1. Browser → Server: "Hello, I support TLS 1.3, these cipher suites"
2. Server → Browser: "Let's use TLS 1.3 with AES-256-GCM. Here's my certificate"
3. Browser validates the certificate:
   - Is it expired?
   - Does the domain match?
   - Was it issued by a trusted CA?
   - Is the certificate chain complete?
4. Browser and server agree on encryption keys
5. Encrypted communication begins

The certificate is what makes step 3 possible. It’s the server’s identity proof — like a digital passport issued by a trusted authority.

Anatomy of a Certificate

A certificate contains:

Subject: CN=example.com
Subject Alternative Names: example.com, www.example.com, api.example.com
Issuer: Let's Encrypt Authority X3
Valid From: 2026-01-15
Valid To: 2026-04-15
Public Key: RSA 2048 bits
Signature Algorithm: SHA-256 with RSA
Serial Number: 04:a3:b2:...

The important fields:

FieldWhy It Matters
Subject / SANMust match the domain the browser is connecting to
IssuerMust be a Certificate Authority the browser trusts
Valid From / ToThe certificate only works within this date range
Public KeyUsed for the initial key exchange
Signature AlgorithmSHA-1 is deprecated; must be SHA-256 or better

Inspect certificate fields: Our SSL Certificate Checker helps you analyze certificate configurations, verify fields, and understand the chain of trust — all in your browser.

Certificate Types

Domain Validation (DV)

The CA verifies you control the domain (via DNS record or HTTP challenge). This is what Let’s Encrypt provides. Sufficient for most websites and APIs.

Validation: Automated, takes seconds Cost: Free (Let’s Encrypt) to ~$10/year Browser indicator: Padlock icon

Organization Validation (OV)

The CA verifies the domain and the organization’s identity (business registration, phone verification). Shows organization name in certificate details.

Validation: 1-3 business days Cost: $50-200/year Browser indicator: Padlock icon (same as DV visually)

Extended Validation (EV)

The most thorough verification — the CA checks legal existence, physical address, and operational status. Used to show a green address bar, but modern browsers no longer distinguish EV visually.

Validation: 1-2 weeks Cost: $100-500/year Browser indicator: Padlock icon (same as DV/OV — the green bar is gone)

My recommendation: Use DV certificates (Let’s Encrypt) for everything unless your compliance requirements specifically mandate OV or EV. The encryption is identical regardless of type.

Let’s Encrypt: Free Certificates for Everyone

Let’s Encrypt issues free DV certificates with automated renewal. If you’re not using it, you should be.

Certbot Setup

# Install certbot
sudo apt install certbot python3-certbot-nginx  # Debian/Ubuntu
sudo yum install certbot python3-certbot-nginx   # CentOS/RHEL

# Get a certificate (Nginx)
sudo certbot --nginx -d example.com -d www.example.com

# Get a certificate (Apache)
sudo certbot --apache -d example.com -d www.example.com

# Get a certificate (standalone — for non-web servers)
sudo certbot certonly --standalone -d example.com

Automatic Renewal

Let’s Encrypt certificates expire every 90 days (intentionally short to encourage automation). Certbot sets up automatic renewal:

# Test renewal
sudo certbot renew --dry-run

# The systemd timer or cron job handles actual renewal
# Check it's active:
systemctl list-timers | grep certbot

DNS Challenge (Wildcard Certificates)

For wildcard certificates (*.example.com), you need DNS validation:

sudo certbot certonly --manual --preferred-challenges dns \
  -d "*.example.com" -d example.com

Certbot will ask you to create a TXT record:

_acme-challenge.example.com.  TXT  "abc123..."

For automated wildcard renewal, use a DNS plugin (Cloudflare, Route 53, etc.):

sudo certbot certonly --dns-cloudflare \
  --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
  -d "*.example.com" -d example.com

The Certificate Chain

Browsers don’t trust server certificates directly. They trust a small set of “root” CAs, and those roots sign intermediate CAs, which sign your certificate:

Root CA (trusted by browser)
  └── Intermediate CA (signed by root)
      └── Your certificate (signed by intermediate)

Your server must send the full chain (your cert + intermediate certs). The root is already in the browser’s trust store, so you don’t send it.

The Missing Intermediate Problem

If your server sends only your certificate without the intermediate, some browsers and clients will fail:

# What your server should send
-----BEGIN CERTIFICATE-----
(your certificate)
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
(intermediate certificate)
-----END CERTIFICATE-----

# What some misconfigured servers send
-----BEGIN CERTIFICATE-----
(your certificate only)
-----END CERTIFICATE-----

Desktop browsers sometimes fetch the missing intermediate on their own (AIA fetching), so the site “works” in Chrome but fails in:

  • Mobile browsers
  • API clients (curl, Python requests, Node.js)
  • Older operating systems
  • Monitoring tools

This is one of the most frustrating SSL bugs because it’s intermittent — it works for some people but not others.

Debugging Certificate Issues

Check Certificate from the Command Line

# Show certificate details
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | openssl x509 -text -noout

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

# Verify the full chain
openssl s_client -connect example.com:443 -servername example.com -showcerts

Common Certificate Errors

“NET::ERR_CERT_DATE_INVALID” Certificate has expired. Renew it. If using Let’s Encrypt, check that auto-renewal is working (certbot renew --dry-run).

“NET::ERR_CERT_COMMON_NAME_INVALID” The certificate doesn’t cover the domain you’re accessing. Check the SAN (Subject Alternative Name) list — the domain must be listed there. A certificate for example.com doesn’t automatically cover www.example.com.

“NET::ERR_CERT_AUTHORITY_INVALID” The browser doesn’t trust the CA that issued the certificate. Common causes:

  • Self-signed certificate (fine for development, not production)
  • Missing intermediate certificate
  • The CA was removed from the browser’s trust store

“SSL: CERTIFICATE_VERIFY_FAILED” (Python/Node.js) Same as above, but from a non-browser client. The client’s CA bundle might be outdated:

# Python — update certifi
pip install --upgrade certifi

# Or specify the CA bundle
import requests
response = requests.get('https://example.com', verify='/path/to/ca-bundle.crt')
// Node.js — if you must (development only!)
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';

// Better: specify the CA
const https = require('https');
const agent = new https.Agent({
  ca: fs.readFileSync('/path/to/ca-bundle.crt'),
});

TLS Configuration Best Practices

Minimum TLS Version

# Nginx — only allow TLS 1.2 and 1.3
ssl_protocols TLSv1.2 TLSv1.3;

TLS 1.0 and 1.1 are deprecated (RFC 8996). If your server still accepts them, older clients can negotiate a weak connection. Disable them.

Strong Cipher Suites

# Nginx — modern cipher configuration
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;

OCSP Stapling

Normally, the browser checks if a certificate has been revoked by contacting the CA’s OCSP server. This adds latency and leaks browsing data to the CA. OCSP stapling lets your server include the revocation status, so the browser doesn’t need to ask:

ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;

Certificate Monitoring

Never let a certificate expire unexpectedly. Set up monitoring:

# Simple script to check expiry
DOMAIN="example.com"
EXPIRY=$(echo | openssl s_client -connect $DOMAIN:443 -servername $DOMAIN 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
DAYS_LEFT=$(( ($(date -d "$EXPIRY" +%s) - $(date +%s)) / 86400 ))

if [ $DAYS_LEFT -lt 30 ]; then
  echo "WARNING: $DOMAIN certificate expires in $DAYS_LEFT days"
fi

Better yet, use a monitoring service that alerts you 30, 14, and 7 days before expiry. Certificate expiration is the most preventable production outage there is.

Self-Signed Certificates for Development

For local development, generate a self-signed certificate:

# Generate a self-signed cert valid for 365 days
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes \
  -subj "/CN=localhost" \
  -addext "subjectAltName=DNS:localhost,IP:127.0.0.1"

Use this for local HTTPS testing. Your browser will show a warning — that’s expected. Never use self-signed certificates in production.

For a smoother local experience, tools like mkcert create locally-trusted certificates:

# Install mkcert
brew install mkcert  # macOS
mkcert -install      # Add local CA to trust store

# Create certificates
mkcert localhost 127.0.0.1 ::1
# → localhost+2.pem and localhost+2-key.pem

Quick Checklist

  • Use Let’s Encrypt with automated renewal for all certificates
  • Certificate covers all domains and subdomains (check SAN list)
  • Full certificate chain is served (intermediate + server cert)
  • Minimum TLS 1.2, prefer TLS 1.3
  • HSTS header is set (see HTTP Security Headers Guide)
  • OCSP stapling is enabled
  • Certificate expiry monitoring is in place
  • CAA DNS record restricts which CAs can issue for your domain
  • Auto-renewal is tested (certbot renew --dry-run)
  • Certificate renewal notifications go to a team inbox, not an individual

Further Reading


Analyzing an SSL certificate? Our SSL Certificate Checker breaks down certificate fields, validity, and configuration in a clear format. Pair it with a DNS Lookup to verify your domain’s CAA records and nameserver configuration.

Necmeddin Cunedioglu
Necmeddin Cunedioglu Author

Software developer and the creator of UseToolSuite. I write about the tools and techniques I use daily as a developer — practical guides based on real experience, not theory.