SSL Monitoring for Symfony Agencies: Certbot Renewal Failures After Security Hardening on Nginx
Symfony is a PHP framework commonly used by agencies to build web applications, REST APIs, and e-commerce platforms. The typical Symfony production deployment runs PHP-FPM behind Nginx on a VPS — a DigitalOcean Droplet, Hetzner Cloud instance, or AWS EC2 server — with Certbot managing Let's Encrypt certificates for the application's domains and subdomains.
This deployment pattern has a specific set of SSL failure modes that differ from managed platforms like Heroku or Netlify. The SSL layer depends on Certbot's renewal cron job running successfully, which in turn depends on the server's firewall and networking configuration staying compatible with Let's Encrypt's HTTP-01 validation protocol. When anything in that chain changes after the initial deployment, SSL renewal fails silently.
How Certbot Renewal Works on Nginx
Certbot obtains and renews Let's Encrypt certificates using one of several challenge methods. The most common on Nginx deployments is HTTP-01: Let's Encrypt sends an HTTP request to a specific path on the domain (.well-known/acme-challenge/<token>), and Certbot's webroot plugin responds to confirm domain control.
For HTTP-01 to succeed:
- Port 80 must be accessible on the server from Let's Encrypt's validation infrastructure
- The domain's DNS A records must resolve to the server's IP address
- Nginx must be configured to serve the ACME challenge path, or Certbot must be running in standalone mode and temporarily binding port 80
The Certbot renewal cron job runs twice daily. It checks each certificate's expiry date and attempts renewal when the certificate is within 30 days of expiry. If renewal fails, Certbot logs the error to /var/log/letsencrypt/letsencrypt.log and exits. The cron job output is typically discarded unless the server is configured to capture it. No notification is sent to the agency.
The Security Hardening Failure Mode
The most common cause of Certbot renewal failure for Symfony agencies is security hardening applied after the initial deployment.
When a Symfony application is first deployed, the server is typically configured to get the application running as quickly as possible. Firewall rules are permissive, or at least allow port 80 for the Certbot HTTP-01 challenge and port 443 for the application. Let's Encrypt issues the certificate. The application is live.
After launch, the client or the agency runs a security hardening pass:
- UFW is configured to explicitly allow only required ports — typically 443 and SSH, with port 80 either closed or the rule removed
- A security group on AWS or DigitalOcean is updated to restrict inbound traffic
- A load balancer or reverse proxy upstream of the Nginx server is configured for HTTPS-only termination
- A client security audit requires closing port 80
Any of these changes break Certbot's HTTP-01 challenge. The next time the renewal cron runs for a certificate within the 30-day window, the challenge fails because Let's Encrypt's validation servers cannot reach port 80. Certbot logs the failure. The existing certificate continues serving. 30 days later, the certificate expires.
The time gap hides the problem. If a Symfony application was deployed and its certificate renewed a week before the security hardening, the certificate has roughly 83 days of validity remaining at the time of the hardening change. The failure will not surface until the next renewal attempt — which is when the certificate is next within 30 days of expiry. That is approximately 53 days after the hardening change. By that point, the agency has moved on to other projects and may not connect the certificate expiry to the firewall change applied nearly two months earlier.
Multi-Subdomain Symfony Deployments
Symfony applications commonly deploy across multiple subdomains:
api.clientapp.com— the Symfony REST API or API Platform endpointadmin.clientapp.com— the EasyAdmin or Sonata Admin panelapp.clientapp.com— the consumer-facing application or SPA frontendwebhooks.clientapp.com— webhook receiver for payment processors or third-party integrationsmedia.clientapp.com— asset storage or CDN origin
Each subdomain requires its own Nginx server block and its own Certbot certificate. When the initial deployment configures Certbot for the primary application domain, the API subdomain may be added immediately or may be added during a subsequent sprint. The admin panel may be added later still, during the admin interface development sprint.
Subdomains added during sprints are frequently not added to Certbot. An engineer adding webhooks.clientapp.com for a Stripe integration sprint adds the Nginx server block, configures the Symfony routing, and deploys the application. Running certbot certonly --nginx -d webhooks.clientapp.com is a separate step that is easy to defer and easy to forget entirely, particularly if the team is focused on getting the integration working rather than certificate management.
The result is a subdomain that serves the Symfony application over HTTP, or with a self-signed certificate, or with the certificate from a different subdomain that does not match the webhook hostname. Stripe's webhook delivery endpoint requires valid SSL. The integration appears to work in the Stripe test environment, which is more permissive, and fails in production.
VPS Migration SSL Gaps
When a Symfony application migrates from one VPS to another — an upgrade from a smaller Droplet to a larger one, a move from one data center to another, or a migration from one provider to another — the migration checklist typically covers application code, database, Nginx configuration, and DNS.
Certbot's certificate profiles are frequently missed.
Certbot stores its configuration in /etc/letsencrypt/. This directory contains:
- The certificate files (
/etc/letsencrypt/live/<domain>/) - The renewal configuration for each certificate (
/etc/letsencrypt/renewal/<domain>.conf) - The account credentials for the Let's Encrypt ACME account
A complete Certbot migration requires either transferring this directory to the new server or re-running certbot certonly for every domain and subdomain on the new server. Neither step is in the standard migration checklist.
The common migration outcome: The primary application domain is re-configured with Certbot on the new server. Two or three subdomains are missed. DNS is updated to point at the new server. The missed subdomains' certificates were last renewed on the old server and will expire at their original renewal date — 90 days from when they were last renewed on the old server. The old server is decommissioned. The missed subdomains' certificates are no longer being renewed anywhere.
Monitoring Approach for Symfony Agencies
An effective Symfony SSL monitoring setup needs to cover the Certbot renewal path, not just the primary application domain.
Monitor every Certbot-managed subdomain as a separate asset. For each Symfony client, add api., admin., app.*, and any webhook or media subdomains as separate monitored assets with SSL expiry monitoring. SSL certificate monitoring with a 30-day expiry alert gives enough lead time to identify whether the failure is a port 80 block from security hardening, a Certbot profile gap from a VPS migration, or a subdomain that was never configured in Certbot.
Enable DNS change monitoring on all Symfony client domains. DNS change monitoring on every Symfony application domain detects when a VPS migration changes the DNS A records. A change to the A record resolving the primary application domain or any API subdomain alerts your team immediately — before the migration is complete and before any SSL gaps surface at certificate expiry.
Set the SSL alert threshold at 30 days. The 90-day Let's Encrypt certificate gives a fixed renewal window. Certbot attempts renewal when the certificate is within 30 days of expiry. Monitoring SSL expiry at the same 30-day threshold means that a Certbot renewal failure is caught within the same window — giving time to identify the root cause (port 80 blocked, Certbot profile missing, DNS mismatch) and correct it before the certificate expires.
Monitor DigitalOcean, Hetzner, and AWS as vendors. Add common Symfony hosting providers as vendor status feeds. A DigitalOcean regional incident that affects connectivity across multiple Symfony client Droplets simultaneously is distinguishable from individual Certbot renewal failures only if you have the vendor status context alongside the SSL alerts.
What a Symfony Agency Monitoring Setup Looks Like in Merlonix
For each Symfony client in Merlonix:
- Add the primary application domain with SSL certificate monitoring and a 30-day expiry alert.
- Add each API, admin, and webhook subdomain as a separate asset with its own SSL and DNS monitoring. A subdomain with no valid SSL certificate shows up immediately.
- Enable DNS change monitoring on every subdomain. A VPS migration that updates the A record on the primary domain but not the API subdomain surfaces as a DNS discrepancy — the monitored record changes on one subdomain but not others.
- Add DigitalOcean, Hetzner, and AWS as vendor status feeds alongside client monitoring.
For Symfony agencies that include server maintenance or a maintenance retainer, this configuration gives your team proactive SSL and DNS coverage across every subdomain the application deploys — not just the primary domain the client typically thinks of as "the website."
Merlonix is built for agency portfolio monitoring — SSL expiry alerts, DNS change detection, vendor status tracking, and per-client alert routing. Start a free trial and add your first Symfony client domain.
→ Related: SSL and DNS monitoring for Symfony agencies → Related: DNS monitoring for marketing agencies → Related: SSL monitoring for agencies → Related: Vendor outage monitoring for agencies → Related: How to handle SSL expiry during a site migration