Let's Encrypt is a free, automated certificate authority that issues domain-validated TLS certificates through the ACME protocol. Certbot is the reference ACME client: it proves you control a domain, installs the certificate in your web server and renews it before it expires. In this tutorial you will use Certbot on Ubuntu 24.04 to enable HTTPS on Nginx (with the Apache equivalent), redirect HTTP to HTTPS, confirm that automatic renewal works and, optionally, issue a wildcard certificate with a DNS challenge.

Prerequisites

To follow this tutorial you need:

  • A server running Ubuntu 24.04 LTS, for example a CubePath VPS, with a non-root user that has sudo privileges.
  • A registered domain name. This guide uses your_domain as a placeholder; replace it everywhere with your real domain.
  • DNS A records (and AAAA if the server has IPv6) for your_domain and www.your_domain pointing to your server's public IP address.
  • Nginx installed with a server block for your_domain, or Apache with a virtual host for it. Certbot reads the server_name (Nginx) or ServerName/ServerAlias (Apache) directives to know where to install the certificate.
  • Ports 80 and 443 open. Let's Encrypt validates the HTTP-01 challenge over port 80, so it must be reachable from the internet even if you plan to serve only HTTPS.

Check that DNS already resolves to your server before continuing, because failed validations count against Let's Encrypt rate limits:

dig +short your_domain
dig +short www.your_domain

Both commands should print your server's public IP address.

Step 1 - Installing Certbot

Ubuntu 24.04 ships Certbot and its web server plugins in the universe repository. Install Certbot together with the plugin for your web server.

For Nginx:

sudo apt update
sudo apt install certbot python3-certbot-nginx

For Apache:

sudo apt update
sudo apt install certbot python3-certbot-apache

Confirm that Certbot is installed:

certbot --version
certbot 2.9.0

Step 2 - Allowing HTTPS through the firewall

If UFW is active, allow both HTTP and HTTPS. The Nginx and Apache packages register UFW application profiles for this.

For Nginx:

sudo ufw allow 'Nginx Full'
sudo ufw delete allow 'Nginx HTTP'

For Apache:

sudo ufw allow 'Apache Full'
sudo ufw delete allow 'Apache'

The delete command removes the old HTTP-only rule if it exists; if it doesn't, UFW reports that and nothing changes. Verify the result:

sudo ufw status
Status: active

To                         Action      From
--                         ------      ----
OpenSSH                    ALLOW       Anywhere
Nginx Full                 ALLOW       Anywhere
OpenSSH (v6)               ALLOW       Anywhere (v6)
Nginx Full (v6)            ALLOW       Anywhere (v6)

Step 3 - Checking the web server configuration

Certbot finds the right server block by matching the domain names you request against server_name. Open your site's configuration:

sudo nano /etc/nginx/sites-available/your_domain

Make sure it contains a line like this:

server_name your_domain www.your_domain;

Test the syntax and reload Nginx:

sudo nginx -t
sudo systemctl reload nginx
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

On Apache, the equivalent directives are ServerName your_domain and ServerAlias www.your_domain in /etc/apache2/sites-available/your_domain.conf. Check them with sudo apache2ctl configtest.

Step 4 - Obtaining the certificate

Run Certbot with the plugin for your web server. It performs the HTTP-01 challenge, obtains the certificate, adds the TLS directives to your configuration and reloads the server.

For Nginx:

sudo certbot --nginx -d your_domain -d www.your_domain

For Apache:

sudo certbot --apache -d your_domain -d www.your_domain

The first time you run Certbot it asks for an email address for important account notices and asks you to accept the Let's Encrypt subscriber agreement. When it finishes you will see something like this:

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/your_domain/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/your_domain/privkey.pem
This certificate expires on 2026-12-24.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.

Deploying certificate
Successfully deployed certificate for your_domain to /etc/nginx/sites-enabled/your_domain
Successfully deployed certificate for www.your_domain to /etc/nginx/sites-enabled/your_domain
Congratulations! You have successfully enabled HTTPS on https://your_domain and https://www.your_domain

Current Certbot versions configure the HTTP to HTTPS redirect automatically. If you open the Nginx configuration again, you will see the lines Certbot added, marked with # managed by Certbot:

listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/your_domain/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/your_domain/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

The included options-ssl-nginx.conf sets sensible protocol and cipher defaults, so you don't need to add your own TLS tuning to get a secure configuration.

Step 5 - Verifying HTTPS

Request the site over HTTP and check that it redirects:

curl -sI http://your_domain | head -n 3
HTTP/1.1 301 Moved Permanently
Server: nginx/1.24.0 (Ubuntu)
Date: Thu, 25 Sep 2026 10:00:00 GMT

Then inspect the certificate the server presents:

echo | openssl s_client -connect your_domain:443 -servername your_domain 2>/dev/null | openssl x509 -noout -issuer -subject -dates
issuer=C = US, O = Let's Encrypt, CN = E7
subject=CN = your_domain
notBefore=Sep 25 09:00:00 2026 GMT
notAfter=Dec 24 09:00:00 2026 GMT

The issuer's organization should be Let's Encrypt (the intermediate's common name is a short code that changes over time), and notAfter should be about 90 days in the future. Finally, open https://your_domain in a browser and check the padlock.

You can list every certificate Certbot manages at any time:

sudo certbot certificates
Found the following certs:
  Certificate Name: your_domain
    Serial Number: 4a3f...
    Key Type: ECDSA
    Domains: your_domain www.your_domain
    Expiry Date: 2026-12-24 09:00:00+00:00 (VALID: 89 days)
    Certificate Path: /etc/letsencrypt/live/your_domain/fullchain.pem
    Private Key Path: /etc/letsencrypt/live/your_domain/privkey.pem

Step 6 - Testing automatic renewal

Let's Encrypt certificates are valid for 90 days. The Ubuntu certbot package installs a systemd timer that runs certbot renew twice a day; it renews any certificate that expires within 30 days and does nothing otherwise. Check that the timer is active:

systemctl list-timers certbot.timer
NEXT                        LEFT     LAST                        PASSED  UNIT          ACTIVATES
Thu 2026-09-25 21:43:00 UTC 11h left Thu 2026-09-25 09:12:00 UTC 49min ago certbot.timer certbot.service

Simulate a renewal against the Let's Encrypt staging environment. This doesn't replace your certificate and doesn't count against production rate limits:

sudo certbot renew --dry-run
Congratulations, all simulated renewals succeeded:
  /etc/letsencrypt/live/your_domain/fullchain.pem (success)

Certificates obtained with the --nginx or --apache installer reload the web server on renewal automatically. If you obtained a certificate with certonly and another service uses it (a mail server, for example), add a deploy hook. Scripts in /etc/letsencrypt/renewal-hooks/deploy/ run only after a certificate is actually renewed:

sudo nano /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
#!/usr/bin/env bash
set -euo pipefail
systemctl reload nginx

Make it executable:

sudo chmod 755 /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh

Deploy hooks are skipped during --dry-run. To test the hook itself, run sudo /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh and check systemctl status nginx.

Step 7 - Issuing a wildcard certificate (optional)

A wildcard certificate such as *.your_domain covers every first-level subdomain. Let's Encrypt only issues wildcards through the DNS-01 challenge, where Certbot creates a TXT record in your DNS zone. This requires a DNS plugin for your DNS provider. This example uses Cloudflare; Ubuntu also packages plugins for other providers, for example python3-certbot-dns-route53 and python3-certbot-dns-digitalocean.

Install the plugin:

sudo apt install python3-certbot-dns-cloudflare

Create an API token in the Cloudflare dashboard with the Zone:DNS:Edit permission limited to your zone. Store it in a credentials file readable only by root:

sudo mkdir -p /etc/letsencrypt/secrets
sudo nano /etc/letsencrypt/secrets/cloudflare.ini
dns_cloudflare_api_token = your_cloudflare_api_token
sudo chmod 600 /etc/letsencrypt/secrets/cloudflare.ini

Request the certificate for the apex domain and the wildcard:

sudo certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /etc/letsencrypt/secrets/cloudflare.ini \
  -d your_domain \
  -d '*.your_domain'

Quote '*.your_domain' so the shell doesn't expand the asterisk. Because this uses certonly, point your server blocks to /etc/letsencrypt/live/your_domain/fullchain.pem and privkey.pem yourself and add the deploy hook from Step 6. Renewal uses the same DNS plugin and credentials automatically; confirm it with sudo certbot renew --dry-run.

Managing certificates

A few commands you will use later:

  • Add a domain to an existing certificate: run the original command again with the full list of domains, for example sudo certbot --nginx --cert-name your_domain -d your_domain -d www.your_domain -d blog.your_domain.
  • Delete a certificate you no longer need (remove references to it from your web server configuration first): sudo certbot delete --cert-name your_domain.
  • Revoke a compromised certificate: sudo certbot revoke --cert-name your_domain.

Never edit or move the files in /etc/letsencrypt/live/ directly. They are symlinks into /etc/letsencrypt/archive/, and Certbot relies on that layout to renew. If you back up the server, include the whole /etc/letsencrypt/ directory.

Troubleshooting

Challenge failed with "Timeout during connect" or "Connection refused". Let's Encrypt couldn't reach port 80. Check sudo ufw status, any firewall in front of the server, and that Nginx is listening with sudo ss -tlnp | grep ':80'.

"No valid A records found" or "NXDOMAIN". The DNS record doesn't exist or hasn't propagated. Compare dig +short your_domain @1.1.1.1 with your server's IP address. If you recently added an AAAA record, make sure it points to this server and that Nginx listens on IPv6 (listen [::]:80;), because Let's Encrypt prefers IPv6 when it is published.

"Could not automatically find a matching server block". No server block has a server_name matching the requested domain. Fix the server_name line, reload Nginx and run Certbot again.

"too many certificates already issued" or "too many failed authorizations". You hit a Let's Encrypt rate limit. Limits reset after a period of time. While debugging, add --dry-run (or --test-cert for certonly) so Certbot uses the staging environment, which has much higher limits.

Renewal fails for a certificate you created months ago. Run sudo certbot renew --dry-run and read /var/log/letsencrypt/letsencrypt.log. The most common causes are a server block that was removed, a port 80 rule that was closed, or DNS that moved to another server.

Conclusion

Your site now serves HTTPS with a free Let's Encrypt certificate, HTTP requests are redirected, and a systemd timer renews the certificate automatically. Run sudo certbot renew --dry-run after any change to DNS, firewall rules or server blocks to catch renewal problems early. As next steps, enable HTTP/2 on your web server, add a Strict-Transport-Security header once you're sure the whole site works over HTTPS, and test your configuration with the SSL Labs Server Test.