Everyone automates their certs with plugins or built-in ACME. That's a single point of failure and ties you to the firewall's implementation. Usually a mess when it breaks.
I run a separate, tiny container on a different host. It handles DNS challenges for both my pfSense and OPNsense boxes. No ACME client on the firewalls at all. They just get a fresh cert pushed via SCP when renewed. No firewall services rely on a package that might update and break. The firewalls become dumb recipients.
Here's the core of the docker-compose. The key is using the same certbot Docker image with two different configs, one for each firewall's domain. It writes to separate volumes, and a cronjob inside the container scps the certs out. The firewalls have a static key for scp.
version: '3'
services:
certbot-pfsense:
image: certbot/dns-cloudflare
container_name: certbot-pfsense
volumes:
- ./data/certbot-pfsense:/etc/letsencrypt
- ./data/scripts:/scripts
command: "/bin/sh -c 'trap exit TERM; certbot certonly --dns-cloudflare --dns-cloudflare-credentials /secrets/cloudflare.ini -d firewall1.mydomain.com --email admin@mydomain.com --agree-tos --noninteractive --keep-until-expiring; /scripts/push-cert.sh firewall1.mydomain.com'"
certbot-opnsense:
image: certbot/dns-cloudflare
container_name: certbot-opnsense
volumes:
- ./data/certbot-opnsense:/etc/letsencrypt
- ./data/scripts:/scripts
command: "/bin/sh -c 'trap exit TERM; certbot certonly --dns-cloudflare --dns-cloudflare-credentials /secrets/cloudflare.ini -d firewall2.mydomain.com --email admin@mydomain.com --agree-tos --noninteractive --keep-until-expiring; /scripts/push-cert.sh firewall2.mydomain.com'"
The push-cert.sh script is just an scp command. Now if either firewall's ACME plugin has a bug or their OS package repo fails, my HTTPS admin portals and VPN endpoints don't care. The complexity is offloaded.
You're now dependent on your container host, but that's easier to backup and stage than a firewall config. Just saying.
Just saying.
Your architectural separation is conceptually solid, but you're trading one complexity for another. The operational burden shifts to maintaining the orchestration, key distribution, and cron health for that container. What's your monitoring strategy for the SCP job? If it fails silently, your firewalls are left with expired certificates until manual intervention.
The reliability gain is real, but the total cost of ownership includes that new failure domain. For teams with mature infrastructure pipelines, this decoupling makes sense. For a solo admin, the built-in ACME client, while occasionally brittle, might represent a simpler mean time to repair when it does break. The vendor stability risk you've mitigated is replaced by a self-built integration stability risk.
PM by day, reviewer by night.
So you're arguing for a single, poorly implemented point of failure because fixing it might be easier? That's backwards ops.
The "self-built integration risk" you mention is a known quantity I control. The firewall's built-in ACME is a black box that changes on their schedule, not mine. The cron job's health is trivial to monitor - it's literally one line in a monitoring system you should already have.
Mean time to repair is lower when you built the system. You know where the logs are, you know the exact commands, and you can fix it without waiting for a vendor patch. The "operational burden" is just basic sysadmin work, not some new rocket science.
Just my two cents.