Getting a free SSL certificate with Let's Encrypt
certbot with the nginx plugin, what the HTTP-01 challenge needs open, checking that renewal works, and fixing the usual failures.
Let's Encrypt issues free certificates that browsers trust, and certbot requests, installs, and renews them for you. The whole process depends on Let's Encrypt being able to reach your server from the internet on port 80, so get that right first and the rest is one command.
Before you start
Three things have to be true. Checking them takes a minute and saves most of the failures further down this page.
The domain resolves to this server
dig +short A example.comIt has to print your server's address, for example 203.0.113.10. If dig is
missing, install dnsutils on Debian and Ubuntu or bind-utils on the RHEL
family.
Port 80 is open to everyone
Open it in the host firewall and add an inbound rule for TCP 80 in the panel. See Using a firewall on your Linux server and Port rules. Open TCP 443 at the same time, or the certificate will have nothing to protect.
nginx already serves the name
The server block needs a server_name matching the name you are requesting. See
Installing and configuring nginx. The nginx plugin edits an
existing site; it does not invent one.
Installing certbot
On Debian and Ubuntu:
apt update
apt install -y certbot python3-certbot-nginxOn RHEL, Alma, and Rocky, certbot comes from EPEL:
dnf install -y epel-release
dnf install -y certbot python3-certbot-nginxIf your distribution's certbot is too old for something you need, the project also ships a snap. Use one or the other, never both:
snap install --classic certbot
ln -s /snap/bin/certbot /usr/bin/certbotGetting the certificate
certbot --nginx -d example.com -d www.example.comThe first run asks for an email address, which is where expiry warnings go, and
for agreement to the terms. Then it proves you control each name, writes the
certificate, edits your server block to add listen 443 ssl; and the
ssl_certificate lines, offers to redirect HTTP to HTTPS, and reloads nginx.
Every name you want on the certificate needs its own -d, and every one of them
is validated separately. One name that does not resolve fails the whole request.
The files land in /etc/letsencrypt/live/example.com/. fullchain.pem and
privkey.pem there are symlinks that always point at the current certificate, so
reference those paths in any configuration you write by hand. Never point at
/etc/letsencrypt/archive/, because those filenames change at every renewal and
your service will keep serving the old certificate until it expires.
What the HTTP-01 challenge needs
certbot writes a file on your server, and Let's Encrypt fetches it from the
public internet at http://example.com/.well-known/acme-challenge/ followed by a
one-time token. Three consequences follow from that, and they explain nearly
every failure:
- The name has to resolve publicly to this server. Not to your old host, not to a proxy, and not only inside your own network.
- Port 80 has to be open to the whole internet, not only to your address. This is true even for a site you intend to serve on HTTPS only. Redirects are followed, so an existing HTTP to HTTPS redirect is fine, but the first request is plain HTTP on port 80.
- A wildcard certificate cannot be issued this way. Wildcards require the DNS-01 challenge, which proves control by publishing a TXT record instead, and needs your DNS provider rather than your server.
When nginx is not the thing being secured
For a service that is not a web server, get the certificate on its own and wire the paths in yourself.
Keep nginx running and let it serve the challenge from a directory:
certbot certonly --webroot -w /var/www/example.com -d example.comOr let certbot bind port 80 itself, which means stopping anything already using it:
systemctl stop nginx
certbot certonly --standalone -d example.com
systemctl start nginxWith certonly nothing reloads your service when the certificate changes, so add
a hook. Create /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh:
#!/bin/sh
systemctl reload nginxMake it executable with chmod +x. Anything in that directory runs after a
successful renewal, for every certificate.
Automatic renewal, and checking it actually works
Certificates last 90 days and certbot renews them when roughly a third of that is left, so a renewal attempt failing once is not an emergency. Silent failure for a month is, which is why this section exists.
The package installs a systemd timer. Confirm it is enabled and scheduled:
systemctl list-timers 'certbot*'
systemctl is-enabled certbot.timerA snap install uses snap.certbot.renew.timer instead, and some systems use a
cron entry at /etc/cron.d/certbot. You need one of the three, not all of them.
Then prove the renewal itself works, end to end:
certbot renew --dry-runThis performs the whole process against Let's Encrypt's staging service. It exercises the challenge exactly as a real renewal would, without replacing your certificate and without counting against the real rate limits. Run it after any change to your firewall, your panel rules, or your nginx configuration, because those are what break renewals months later.
To see what you hold and when it expires:
certbot certificatesCommon failures
Port 80 is closed
The error mentions a timeout during connect, or a connection refused, on the challenge. The traffic has to pass the host firewall and the panel's rules, and either one alone is enough to stop it.
Test from a different machine, not from the server:
curl -I http://example.com/A request made on the server never leaves it, so it proves nothing about what the internet can reach.
DNS is not pointing here yet
The error names a DNS problem, often NXDOMAIN, or the challenge is fetched from a server that is not yours. Check the record and wait for the previous record's TTL to expire before retrying.
dig +short A example.comRetrying in a loop while you wait does not make DNS propagate faster, and it does spend the allowance described next.
Rate limits
Let's Encrypt limits how often certificates are issued for the same set of names, and limits repeated failed validations considerably more tightly. Once you are blocked, you wait: nothing on the Clouduxe side changes it, and no support request can lift it, because the limit is enforced by Let's Encrypt.
Avoid it rather than recover from it. While something is broken, debug with:
certbot renew --dry-run
certbot certonly --nginx --test-cert -d example.comBoth use the staging service, whose limits are far higher. A --test-cert
certificate is not trusted by browsers, so remove it before requesting the real
one:
certbot delete --cert-name example.comThe plugin cannot find your site
certbot reports that it could not find a virtual host controlled by nginx. The
name after -d does not match any server_name in your configuration. Add or
correct the server block, run nginx -t, reload, and request again.