What certbot is, and what it actually does
Certbot is a free, open-source ACME client maintained by the Electronic Frontier Foundation. ACME is the protocol Let's Encrypt (and a growing number of other CAs) uses to issue certificates without a human in the loop. Certbot speaks it for you: it creates an account key, asks the CA for a certificate, proves you control the domain, saves the certificate and key to disk, and, if you let it, edits your web server configuration and renews everything before it expires.
The proof of control is a challenge, and certbot supports two of them. HTTP-01: the CA asks for a random token at http://example.com/.well-known/acme-challenge/<token> on port 80, and certbot makes sure that file is served. DNS-01: the CA looks up a TXT record at _acme-challenge.example.com, and certbot (or you) publishes it. HTTP-01 is the easy default; DNS-01 is the only way to get a wildcard and the way to issue for a server that is not reachable from the internet.
Who does what is set by two kinds of plugins. An authenticator answers the challenge (--nginx, --apache, --webroot, --standalone, --manual, or a DNS plugin such as --dns-cloudflare). An installer puts the certificate into a server configuration (nginx or apache). certbot --nginx does both; certbot certonly ... only fetches the certificate and leaves the configuration to you.
Everything lands under /etc/letsencrypt. The files you point a server at live in /etc/letsencrypt/live/<cert-name>/: fullchain.pem (your certificate plus the intermediate) and privkey.pem. They are symlinks into archive/, so the paths never change across renewals. If you want the background on what a certificate is and why it is free, read free SSL certificates; this guide is about the tool.
Installing certbot: snap first, distro packages second
The certbot project recommends the snap package on Linux. It tracks the current release (5.x in 2026), bundles its own Python, and installs a systemd timer for renewal. Remove any distro copy first so there are not two certbots fighting over /etc/letsencrypt: sudo apt remove certbot, sudo dnf remove certbot or sudo yum remove certbot.
Distro packages work and are what many servers already have: sudo apt install certbot python3-certbot-nginx (or python3-certbot-apache) on Debian and Ubuntu, and the same package names from EPEL on RHEL, Rocky and Alma. The trade-off is age: an LTS distribution can ship a certbot several major versions behind, which matters now that Let's Encrypt is shortening certificate lifetimes (see the renewal section). Certbot is also published on PyPI and as Docker images (certbot/certbot and one image per DNS plugin) if you prefer those.
After installing, certbot --version tells you what you got, and sudo certbot certificates lists every certificate it manages with its domains, expiry date and file paths.
Recommended install on Linux (snap)
# remove an OS-packaged certbot first, if present
sudo apt remove certbot
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/local/bin/certbot
certbot --version
sudo certbot certificates
Issuing a certificate for nginx and Apache
The nginx and Apache plugins are the shortest path. sudo certbot --nginx -d example.com -d www.example.com finds the server block whose server_name matches, answers HTTP-01 through it, writes ssl_certificate and ssl_certificate_key into that block and reloads nginx. Recent versions add the HTTP-to-HTTPS redirect by default; --no-redirect turns that off. --apache does the same with virtual hosts. Both need a server_name / ServerName that matches the domain, or certbot cannot tell which block to edit. New to the server itself? Start with what nginx is.
Webroot is the choice when you want certbot to keep its hands off your configuration: it writes the challenge file into a directory your running server already serves. certonly means you add the two ssl_ lines yourself, once; renewals then just replace the files behind the same paths.
Standalone starts a tiny web server of its own on port 80. It is for machines with no web server, or with one that is not nginx or Apache. Port 80 must be free during issuance and every renewal, so stop the other server in a hook: --pre-hook "systemctl stop haproxy" --post-hook "systemctl start haproxy".
Each -d adds a name to the same certificate, and the first one becomes the certificate name. To add a name later, run the command again with the full list plus --cert-name example.com; certbot replaces the old certificate rather than making a second one.
The four common ways to issue (pick one)
# nginx: issue and install
sudo certbot --nginx -d example.com -d www.example.com
# Apache: issue and install
sudo certbot --apache -d example.com -d www.example.com
# webroot: issue only; your server keeps serving /.well-known/acme-challenge/
sudo certbot certonly --webroot -w /var/www/example -d example.com -d www.example.com
# standalone: certbot listens on :80 itself
sudo certbot certonly --standalone -d example.com
# then, in nginx, for the webroot/standalone case:
# ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
# ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
Wildcard certificates with DNS-01
Let's Encrypt issues *.example.com only through DNS-01. A wildcard covers one level (shop.example.com, not a.shop.example.com) and does not cover the bare domain, so request both: -d example.com -d '*.example.com'. Quote the asterisk so the shell does not expand it.
With a DNS plugin certbot creates and deletes the TXT records through your DNS provider's API, which is what makes renewal automatic. There are official plugins for Cloudflare, Route 53, Google Cloud DNS, DigitalOcean, Linode, OVH, RFC 2136 servers and others, and third-party plugins for many more. With the snap you first allow plugins to run as root (sudo snap set certbot trust-plugin-with-root=ok) and then install the plugin snap. Keep the credentials file at mode 600, and give the API token the narrowest scope your provider offers (for Cloudflare, *Zone: DNS: Edit* on that one zone).
With --manual, certbot prints the TXT value and waits while you add it by hand. Requesting example.com and *.example.com together asks for two TXT values under the same name _acme-challenge.example.com; publish both as separate records. Check with dig +short TXT _acme-challenge.example.com before pressing Enter. The catch: a manual certificate does not renew automatically unless you supply --manual-auth-hook and --manual-cleanup-hook scripts that do the DNS work, so for anything long-lived use a plugin.
If your DNS provider has no API, you can point _acme-challenge.example.com at a zone you do control with a CNAME record; the CA follows the CNAME and reads the TXT record there.
Wildcard plus apex through the Cloudflare DNS plugin (snap)
sudo snap set certbot trust-plugin-with-root=ok
sudo snap install certbot-dns-cloudflare
# /root/.secrets/cloudflare.ini (chmod 600)
# dns_cloudflare_api_token = <token with Zone:DNS:Edit>
sudo certbot certonly \
--dns-cloudflare \
--dns-cloudflare-credentials /root/.secrets/cloudflare.ini \
-d example.com -d '*.example.com'
# by hand (no automatic renewal without hooks)
sudo certbot certonly --manual --preferred-challenges dns \
-d example.com -d '*.example.com'
Automatic renewal: timers, hooks and --dry-run
You do not write a cron job for certbot; the package already did. The snap installs the systemd timer snap.certbot.renew.timer, Debian and Ubuntu packages install certbot.timer, and some packages use a file in /etc/cron.d/. The job runs certbot renew twice a day, which renews only the certificates that are due and exits quietly otherwise. See it with systemctl list-timers | grep certbot.
When is a certificate due? Since certbot 4.0, when less than one third of its lifetime remains (half, for certificates of 10 days or less), and certbot also follows the CA's ACME Renewal Info (ARI) hints. This matters because Let's Encrypt is shortening lifetimes: the default profile moves from 90 to 64 days on 10 February 2027 and to 45 days in February 2028. A setup that renews from a hard-coded "every 60 days" cron line will break; one that runs certbot renew daily will not. Let's Encrypt also stopped sending expiry reminder emails in 2025, so nobody will warn you if renewal silently fails: monitor the certificate expiry date yourself.
Reload the server after renewal. The nginx and Apache installers reload for you. With webroot, standalone or a DNS plugin, add a deploy hook, which runs only when a certificate was actually renewed. Put it in the command at issuance (--deploy-hook, saved to /etc/letsencrypt/renewal/<name>.conf) or drop an executable script into /etc/letsencrypt/renewal-hooks/deploy/.
Always test with sudo certbot renew --dry-run. It runs the full renewal against the Let's Encrypt staging environment, with real challenges, without touching your live certificates or using production rate limits.
Check the timer, test renewal, reload nginx after each renewal
systemctl list-timers | grep certbot
sudo certbot renew --dry-run
# reload nginx whenever any certificate is renewed
sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh >/dev/null <<'EOF'
#!/bin/sh
systemctl reload nginx
EOF
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
# when does each certificate expire?
sudo certbot certificates
Let's Encrypt rate limits you can actually hit
Let's Encrypt's limits are generous for normal use and painful during a debugging session. The ones that bite:
5 certificates per exact set of names per 7 days. Issuing example.com + www.example.com five times in a week, for instance by re-running a provisioning script or wiping /etc/letsencrypt on every container start, locks that exact set for days. The error says *too many certificates already issued for this exact set of identifiers* and gives a retry time. There is no override for this one. Changing the set of names (adding one more name) gets a new limit, but the real fix is to keep /etc/letsencrypt on persistent storage.
5 failed validations per hostname per account per hour. A wrong DNS record plus a retry loop burns this in minutes.
50 certificates per registered domain per 7 days, counted across all subdomains of example.com, and 300 new orders per account per 3 hours. Hosting platforms with many subdomains meet these first.
Renewals are treated kindly: a renewal that uses ARI, as current certbot does, is exempt from all limits, and a plain renewal of the same set of names is exempt from the per-domain and per-order limits.
The rule for experiments: add --test-cert (alias --staging) or use --dry-run. Staging has far higher limits and issues certificates browsers do not trust, which is exactly what you want while you get the challenge working.
Common certbot errors and how to fix them
Timeout during connect / connection refused on port 80. HTTP-01 always starts on port 80, even if your site only serves HTTPS. Open port 80 in the firewall and the cloud security group; redirecting it to HTTPS is fine, because the CA follows redirects. If port 80 cannot be opened, switch to DNS-01.
Invalid response / 404 on /.well-known/acme-challenge/. The request reached a server, but not the right one or not the right directory. Typical causes: the A record still points at an old host, a load balancer sends the request to another node, a location block or rewrite catches the path, or -w points at the wrong webroot. Test it yourself: create a file under .well-known/acme-challenge/ and curl it over plain HTTP from outside.
An AAAA record pointing elsewhere. If the name has an IPv6 address, Let's Encrypt validates over IPv6 first. A stale AAAA record that answers from a different server makes validation fail even though IPv4 is fine. Fix or delete the AAAA record; dig AAAA example.com shows it.
DNS problem: NXDOMAIN / no valid A records. The name does not resolve yet. Wait for propagation, and check the record at an outside resolver, not just your own machine.
CAA record prevents issuance. A CAA record on the domain (or a parent) lists the CAs allowed to issue, and Let's Encrypt is not on it. Add example.com. CAA 0 issue "letsencrypt.org", and issuewild too if you set one for wildcards. Background in what is DNS.
Incorrect TXT record / no TXT record found. DNS-01 was checked before the record propagated, or only one of the two wildcard values was published. Raise the plugin's --dns-<provider>-propagation-seconds or wait longer in manual mode.
Could not automatically find a matching server block. The nginx plugin needs a server_name that equals the requested name. Add it, run nginx -t, and retry.
Too many certificates already issued. The duplicate limit above. Use the certificate you already have (certbot certificates), and test with --staging from now on.
Certbot on Windows: use a Windows-native ACME client
Certbot has discontinued Windows support. The last Windows installer shipped with certbot 2.9.0 in February 2024, and the certbot site now points Windows users to community-listed alternatives. Old installers still found on download sites are years behind and will not get the renewal changes Let's Encrypt is rolling out, so do not build on them.
What to use instead depends on the server:
IIS or any Windows server: win-acme has been the usual choice: a command-line tool that binds the certificate in IIS, writes it to the Windows certificate store or PEM/PFX files, and creates a scheduled task for renewal. Its maintainer now develops simple-acme, described as a backwards-compatible drop-in replacement, so check that project before a new deployment.
PowerShell automation: Posh-ACME is a PowerShell module with a large set of DNS plugins for wildcards.
Prefer a GUI: Certify The Web is a graphical client for IIS.
You really want certbot: run it inside WSL 2 for issuing, then export the files to Windows. This is practical for DNS-01 certificates you copy elsewhere, less so for an IIS site that needs automatic binding.
None of this is endorsement: all are third-party projects, so read their current documentation before you rely on them.
Revoking and deleting certificates
Revoke when the private key may have leaked, or when you no longer control the domain. Certbot needs either the certificate name or the certificate file, and a reason: keycompromise, superseded, cessationofoperation, affiliationchanged or unspecified (the default). After revoking, certbot offers to delete the local files; answer yes, or it will keep trying to renew a revoked certificate. If the key leaked, issue the replacement with a fresh key, which certbot does by default.
Delete without revoking when you simply stop using a certificate, for instance after moving a site: certbot delete --cert-name example.com removes it from /etc/letsencrypt and from renewal. Remove the matching ssl_ lines from the server configuration first, or nginx will fail to start on the next reload.
Let's Encrypt has stopped running OCSP; browsers learn about revocations through CRLs, so a revocation is not visible everywhere instantly. That is another reason to keep private keys readable by root only.
Revoke a compromised certificate, or remove one you no longer need
sudo certbot revoke --cert-name example.com --reason keycompromise
sudo certbot delete --cert-name example.com
Behind a CDN: who holds the certificate
Once a site sits behind a CDN or another reverse proxy, visitors no longer see your origin's certificate. The edge terminates TLS with its own certificate, and the connection from the edge to your origin is a separate handshake. You can keep certbot on the origin to secure that second hop, but the certificate the browser checks is the edge's.
On CDN.com.tr, the edge certificate is handled by Auto SSL: every certificate is a domain-validated Let's Encrypt certificate at no separate fee. Point a hostname at the edge with a CNAME and that name gets its own certificate, validated over HTTP. Delegate your DNS to CDN.com.tr and the root domain gets a wildcard covering the root and every first-level subdomain, validated with a DNS record. A certificate is requested once the domain is verified and its DNS points to us, and it is renewed automatically 30 days before it expires, so there is no certbot timer to watch.
Moving a live site? The zero-downtime wizard issues the wildcard through a TXT record at your current DNS provider before you switch, much like a certbot --manual DNS-01 run, and like that run it does not renew automatically while DNS stays outside CDN.com.tr. And if you hold an OV or EV certificate from another CA, you can upload it and attach it to a hostname; it gets the same TLS policy as an automatic one. The TLS guide explains what the edge negotiates, and HSTS is the next step once HTTPS is reliable.
Certbot FAQ
Is certbot free?
Yes. Certbot is open-source software from the EFF, and Let's Encrypt certificates cost nothing. You pay nothing per certificate or per renewal; the only limits are Let's Encrypt's rate limits.
How do I renew a certbot certificate manually?
Run sudo certbot renew. It renews every certificate that is due and skips the rest. To force one certificate early, use sudo certbot renew --cert-name example.com --force-renewal, but do not make that a habit: forced renewals count against the duplicate-certificate limit. Test first with sudo certbot renew --dry-run.
Where does certbot store certificates?
In /etc/letsencrypt/live/<cert-name>/. Point your server at fullchain.pem and privkey.pem. These are symlinks to the newest files in /etc/letsencrypt/archive/, so the paths stay the same after each renewal. Back up the whole /etc/letsencrypt directory, not just live/.
Can certbot issue a wildcard certificate?
Yes, but only through DNS-01: use a DNS plugin for your provider, or --manual --preferred-challenges dns. Request both example.com and '*.example.com', because the wildcard does not cover the bare domain. Manual wildcards do not renew by themselves unless you add hook scripts.
Does certbot work on Windows?
Not any more. Certbot discontinued Windows support in February 2024; 2.9.0 was the last release with a Windows installer. Use a Windows-native ACME client such as win-acme (or its successor simple-acme), Posh-ACME or Certify The Web, or run certbot inside WSL 2.
What is the difference between certbot --nginx and certbot certonly?
certbot --nginx obtains the certificate and edits your nginx configuration to use it, adding the redirect if you allow it. certbot certonly only obtains the certificate and saves it under /etc/letsencrypt/live/; you add the ssl_certificate lines yourself and a deploy hook to reload nginx after renewals.
Do I still need certbot if my site is behind a CDN?
Not for the certificate visitors see: the CDN serves its own at the edge. On CDN.com.tr, Auto SSL issues and renews a Let's Encrypt certificate for each connected domain. You may still use certbot on the origin if the edge connects to it over HTTPS.