ClouduxeDocs

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

Shell
dig +short A example.com

It 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:

Shell
apt update
apt install -y certbot python3-certbot-nginx

On RHEL, Alma, and Rocky, certbot comes from EPEL:

Shell
dnf install -y epel-release
dnf install -y certbot python3-certbot-nginx

If your distribution's certbot is too old for something you need, the project also ships a snap. Use one or the other, never both:

Shell
snap install --classic certbot
ln -s /snap/bin/certbot /usr/bin/certbot

Getting the certificate

Shell
certbot --nginx -d example.com -d www.example.com

The 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:

Shell
certbot certonly --webroot -w /var/www/example.com -d example.com

Or let certbot bind port 80 itself, which means stopping anything already using it:

Shell
systemctl stop nginx
certbot certonly --standalone -d example.com
systemctl start nginx

With certonly nothing reloads your service when the certificate changes, so add a hook. Create /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh:

Shell
#!/bin/sh
systemctl reload nginx

Make 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:

Shell
systemctl list-timers 'certbot*'
systemctl is-enabled certbot.timer

A 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:

Shell
certbot renew --dry-run

This 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:

Shell
certbot certificates

Common 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:

Shell
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.

Shell
dig +short A example.com

Retrying 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:

Shell
certbot renew --dry-run
certbot certonly --nginx --test-cert -d example.com

Both 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:

Shell
certbot delete --cert-name example.com

The 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.