Let's Encrypt wildcard renewal failing on multi-tenant setup
So I've got a multi-tenant WordPress setup using a wildcard cert from Let's Encrypt (*.example.com), and the automated renewal keeps failing. Getting a validation error every 30 days when certbot tries to renew.
I'm using DNS validation with Route53 (AWS), and the renewal script runs fine manually but fails via cron. Pretty sure it's a permissions issue with the AWS credentials, but I want to make sure I'm not missing something obvious here.
Anyone else run into this? What's your setup for handling wildcard renewals at scale?
Edited at 26 Mar 2026, 12:50
Yup, classic cron + AWS creds issue. When certbot runs via cron, it's usually in a minimal environment without your shell's env vars. Check two things: (1) are your AWS credentials in /root/.aws/credentials or set via env vars in the cron job itself? (2) does the certbot hook script have the right permissions and PATH to find AWS CLI?
I'd add explicit env vars to the cron line: AWS_PROFILE=default /usr/bin/certbot renew --quiet or use IAM role if this is on an EC2 instance (way cleaner). Also check /var/log/letsencrypt/ for actual error details—99% of the time it's not permissions but a missing env var or DNS propagation timeout. Let me know what the logs say.
Ah yeah, that's probably it! I bet the cron environment doesn't have the AWS creds loaded. Let me check if they're explicitly set in the renewal hook script rather than relying on env vars. Thanks for the pointer!
One more thing to watch: even if you get the creds working in cron, make sure your renewal hook script has the right IAM permissions. I've seen people grant Route53 access but forget the route53:ChangeResourceRecordSets action—certbot silently fails because it can't actually write the DNS challenge. Also double-check that your hook is using the right AWS region if you're not using the default. https://letsencrypt.org/docs/ has good examples for DNS validation setup.
pro tip: use --preferred-challenges dns explicitly in your renewal hook, saved me from debugging this exact thing twice