Certbot is the official Let's Encrypt client: it obtains a free TLS certificate and patches your Nginx configuration for HTTPS itself. On a VDS this is the standard way to get the padlock in the address bar without buying a certificate.
Certbot — Free SSL for Nginx
Let's Encrypt issues certificates valid for 90 days and confirms domain ownership through one of two validation methods: HTTP (a file on the site) or DNS (a TXT record). The python3-certbot-nginx plugin uses HTTP validation and adds the listen 443 ssl blocks itself.
Installing Certbot
On Ubuntu 20.04+ and Debian 11+ the package is already in the standard repository, no separate PPA is needed:
apt update
apt install certbot python3-certbot-nginx -y
# Check the version
certbot --version
Getting a Certificate
The nginx plugin finds the matching server_name in your configuration by itself and inserts the SSL block there:
certbot --nginx -d yourdomain.com -d www.yourdomain.com
If Nginx is not configured yet, or the certificate is needed separately from editing the config — for example to verify things before a production switch — use certonly mode with the site's webroot:
certbot certonly --webroot -w /var/www/yourdomain.com -d yourdomain.com -d www.yourdomain.com
In that case the certificate lands in /etc/letsencrypt/live/yourdomain.com/, and the paths to fullchain.pem and privkey.pem need to be added to the server block by hand.
Auto-renewal
Certbot installs a systemd timer that checks the expiry twice a day and renews any certificate with fewer than 30 days left:
systemctl status certbot.timer
systemctl list-timers | grep certbot
Renewal does not require re-entering the domain and does not touch the rest of the Nginx configuration — only the files under /etc/letsencrypt/live/ are updated, and Nginx picks them up after the reload certbot runs automatically through its deploy hook.
Forcing a Renewal Test
# A dry run without an actual renewal
certbot renew --dry-run
# Log of past attempts
cat /var/log/letsencrypt/letsencrypt.log
Comparing Ways to Get an SSL Certificate
| Method | Validation | Wildcard domains | Cost and term |
|---|---|---|---|
| Certbot + nginx plugin | HTTP-01, automatic | No | Free, 90 days, auto-renewal |
| Certbot + DNS plugin | DNS-01, via the DNS provider's API | Yes | Free, 90 days, auto-renewal |
| Certbot certonly --webroot | HTTP-01, without editing Nginx | No | Free, 90 days |
| Paid certificate (OV/EV) | Manual organization verification | Usually yes | Paid, 1–2 years |
A wildcard certificate for *.yourdomain.com requires DNS validation, because confirming ownership of every possible subdomain at once over HTTP is not possible — the full process is covered in the article on wildcard certificates with Certbot.
Common Errors Explained
The error Could not bind TCP port 80 because it is already in use shows up when certbot in standalone mode tries to spin up its own temporary web server on port 80 while Nginx already holds it. The fix is to use the --nginx or --webroot plugin instead of standalone — they work through the already-running server rather than replacing it.
Failed authorization procedure. yourdomain.com (http-01): urn:ietf:params:acme:error:connection ::
Fetching http://yourdomain.com/.well-known/acme-challenge/xxx: Connection refused
This error almost always means port 80 is blocked by a firewall, or the domain's DNS record does not point to the current server yet. Check ufw status and the result of dig yourdomain.com +short before trying again.
The too many certificates already issued limit — Let's Encrypt enforces a cap of 5 identical certificates for the exact same set of domains per 7 days, and 50 certificates per registered domain per week. This limit is caught during testing through the staging environment — add --staging to the command to debug the process without spending your production certificate quota.
Manually Verifying the Certificate
After issuance it is worth checking the certificate independently of Certbot, from a client's point of view:
# Validity period and issuer
openssl x509 -in /etc/letsencrypt/live/yourdomain.com/cert.pem -noout -dates -issuer
# The full chain exactly as a browser sees it
curl -vI https://yourdomain.com 2>&1 | grep -i "SSL certificate"
When Certbot Does Not Fit
- A domain with no public DNS record (an internal host, a test setup behind a VPN) — HTTP-01 validation will not pass, and you need a self-signed certificate or an internal CA instead.
- The site is fully proxied through Cloudflare in Full (strict) mode — then the certificate needs to come from either Cloudflare Origin CA, or the same Let's Encrypt but via DNS-01 through Cloudflare's API rather than HTTP-01 directly.
- Several servers behind a load balancer need the certificate synced across nodes — Certbot does not handle that by itself, you need shared storage or a dedicated issuing node.
- Businesses sometimes need a certificate with organization validation (OV/EV) to satisfy partner requirements — Let's Encrypt does not issue those at all, only DV.
Verifying the Result
- Open the site over
https://in a browser — the address bar should show a closed padlock with no warnings. - Check the redirect from HTTP to HTTPS:
curl -I http://yourdomain.comshould return a301or308to the https address. - Check the auto-renewal timer:
systemctl is-enabled certbot.timershould returnenabled. - Use an external TLS-checking service to confirm the full certificate chain is served, not just the domain's own certificate — some browsers fill in the intermediate certificate themselves, but not all.
If, after installing the certificate, you need to fine-tune the HTTPS block itself — protocols, ciphers, HSTS — that is a separate topic covered in the article on configuring Nginx for a site on a VDS.