Guide
Caddy SSL certificate expired
Caddy is serving an expired TLS certificate on the public listener—confirm automatic HTTPS state, renew or replace material, reload Caddy, and verify serials externally.
Updated May 30, 2026 865 words
When caddy ssl certificate expired breaks customer traffic, confirm what Caddy presents on the public port—not only what sits in the data directory—then fix automatic HTTPS or pinned paths before you reload.
Caddy often manages ACME renewal internally, which makes expired edge certificates frustrating: logs may look quiet while browsers warn. Platform teams run this stack on single VMs, Kubernetes ingress sidecars, and edge nodes where systemd or containers wrap the same binary.
Incidents usually mean automatic HTTPS stalled, a manual tls file override blocked renewal, storage permissions failed after a hardening pass, or upstream load balancers still terminate TLS in front of Caddy while operators renewed the wrong hop.
This guide walks operators through live confirmation, Caddy-specific renewal paths, graceful reload, and external verification. If obtain errors appear in logs rather than simple clock drift, pair this runbook with Caddy automatic HTTPS renewal failed for ACME triage.
What to check
Separate three facts before changing configuration: the certificate strangers negotiate on port 443, the assets Caddy wrote under its data directory, and whether the site block still uses automatic HTTPS or a pinned manual path.
Capture serial and notAfter from a live handshake first. File dates on disk can look fresh while workers serve an older loaded certificate until reload. Compare issuer and subject alternative names against the hostname customers type, including apex versus www splits.
Read Caddy logs for obtain or renew failures around the last scheduled renewal window. Kubernetes deployments should confirm the data volume is writable and persisted across pod restarts. When Caddy sits behind another reverse proxy, confirm which layer presents the expired material to the internet.
Note whether only one site label failed while neighbors remain valid—that pattern often indicates a per-site tls stanza instead of a global outage.
Step-by-step
Confirm live expiry
HOST=www.example.com
echo | openssl s_client -connect "${HOST}:443" -servername "${HOST}" 2>/dev/null \
| openssl x509 -noout -dates -serial -subject -issuer
Run the same command from outside your office network or use a live TLS check so split-horizon DNS does not fool the ticket.
Inspect Caddy state
caddy validate --config /etc/caddy/Caddyfile
journalctl -u caddy -n 150 --no-pager | grep -Ei 'obtain|renew|tls|error'
For JSON configuration, adjust the config path. Query the admin API on the local socket if enabled to see which certificate IDs load for each site.
Renew or replace material
When automatic HTTPS should handle the name, fix the root cause (DNS tokens, challenge ports, storage permissions) documented in Caddy automatic HTTPS renewal failed, then trigger renewal:
caddy reload --config /etc/caddy/Caddyfile
journalctl -u caddy -f
If you temporarily pinned PEM paths, install the renewed full chain and private key at those paths, ensure Caddy user can read them, then reload.
Reload and verify
After reload, repeat the openssl command and confirm serial changed and notAfter moved forward. Hit each customer hostname independently; wildcard coverage still requires correct SNI on the client command.
curl -vI --connect-timeout 10 "https://${HOST}/" 2>&1 | grep -Ei 'expire|SSL certificate|subject:|issuer:'
Document before-and-after serials in the incident record.
How to read the output
notAfter in GMT is the instant browsers stop trusting the leaf. If notAfter is in the past on the live check, the incident is customer-visible regardless of cron success elsewhere.
serial should change after a successful re-issuance unless you deliberately reinstalled the same issuance file. Matching serial after reload suggests Caddy still serves prior material or traffic hits a different node.
issuer reveals whether Let's Encrypt, ZeroSSL, or an internal CA signed the cert. A corporate CA name on the wire but Let's Encrypt files on disk means the active site block still references manual paths.
subject and SAN entries must include the public hostname. Future notAfter on the wrong name still fails hostname verification in clients and webhooks.
Common mistakes
Renewing on a bastion host while production Caddy never received new PEMs. Editing only the Caddyfile without reload leaves the previous certificate in memory for open connections.
Assuming orange-cloud CDN proxies fix expiry; they mask origin problems until a bypass test exposes the same expired Caddy cert. Running two ACME clients for the same names on port 80 while Caddy also solves challenges.
Copying certificates into the data directory by hand while automatic HTTPS still points at different metadata paths. Checking localhost curl against admin names while users hit marketing hostnames on the same server.
Skipping validation after container image updates that changed UID mappings and broke writes to the data directory.
Closing tickets without IPv6 checks when AAAA records point at the same Caddy fleet. Marketing campaigns that add vanity hostnames without corresponding site blocks leave default certificates expiring on unused labels that still answer on shared IPs.
Change-advisory boards should treat caddy ssl certificate expired incidents as customer-facing even when agent-based health checks on upstream apps stay green. Edge TLS is the contract your users see in the browser chrome and what SaaS webhooks validate on delivery retries.
Runbooks that only mention restarting Docker Compose services miss Caddy when it runs as a separate container with its own volume. Inventory each compose project and systemd unit that binds customer IPs before you declare victory.
Staging environments that reuse production DNS with TLS internal or self-signed modes should never pollute production renewal metrics. Color-code hostnames in monitoring so synthetic checks hit the production site block, not a developer laptop tunnel.
During post-incident review, attach before-and-after openssl transcripts and the Caddy configuration stanza that owned the hostname. Future responders inherit evidence instead of repeating blind reloads.
When to use automation
Manual openssl during incidents does not scale across dozens of site labels and regional anycast endpoints. Schedule live TLS monitoring on the exact hostnames users and webhooks request, and store historical notAfter for audit.
Pair certificate watches with RDAP domain expiration when registries publish dates, so a registrar lapse does not surprise you during the next obtain attempt. SSLdays reads the live chain Caddy presents and emails alerts at 30, 14, 7, and 1 days before expiry (once per threshold per renewal cycle). Paid plans add Slack and Discord webhooks for hourly checks on larger inventories.
Start with a one-shot live check, then register monitoring at /register so the next Caddy renewal gap pages the right owner before browsers do.
Frequently asked questions
Can one expired site take down neighbors?
Neighboring sites keep working unless they share a broken issuer configuration or exhausted storage permissions globally. Per-site manual tls paths isolate failures.
Should I delete the data directory to force renewal?
Deleting storage loses ACME account metadata and can trigger rate limits. Fix permissions and configuration first; see the renewal-failed guide before wiping state.
What if only IPv6 clients fail?
Confirm AAAA records reach the same Caddy instance and reload ran on the process binding IPv6. openssl against the v6 address with the same servername.
Is expired material always an ACME problem?
No. Commercial certificates pinned in site blocks expire on calendar schedules unrelated to Let's Encrypt. Renewal means vendor re-issue plus install, then reload Caddy on the public listener.
Monitor this hostname on a schedule
Live certificate checks and renewal reminders — not guesswork from PEM files.