Skip to main content

Installing SSL on Nginx with Certbot

VDS / VPS · 22.02.2025 · 5 min read
Illustration for “Installing SSL on Nginx with Certbot”

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

MethodValidationWildcard domainsCost and term
Certbot + nginx pluginHTTP-01, automaticNoFree, 90 days, auto-renewal
Certbot + DNS pluginDNS-01, via the DNS provider's APIYesFree, 90 days, auto-renewal
Certbot certonly --webrootHTTP-01, without editing NginxNoFree, 90 days
Paid certificate (OV/EV)Manual organization verificationUsually yesPaid, 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

  1. Open the site over https:// in a browser — the address bar should show a closed padlock with no warnings.
  2. Check the redirect from HTTP to HTTPS: curl -I http://yourdomain.com should return a 301 or 308 to the https address.
  3. Check the auto-renewal timer: systemctl is-enabled certbot.timer should return enabled.
  4. 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.

Was this article helpful?
← Back to Knowledge Base Ask Support