Certbot is one of the most important tools in the history of the web. It made HTTPS accessible to everyone. It turned Let's Encrypt from a good idea into a practical reality. Millions of websites run TLS because certbot made it easy.
That was 2016. It's 2026 now, and certbot has accumulated a decade of architectural debt: a tool that increasingly fights your package manager, makes opinionated decisions about your web server, and breaks in subtle ways that only show up at renewal time.
We're not saying throw it away. We're saying know when you've outgrown it.
What certbot got right
Credit where it's due. Before certbot, getting an SSL certificate meant paying a CA, waiting for verification emails, downloading a zip file, figuring out which of the four .pem files goes where, and setting a calendar reminder to do it all again in a year. Certbot turned that into certbot --nginx and a cron job. Revolutionary.
For a single server with one or two domains and a straightforward nginx or Apache config, certbot still works fine. If that's your setup, this post isn't for you. Keep running it. It's fine.
This post is for everyone else.
The slow slide into bloat
Certbot started as an ACME client. It's now trying to be a web server configuration manager, a package ecosystem, a plugin framework, and a renewal scheduler, all at once. That's a lot of hats for a tool whose core job is "prove you own a domain, get a certificate."
The snap situation
The EFF made snap the primary installation method for certbot. Pip in a virtualenv is technically still supported on a "best effort" basis, and distro apt packages exist but lag far behind. In practice, snap is what the docs push, what the tutorials show, and what most people end up running. If you've ever tried to run snap on a headless production server, you already know how this went.
Snap pulls in its own runtime, its own filesystem, its own update daemon. On minimal server images (the kind you should be running in production), it's absurd. The snapd process sits there consuming resources on a machine that exists solely to serve HTTP traffic.
And it's not just philosophical. Snap's sandboxing routinely breaks subtle things: Python paths in custom auth hooks, file permissions on shared volumes, environment variable propagation in cron contexts. The failures are silent. Your renewal succeeds on the last run because the cert wasn't due yet. Two weeks later, it fails. You find out when your monitoring fires (if you have monitoring) or when a customer calls (if you don't).
The certbot team's position is clear: snap or pip in a venv. The apt packages in Debian and Ubuntu repos still exist, but they only update at distro release cycles, which means they're perpetually behind on features and security fixes. The dedicated certbot PPA is deprecated. For a tool that runs on servers, pushing snap as the default is a choice.
The renewal lottery
Certbot renewals work until they don't, and the failure modes are subtle.
It forgets your webroot and puts validation files in the wrong directory. It creates overlapping certificates for the same domains and sends you expiry emails for both, forever. It quietly changes renewal parameters between versions. Its output tells you a cert "not due for renewal" in a format that looks almost identical to a successful renewal, so you glance at the cron log and assume everything's fine.
We've debugged enough certbot renewal failures across client servers to know this isn't an edge case. It's a pattern. The tool works great on day one, and then slowly drifts out of sync with your actual server state over months.
The Docker mismatch
Certbot was designed for a world where one server runs one application behind one web server. That world barely exists anymore.
In containers, certbot is architecturally wrong. The official Docker image runs as a one-off script, not a service. It needs to bind to port 80 or write to a webroot that your actual application container also needs to access. Multi-domain setups require separate invocations or custom INI configs. The renewal state lives inside the container, which means if the container dies, your renewal history dies with it (unless you mounted the right volumes, which certbot's docs don't make obvious).
None of this is certbot's fault, exactly. It was built for bare metal. But the world moved, and certbot didn't move with it.
The 47-day cliff
Here's where it gets real. The CA/Browser Forum voted to reduce maximum certificate lifespans to 47 days, phased in by March 2029. Apple drove the ballot, and it passed with support from all major browser vendors. The timeline steps down gradually: 200 days by March 2026, 100 days by March 2027, 47 days by March 2029.
That's not tomorrow, but it's not far off either. And 200-day certs are already the new max as of this year.
With 90-day certificates, a flaky renewal setup fails once and you have 60 days of runway before anything breaks. At 47 days, that window shrinks to roughly two weeks. If your certbot cron misses two cycles (a snap update breaks it, a webroot path changed, the server was down for maintenance), you're serving expired certificates.
Every fragile renewal setup that "mostly works" today is living on borrowed time. The margin for error shrinks with each step down, and if your certificate management can't handle fully automated, reliable renewal right now, it won't survive the transition.
When to outgrow certbot
Certbot is the right tool if all of these are true: you're running a single server, your nginx or Apache config is standard, you have one or two domains, and you never need to issue certificates programmatically.
You've outgrown it if any of these apply:
Multi-tenant applications. Your SaaS onboards customers with custom domains. You need to issue certs on demand, triggered by your application, not by a cron job. Certbot doesn't know about your tenants. Your application does.
Containerized deployments. You're running on Kubernetes, Docker Compose, or any setup where processes are ephemeral. Certbot's state model assumes persistent filesystems and long-lived processes. Container orchestrators assume neither.
PHP applications that manage their own certificates. If your application already handles domain provisioning, DNS configuration, or customer onboarding, bolting on a separate Python tool via exec('certbot certonly ...') is fragile, insecure, and ugly. Your certificate management should live where your domain management lives: in your application code.
Multiple servers behind a load balancer. Certbot on Server A doesn't know about Server B. You end up writing sync scripts or shared storage hacks to keep certs consistent across nodes. This is build-your-own-package-manager territory.
Shared hosting without shell access. If you can't install certbot, you can composer require CoyoteCert. Different conversation.
What about Caddy, Traefik, or acme.sh?
Three alternatives come up every time certbot does. Quick honest take on each:
Caddy is the strongest answer to "I never want to think about TLS again." Auto-HTTPS is built into the web server itself, no separate ACME client needed. If you're starting greenfield and Caddy fits your stack, use Caddy. The catch: most production environments are already running nginx or Apache, and rip-and-replace isn't trivial. Caddy also doesn't help if your TLS automation needs to live inside an application, say, a SaaS issuing certs for tenant domains.
Traefik is excellent in Kubernetes and Docker Compose environments where it's the ingress anyway. Auto-HTTPS via ACME, container-native, declarative config. If Traefik is already in your stack, you probably don't need anything else for TLS. If it isn't, adding it just for certs is overkill.
acme.sh is what we'd recommend over certbot for most "shell script in a cron job" use cases. Pure POSIX shell, no Python, no snap, no opinions about your web server. It's underrated. If your problem is "certbot has too many dependencies," acme.sh solves it without leaving the shell-script paradigm.
PHP ACME libraries like Acmephp and kelunik/acme exist and have for years. They work. They're also, in our experience, harder to use than they should be (verbose APIs, awkward storage abstractions, sparse Laravel integration, varying levels of recent maintenance). CoyoteCert is what we wanted those libraries to be: fluent, typed, opinionated about the right things, framework-agnostic but Laravel-friendly.
If Caddy, Traefik, or acme.sh fit your situation, use them. CoyoteCert is for the case where certificate management needs to live inside your PHP application's logic, not next to it.
What we built instead
CoyoteCert is a PHP 8.3+ ACME v2 client library. It does the same thing certbot does (talks to Let's Encrypt, proves you own a domain, gets a certificate) but from inside your PHP application, with no shell calls, no Python dependency, no snap, and no opinions about your web server config.
Here's what issuing a certificate looks like:
use Blendbyte\CoyoteCert\CoyoteCert;
use Blendbyte\CoyoteCert\Provider\LetsEncrypt;
use Blendbyte\CoyoteCert\Storage\FilesystemStorage;
use Blendbyte\CoyoteCert\Challenge\Http01Handler;
$cert = CoyoteCert::with(new LetsEncrypt())
->storage(new FilesystemStorage('/var/certs'))
->identifiers(['example.com', 'www.example.com'])
->email('admin@example.com')
->challenge(new Http01Handler('/var/www/html'))
->issueOrRenew();
That's the whole thing. No config files. No plugins. No webroot guessing. You tell it where to put the challenge file, it proves ownership, and you get back a typed Certificate object with the fullchain and private key.
Need wildcards? Swap the challenge handler to DNS-01:
use Blendbyte\CoyoteCert\Challenge\CloudflareDns01Handler;
$cert = CoyoteCert::with(new LetsEncrypt())
->storage(new FilesystemStorage('/var/certs'))
->identifiers(['example.com', '*.example.com'])
->email('admin@example.com')
->challenge(new CloudflareDns01Handler(apiToken: 'your-cloudflare-token'))
->issueOrRenew();
Cloudflare, Route 53, DigitalOcean, Hetzner, and ClouDNS are built in. Running something else? Implement two methods and you're done.
Most people will only ever use Let's Encrypt, and that's fine, it's the default. But if you need to switch CAs (for ZeroSSL's longer cert validity, Buypass for compliance reasons, or Google Trust Services for an enterprise mandate), it's a one-line change. Any RFC 8555-compliant CA works.
You don't have to write PHP to use it
Not everything needs to be embedded in your application. Sometimes you just want a cron job that renews your certs and doesn't require snap.
CoyoteCert ships with a CLI:
# Renew all certificates that are due
vendor/bin/coyotecert renew --due
It's idempotent. Run it every hour if you want. It checks what's due, renews what needs renewing, and skips everything else. Drop it in cron and walk away:
0 * * * * cd /var/www/myapp && vendor/bin/coyotecert renew --due >> /var/log/coyotecert.log 2>&1
If you're running Laravel, this slots into your existing infrastructure naturally. CoyoteCert is framework-agnostic at its core, but the Laravel integration ships with:
- A service provider for
config/coyotecert.php - Scheduler integration so renewal runs as a
Schedule::command()task - Log channel configuration that respects your
logging.phpsetup - Storage drivers that wrap Laravel's filesystem abstraction (so certs can live on S3, local, or anywhere
Storage::works)
// app/Console/Kernel.php
$schedule->command('coyotecert:renew --due')->hourly();
That's it. No separate cron, no shell scripts, no hand-rolled subprocess management. If your application already manages domains, it now manages certificates too.
(Adjust the above to match what CoyoteCert actually ships. Don't promise features that aren't built yet.)
What this changes for multi-tenant setups
This is where certbot completely falls apart and where programmatic ACME shines.
Say you're building a SaaS platform where customers connect custom domains. The workflow is: customer adds their domain in your dashboard, your app creates the DNS records or verifies the CNAME, your app issues a TLS certificate, and traffic starts flowing.
With certbot, you'd need to: shell out from PHP to run certbot with the right arguments, parse stdout to figure out if it worked, handle the fact that certbot might be in the middle of another renewal, and hope the filesystem state doesn't get corrupted.
With CoyoteCert, it's just PHP:
// In your onboarding controller
public function connectDomain(Request $request): void
{
$domain = $request->validated('domain');
$cert = CoyoteCert::with(new LetsEncrypt())
->storage(new FilesystemStorage("/var/certs/{$domain}"))
->identifiers([$domain])
->email('ssl@yourapp.com')
->challenge(new Http01Handler(public_path()))
->issueOrRenew();
// Store cert paths, reload nginx, update your database
$this->configureTenant($domain, $cert);
}
No subprocess. No race condition with other renewals. No filesystem guessing. Your application handles the certificate lifecycle the same way it handles everything else: in code, with types, with error handling you control.
The honest trade-offs
CoyoteCert isn't a drop-in certbot replacement for every scenario. Let's be upfront about what it doesn't do:
It doesn't configure your web server. Certbot's --nginx and --apache flags are convenient if you want a tool that both gets the cert and edits your server config. CoyoteCert gives you the cert and the key. What you do with them is your business. We think that's actually a feature (we've cleaned up too many certbot-mangled nginx configs to think otherwise), but it's more work on day one.
It requires PHP 8.3+. If you're running PHP 7.x, you're not the target audience. (You also have bigger problems than certificate management.)
It doesn't have a web UI. No fancy dashboard showing your certs. If you need that, you'll build it yourself or use something like Traefik.
It's newer than certbot. Certbot has been battle-tested by millions of deployments since 2016. CoyoteCert is the new entrant. We use it ourselves and trust it on production, but if you need a tool with a decade of community-tested edge cases behind it, certbot still has us beat on raw maturity.
DNS provider coverage is narrower. We support Cloudflare, Route 53, DigitalOcean, Hetzner, and ClouDNS out of the box because that's what we and our clients use. Certbot has plugins for dozens more (some maintained, many not). If you need DNS-01 with a niche provider, you'll either implement the two-method handler interface yourself (about 30 lines of code) or stay on certbot.
Try it
composer require blendbyte/coyotecert
The GitHub repo has the full documentation, examples for every challenge type, and a getting-started guide that doesn't require a PhD in ACME.
It's MIT licensed, it's free, and it'll stay that way. We built it because we needed it for our own infrastructure and client projects. The certbot treadmill was eating hours we'd rather spend on actual work.
Need help with TLS or certificate automation?
Setting up certificate management properly, whether it's CoyoteCert, certbot, or something else entirely, is the kind of thing that's easy to get 90% right and painful to get 100% right. We've done it across dozens of client environments.
If you need help with certificate automation, multi-tenant TLS, or anything server-side, talk to us. Free 30-minute consultation, and you'll get engineers, not a sales pitch.