Install
Snap is what Certbot's own docs recommend, because it always carries the current release. Distro packages lag behind but update with the rest of the system. The plugin for your web server is a separate package either way.
| Command | What it does |
|---|---|
| Install the current Certbot on any distro with snap | |
| Put the snap on ; skip this and you get "certbot: command not found" | |
| Debian and Ubuntu, with the nginx plugin | |
| Debian and Ubuntu, with the Apache plugin | |
| Fedora, RHEL, Rocky, Alma | |
| macOS, for local or DNS-only use | |
| Confirm what you actually have |
Without snap, a virtualenv keeps Certbot off the system Python:
sudo python3 -m venv /opt/certbot
sudo /opt/certbot/bin/pip install certbot certbot-nginx
sudo ln -s /opt/certbot/bin/certbot /usr/bin/certbotA pip or virtualenv install schedules nothing, so add the renewal cron entry yourself. Snap and distro packages both bring their own timer.
nginx
The nginx plugin is the usual route: it proves control of the domain, fetches the certificate, and edits the matching block in one go. It needs an existing block whose matches, which the nginx config generator can produce.
| Command | What it does |
|---|---|
| Get and install a certificate, picking domains from your nginx config | |
| Get and install for the listed domains | |
| Get the certificate only; leave nginx config untouched | |
| Wire an already-issued certificate into nginx | |
| / | Force or skip the HTTP to HTTPS redirect (redirect is the default since Certbot 2.0) |
| Check and reload nginx after editing by hand |
Confirm the result from outside with the SSL checker.
Apache
Same idea with the Apache plugin. On Debian and Ubuntu the service is ; on RHEL and friends it is .
| Command | What it does |
|---|---|
| Get and install, picking domains from your VirtualHosts | |
| Get and install for the listed domains | |
| Get the certificate only; leave Apache config untouched | |
| Check the config before reloading | |
| Load the new certificate |
certonly
obtains or renews a certificate without touching any web server config; you point the server at the files yourself. Pick one authenticator per run.
| Command | What it does |
|---|---|
| Prove control through the running nginx | |
| Prove control through the running Apache | |
| Certbot runs its own temporary server on port 80 | |
| Drop challenge files into an existing site | |
| Name the certificate instead of using the first domain |
The files land in as and . Inspect what you got with the certificate decoder or from the OpenSSL cheat sheet.
Standalone
Standalone binds its own listener on port 80, so it works on machines with no web server at all: mail servers, database hosts, brokers. Whatever already holds port 80 must stop first.
| Command | What it does |
|---|---|
| Get a certificate with certbot's built-in server | |
| See what is currently holding port 80 | |
| Listen locally on another port behind a proxy; Let's Encrypt still connects to 80 |
If a web server owns port 80, stop it around the run and restart it after:
sudo certbot certonly --standalone -d mail.example.com \
--pre-hook "systemctl stop nginx" --post-hook "systemctl start nginx"Certbot remembers the hooks and reuses them at renewal, so the stop-start happens automatically from then on.
Webroot
Webroot never takes the site down: certbot writes the challenge into under the directory you name, and Let's Encrypt fetches it over plain HTTP.
| Command | What it does |
|---|---|
| Get a certificate using the live site's document root | |
| A second applies to the flags after it |
One certificate for several sites, each with its own root:
sudo certbot certonly --webroot \
-w /var/www/html -d example.com -d www.example.com \
-w /var/www/blog -d blog.example.comThe path must be served over HTTP without a redirect to somewhere the challenge is not. A on during the run means points at the wrong directory.
DNS Challenge
The dns-01 challenge proves control with a TXT record, so it needs no open port at all, works behind a CDN, and is the only challenge that can issue wildcards.
| Command | What it does |
|---|---|
| Ask for the DNS challenge | |
| Check the record is visible before continuing | |
| Let snap-installed plugins run with certbot | |
| Install a DNS plugin (one exists per major provider) |
Manual, for a one-off certificate:
sudo certbot certonly --manual --preferred-challenges dns -d example.comCertbot prints the TXT value, you add it at your DNS host, wait for it to propagate, and press Enter. A manual certificate cannot renew unattended unless you also supply .
Automated, with a provider plugin and an API credentials file (chmod 600):
sudo certbot certonly --dns-cloudflare \
--dns-cloudflare-credentials ~/.secrets/cloudflare.ini \
-d example.comSlow DNS host? Add (each plugin has its own version of the flag).
Every plugin follows the same three-part pattern: a package, a flag, and a credentials file.
| Plugin | Flag |
|---|---|
| (reads standard AWS credentials) | |
| (BIND and any RFC 2136 server) |
Registrars with no official plugin, GoDaddy, Namecheap and Porkbun among them, have community plugins on PyPI, or you can delegate with a CNAME to a provider that does have one.
Wildcard Certificates
Wildcards come only from the DNS challenge above.
sudo certbot certonly --manual --preferred-challenges dns \
-d example.com -d "*.example.com"- Quote so the shell does not try to expand it.
- Include the bare domain: does not cover .
- A wildcard covers one level only: yes, no.
- For automatic renewal, use a DNS plugin instead of .
Renewal
Packaged installs (snap, apt, dnf) add a systemd timer or cron job that runs twice a day. Each certificate renews once it is within 30 days of expiry, reusing the exact options it was created with, so you never repeat or .
| Command | What it does |
|---|---|
| Renew everything that is close to expiry | |
| Full rehearsal against the staging server, changes nothing | |
| Renew one certificate | |
| Renew now regardless of expiry; rate limits apply | |
| Confirm the renewal timer is scheduled | |
| See the stored renewal settings for one certificate |
The timer is a normal systemd unit, so the systemctl cheat sheet applies if it is missing or dead.
Add and Remove Domains
There is no "add domain" subcommand. You reissue the certificate with the full list of names you want and pin so it replaces the existing one instead of creating a second certificate under a new directory.
| Command | What it does |
|---|---|
| Reissue with an extra name, same file paths | |
| Skip the "expand this certificate?" prompt, for scripts | |
| Drop every other name by reissuing with a shorter list | |
| Issue with whatever names validate, instead of failing on one bad domain | |
| Remove a whole certificate and stop renewing it |
Get the current name list before you edit it, since leaving a domain out of the reissue removes it:
sudo certbot certificates --cert-name example.comThe live directory keeps its original name, so stays correct in nginx or Apache no matter which domains you add.
List and Delete
| Command | What it does |
|---|---|
| List every certificate with domains, expiry, and file paths | |
| Details for one certificate | |
| Remove a certificate and its renewal config | |
| Pick what to delete from a list | |
| Tell Let's Encrypt the certificate is no longer valid | |
| Why you are revoking (with ) |
does not touch your web server config. Remove the lines that point at the deleted files first, or nginx refuses to start on the next reload.
Deploy Hooks
Nginx and Apache load certificate files at startup and never look again, so a renewed file on disk changes nothing until a reload. A deploy hook runs after each successful issue or renewal, which is exactly the moment for it.
| Command | What it does |
|---|---|
| Run a command after each successful renewal of this certificate | |
| Executable scripts here run when any certificate renews | |
| / | Run around every renewal attempt, successful or not |
| Include deploy hooks in a dry run (skipped otherwise) | |
| , | Set inside a deploy hook: the live directory path and the domains |
Add a hook to an existing certificate without reissuing it:
sudo certbot reconfigure --cert-name example.com \
--deploy-hook "systemctl reload nginx"Files and Logs
Everything Certbot owns lives under , and every run appends to one log file. That log carries the real ACME error text, which is usually far more specific than what the terminal printed.
| Path | What it holds |
|---|---|
| and , the two files a web server points at | |
| Every version ever issued; the live files are symlinks in here | |
| The stored flags a renewal replays | |
| Defaults applied to every run, e.g. or | |
| Account keys, the thing worth backing up | |
| Full output of the most recent run, older runs rotate to |
| Command | What it does |
|---|---|
| Read why the last run failed | |
| Jump straight to the failure lines | |
| Output of the snap renewal timer | |
| Output of the deb or rpm renewal timer | |
| / | More detail on screen during a run |
Back up as a whole, with symlinks preserved (), or the restored copy points nowhere. See the tar cheat sheet.
Windows
Certbot no longer ships a native Windows build; EFF stopped producing the Windows installer during the 2.x series, and the old beta downloads are unsupported. Three options remain.
| Option | What it means |
|---|---|
| WSL | Install Certbot in an Ubuntu WSL distro and copy the files to where Windows needs them |
| win-acme | The maintained native Windows ACME client, with an IIS installer built in |
| posh-acme | A PowerShell ACME client, good for scripted or DNS-only issuance |
IIS wants a , not the pair, so convert with from the OpenSSL cheat sheet.
Account and Version
| Command | What it does |
|---|---|
| Create a Let's Encrypt account (normally happens on first run) | |
| Show the account this machine uses | |
| Change the contact email for expiry warnings | |
| Deactivate the account | |
| Print the installed version | |
| The full option reference | |
| Update a snap install (apt and dnf installs update with the system) |
Docker
The official image is , and its entrypoint is certbot itself, so everything after the image name is a normal certbot command. Mount or the certificates die with the container.
docker run -it --rm -p 80:80 \
-v /etc/letsencrypt:/etc/letsencrypt \
certbot/certbot certonly --standalone -d example.comRenewal is the same image with ; there is no timer inside a container, so schedule it yourself from host cron:
docker run --rm -p 80:80 \
-v /etc/letsencrypt:/etc/letsencrypt \
certbot/certbot renew| Image | What it is |
|---|---|
| The base image | |
| Base image plus the Cloudflare DNS plugin (one image per provider) |
For webroot next to an nginx container, mount the same volume in both containers as the challenge directory. Docker basics are on the Docker cheat sheet.
Command Line Options
| Option | What it does |
|---|---|
| A domain to include; repeat for more | |
| Which certificate to create or act on | |
| / | Never prompt; fail instead (scripts) |
| Accept the terms of service (needed with ) | |
| Contact email for the account | |
| Use the staging server: untrusted test certs, generous rate limits | |
| Staging test that also saves nothing to disk | |
| Add new domains to an existing certificate | |
| Pick the challenge type (, ) | |
| RSA instead of the default ECDSA (default since Certbot 2.0) | |
| Larger RSA key, with | |
| / | Errors only, for cron output |
| More detail ( for debugging) | |
| / / | Alternate directories, e.g. to run without root |
Gotchas
- Let's Encrypt rate limits are real: five identical certificates per week. Test with or , and never put in cron.
- After a renewal your server still serves the old certificate until it reloads. If the SSL checker shows an expired certificate but shows a fresh one, the missing piece is a deploy hook.
- Everything in is a symlink. Point your config there and never copy the files out; copies stop updating.
- The HTTP challenge needs port 80 reachable from the internet. A closed firewall gives "Timeout during connect"; open it with (UFW cheat sheet) and confirm from outside with the port checker. Behind Cloudflare or another CDN, use the DNS challenge instead.
- Failed validations have their own limit, five per account, hostname and hour. Retrying a broken challenge in a loop earns "too many failed authorizations recently" and an hour of lockout, so read and fix the cause between attempts.
- and are long dead. Install from your package manager or snap ().