Securing your website with HTTPS is no longer optional. Search engines penalize unencrypted sites, browsers display warning messages to visitors, and data transmitted without TLS is vulnerable to interception. Let’s Encrypt eliminated the cost barrier by providing free, automated, and open SSL/TLS certificates. Certbot is the recommended client for obtaining and managing those certificates on Linux servers.

This guide walks you through every step — from installation to production-hardened configuration — on Ubuntu 22.04 and 24.04.

Prerequisites

Before you begin, ensure you have the following in place:

  • Ubuntu Server 22.04 LTS or 24.04 LTS with root or sudo access.
  • A registered domain name (e.g., example.com) with DNS A/AAAA records pointing to your server’s public IP address.
  • Nginx or Apache installed and serving at least one virtual host (server block) for your domain.
  • Port 80 open on your firewall. Let’s Encrypt uses HTTP-01 challenges on port 80 by default.
  • Port 443 open for HTTPS traffic after the certificate is installed.

Verify your firewall allows the required ports:

sudo ufw status
sudo ufw allow 'Nginx Full'   # Or 'Apache Full' for Apache

Confirm your domain resolves to the server:

dig +short A example.com
dig +short AAAA example.com

Both commands should return your server’s IP address.

How Let’s Encrypt Works

Let’s Encrypt is a free, automated, and open Certificate Authority (CA) operated by the Internet Security Research Group (ISRG). It issues Domain Validation (DV) certificates using the ACME protocol (Automatic Certificate Management Environment).

The ACME Protocol

The certificate issuance process follows these steps:

  1. Account Registration — Certbot creates an account with the Let’s Encrypt ACME server and agrees to the terms of service.
  2. Domain Authorization — The ACME server sends challenges to prove you control the domain. The two most common challenge types are:
    • HTTP-01 — Certbot places a token file at http://example.com/.well-known/acme-challenge/<TOKEN>. The ACME server fetches that URL to verify ownership.
    • DNS-01 — You create a _acme-challenge.example.com TXT record with a specific value. Required for wildcard certificates.
  3. Certificate Issuance — Once the challenge is validated, the ACME server signs your certificate and returns it.
  4. Renewal — Certificates are valid for 90 days. Certbot automates renewal via a systemd timer or cron job.

Why 90-Day Certificates?

The short validity period is an intentional design choice:

  • Forces automation, reducing human error in certificate management.
  • Limits exposure if a private key is compromised.
  • Encourages modern security practices across the ecosystem.

Installing Certbot

On Ubuntu 22.04 and 24.04, Certbot is available directly from the default repositories. The snap package is the officially recommended installation method.

# Remove any older OS-packaged certbot to avoid conflicts
sudo apt remove certbot -y

# Install Certbot via snap
sudo snap install --classic certbot

# Create a symlink so certbot is in your PATH
sudo ln -s /snap/bin/certbot /usr/bin/certbot

Method 2: APT (Alternative)

If you prefer the system package manager:

sudo apt update
sudo apt install certbot -y

For web server integration, install the corresponding plugin:

# For Nginx
sudo apt install python3-certbot-nginx -y

# For Apache
sudo apt install python3-certbot-apache -y

Verify the installation:

certbot --version

Obtaining Certificates

Certbot supports multiple methods for obtaining certificates. Choose the one that matches your environment.

Option 1: Nginx Plugin

The Nginx plugin automatically modifies your Nginx configuration to serve the certificate and redirect HTTP to HTTPS.

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

Certbot will:

  1. Verify your Nginx configuration and locate the matching server block.
  2. Perform the HTTP-01 challenge.
  3. Download the certificate and private key.
  4. Update the server block with ssl_certificate and ssl_certificate_key directives.
  5. Optionally configure an HTTP-to-HTTPS redirect.

You will be prompted to enter an email address for renewal notifications and to agree to the terms of service.

Option 2: Apache Plugin

The Apache plugin works the same way for Apache virtual hosts:

sudo certbot --apache -d example.com -d www.example.com

Certbot locates the matching <VirtualHost> block, configures SSL, and enables the ssl module if it is not already active.

Option 3: Standalone Mode

If no web server is running (or you want to use Certbot independently), the standalone mode spins up a temporary server on port 80:

sudo certbot certonly --standalone -d example.com -d www.example.com

Important: Stop any service listening on port 80 before running this command. Certbot needs exclusive access to the port.

This mode only obtains the certificate — you must configure your web server manually afterward.

Option 4: Webroot Mode

If your web server is already running and you do not want to stop it, use webroot mode. Certbot places the challenge files in a directory your web server already serves:

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

Ensure your web server is configured to serve files from the specified webroot. For Nginx:

server {
    listen 80;
    server_name example.com www.example.com;

    location /.well-known/acme-challenge/ {
        root /var/www/html;
    }
}

Non-Interactive Mode

For scripting and automation, use the --non-interactive flag along with --agree-tos and --email:

sudo certbot --nginx --non-interactive --agree-tos \
  --email admin@example.com \
  -d example.com -d www.example.com

Wildcard Certificates with DNS Challenge

Wildcard certificates cover all subdomains at a single level (e.g., *.example.com). Let’s Encrypt requires the DNS-01 challenge for wildcards because the HTTP-01 challenge cannot validate arbitrary subdomains.

Manual DNS Challenge

sudo certbot certonly --manual --preferred-challenges dns \
  -d example.com -d "*.example.com"

Certbot will display a value and instruct you to create a TXT record:

Please deploy a DNS TXT record under the name:
_acme-challenge.example.com
with the following value:
gfj9Xq...Rg5nTzA

Log in to your DNS provider, create the record, and wait for propagation before pressing Enter.

Verify the TXT record before confirming:

dig +short TXT _acme-challenge.example.com

Automated DNS Challenge with Plugins

Manual DNS challenges do not support automatic renewal. For production, use a DNS plugin that modifies records via your provider’s API.

Cloudflare Example

sudo snap install certbot-dns-cloudflare

Create a credentials file:

sudo mkdir -p /etc/letsencrypt/cloudflare
sudo tee /etc/letsencrypt/cloudflare/credentials.ini > /dev/null <<EOF
dns_cloudflare_api_token = YOUR_CLOUDFLARE_API_TOKEN
EOF
sudo chmod 600 /etc/letsencrypt/cloudflare/credentials.ini

Obtain the wildcard certificate:

sudo certbot certonly --dns-cloudflare \
  --dns-cloudflare-credentials /etc/letsencrypt/cloudflare/credentials.ini \
  -d example.com -d "*.example.com"

Other Supported DNS Plugins

PluginProviderInstall Command
certbot-dns-cloudflareCloudflaresudo snap install certbot-dns-cloudflare
certbot-dns-route53AWS Route 53sudo snap install certbot-dns-route53
certbot-dns-digitaloceanDigitalOceansudo snap install certbot-dns-digitalocean
certbot-dns-googleGoogle Cloud DNSsudo snap install certbot-dns-google
certbot-dns-linodeLinodesudo snap install certbot-dns-linode

Each plugin follows the same pattern: install the plugin, create a credentials file with your API token, restrict permissions to 600, and run certbot certonly with the plugin flag.

Auto-Renewal Configuration

Certbot sets up automatic renewal when installed via snap or apt. A systemd timer checks for certificates nearing expiration twice a day.

Verify the Timer

sudo systemctl status certbot.timer

Expected output:

● certbot.timer - Run certbot twice daily
     Loaded: loaded (/lib/systemd/system/certbot.timer; enabled)
     Active: active (waiting)
    Trigger: Mon 2026-02-16 03:22:00 UTC; 11h left

Test Renewal

Always test renewal with a dry run:

sudo certbot renew --dry-run

If the dry run succeeds, your renewal pipeline is working correctly.

Renewal Hooks

Certbot supports hooks that run before, during, or after renewal. Use deploy hooks to reload your web server after a new certificate is installed:

sudo certbot renew --deploy-hook "systemctl reload nginx"

To make the hook permanent, add it to the renewal configuration file:

sudo tee -a /etc/letsencrypt/renewal/example.com.conf > /dev/null <<EOF

[renewalparams]
deploy_hook = systemctl reload nginx
EOF

Or for Apache:

sudo tee -a /etc/letsencrypt/renewal/example.com.conf > /dev/null <<EOF

[renewalparams]
deploy_hook = systemctl reload apache2
EOF

Manual Cron Job (If Needed)

If the systemd timer is not active, set up a cron job as a fallback:

sudo crontab -e

Add the following line:

0 3,15 * * * certbot renew --quiet --deploy-hook "systemctl reload nginx"

This runs at 03:00 and 15:00 daily. The --quiet flag suppresses output unless there is an error.

Certificate Management Commands

Certbot provides several commands for managing your certificates.

List All Certificates

sudo certbot certificates

Output includes the certificate name, domains covered, expiry date, and file paths.

Revoke a Certificate

If a private key is compromised or you no longer need a certificate:

sudo certbot revoke --cert-name example.com

Add --delete-after-revoke to also remove the files:

sudo certbot revoke --cert-name example.com --delete-after-revoke

Delete Certificate Files (Without Revoking)

sudo certbot delete --cert-name example.com

Expand an Existing Certificate

To add domains to an existing certificate:

sudo certbot --nginx --expand -d example.com -d www.example.com -d blog.example.com

Update Certificate Email

sudo certbot update_account --email newemail@example.com

Certificate File Locations

After obtaining a certificate, the files are stored at:

FilePathPurpose
Certificate/etc/letsencrypt/live/example.com/fullchain.pemPublic certificate chain
Private Key/etc/letsencrypt/live/example.com/privkey.pemPrivate key (keep secret)
Chain Only/etc/letsencrypt/live/example.com/chain.pemIntermediate CA chain
Certificate Only/etc/letsencrypt/live/example.com/cert.pemDomain certificate only

These are symlinks to the current version in /etc/letsencrypt/archive/. Never reference the archive path directly — always use the live directory.

Nginx SSL Best Practices

After Certbot configures your certificate, harden the Nginx SSL settings for production.

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name example.com www.example.com;

    # Certificate files managed by Certbot
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # TLS Protocol Versions — disable TLS 1.0 and 1.1
    ssl_protocols TLSv1.2 TLSv1.3;

    # Cipher Suites — strong ciphers only
    ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305';
    ssl_prefer_server_ciphers on;

    # OCSP Stapling — faster TLS handshakes
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
    resolver 1.1.1.1 8.8.8.8 valid=300s;
    resolver_timeout 5s;

    # HSTS — force HTTPS for 1 year, include subdomains
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

    # Security Headers
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;

    # DH Parameters for Forward Secrecy
    ssl_dhparam /etc/ssl/certs/dhparam.pem;

    # SSL Session Settings
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:10m;
    ssl_session_tickets off;

    root /var/www/example.com/html;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}

# Redirect HTTP to HTTPS
server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;
    return 301 https://$host$request_uri;
}

Generate DH Parameters

The ssl_dhparam directive requires a Diffie-Hellman parameter file. Generate one:

sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048

This takes a few minutes. A 2048-bit DH group is sufficient for most deployments.

Test Your Configuration

After modifying Nginx, always validate and reload:

sudo nginx -t
sudo systemctl reload nginx

Verify SSL Grade

Test your domain with the Qualys SSL Labs Server Test to confirm an A+ rating. An A+ requires HSTS to be enabled.

Multi-Domain Certificates (SAN)

A Subject Alternative Name (SAN) certificate covers multiple domains and subdomains within a single certificate. This is simpler to manage than individual certificates for each domain.

Obtain a SAN Certificate

sudo certbot --nginx \
  -d example.com \
  -d www.example.com \
  -d api.example.com \
  -d docs.example.com

Certbot bundles all listed domains into one certificate. Each domain must resolve to the server and pass the HTTP-01 or DNS-01 challenge.

Combining Wildcard and Specific Domains

You can combine a wildcard with the base domain in a single certificate:

sudo certbot certonly --dns-cloudflare \
  --dns-cloudflare-credentials /etc/letsencrypt/cloudflare/credentials.ini \
  -d example.com \
  -d "*.example.com"

This covers example.com itself plus any single-level subdomain like www.example.com, api.example.com, and so on.

Rate Limits

Let’s Encrypt enforces rate limits to prevent abuse:

LimitValueWindow
Certificates per Registered Domain507 days
Duplicate Certificates57 days
Failed Validations51 hour
New Registrations103 hours
Pending Authorizations3007 days

Use the staging environment for testing to avoid hitting production rate limits:

sudo certbot --nginx --staging -d example.com

Once your configuration works, remove the staging certificate and request a production one:

sudo certbot delete --cert-name example.com
sudo certbot --nginx -d example.com

Monitoring Certificate Expiry

Proactive monitoring prevents unexpected certificate expirations that result in browser warnings and service outages.

Check Expiry from the Command Line

sudo certbot certificates

Or check a specific domain using OpenSSL:

echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null \
  | openssl x509 -noout -dates

Output:

notBefore=Feb 14 00:00:00 2026 GMT
notAfter=May 15 00:00:00 2026 GMT

Script-Based Monitoring

Create a simple monitoring script that alerts you if a certificate expires within 14 days:

#!/usr/bin/env bash
# check-cert-expiry.sh
DOMAIN="example.com"
DAYS_THRESHOLD=14

EXPIRY_DATE=$(echo | openssl s_client -servername "$DOMAIN" -connect "$DOMAIN":443 2>/dev/null \
  | openssl x509 -noout -enddate | cut -d= -f2)

EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( (EXPIRY_EPOCH - NOW_EPOCH) / 86400 ))

if [ "$DAYS_LEFT" -lt "$DAYS_THRESHOLD" ]; then
    echo "WARNING: Certificate for $DOMAIN expires in $DAYS_LEFT days ($EXPIRY_DATE)"
    # Add email or webhook notification here
    exit 1
fi

echo "OK: Certificate for $DOMAIN expires in $DAYS_LEFT days"
exit 0

Make it executable and schedule it with cron:

chmod +x /usr/local/bin/check-cert-expiry.sh
0 9 * * * /usr/local/bin/check-cert-expiry.sh >> /var/log/cert-monitor.log 2>&1

External Monitoring Services

For critical production environments, consider external certificate monitoring services that check from outside your network and alert via email, Slack, or PagerDuty if a certificate is nearing expiry or has configuration issues.

Troubleshooting Common Errors

Challenge Failed — Connection Refused or Timeout

Symptom: Certbot reports that the ACME server could not reach your domain on port 80.

Causes and Fixes:

  1. Firewall blocking port 80:

    sudo ufw allow 80/tcp
    sudo ufw reload
  2. DNS not pointing to the correct server:

    dig +short A example.com

    The output must match your server’s public IP.

  3. Web server not running:

    sudo systemctl status nginx    # or apache2
    sudo systemctl start nginx
  4. Reverse proxy or CDN intercepting requests: If you use Cloudflare or another CDN with proxying enabled, temporarily set the DNS record to “DNS only” (grey cloud in Cloudflare) during certificate issuance, or use a DNS challenge instead.

Too Many Certificates Already Issued (Rate Limit)

Symptom: Certbot returns Error creating new order :: too many certificates already issued.

Fixes:

  • Wait for the rate limit window to expire (7 days for most limits).
  • Use the staging environment for testing: add --staging to your Certbot command.
  • Combine domains into a single SAN certificate instead of requesting separate certificates for each domain.

Check your current certificate issuance history:

# View certificates issued for your domain via crt.sh
curl -s "https://crt.sh/?q=example.com&output=json" | python3 -m json.tool | head -50

DNS Resolution Errors

Symptom: DNS problem: NXDOMAIN looking up A for example.com or similar.

Fixes:

  • Verify the A record exists and has propagated:

    dig +trace A example.com
  • Check from a public DNS resolver:

    dig @8.8.8.8 A example.com
  • For DNS-01 challenges, ensure the TXT record is published at _acme-challenge.example.com:

    dig +short TXT _acme-challenge.example.com
  • Wait 2–5 minutes for DNS propagation before pressing Enter in Certbot.

Renewal Failures

Symptom: certbot renew fails and the systemd timer logs show errors.

Diagnosis:

# Check Certbot logs
sudo tail -100 /var/log/letsencrypt/letsencrypt.log

# Test renewal
sudo certbot renew --dry-run

# Check the renewal configuration
sudo cat /etc/letsencrypt/renewal/example.com.conf

Common causes:

  • Port 80 is blocked — A firewall rule or another service is occupying port 80. Ensure your web server is running and port 80 is open.
  • Web server configuration changed — If you modified the Nginx or Apache config and removed the ACME challenge location block, renewal will fail.
  • Server block deleted — Certbot cannot find the server block referenced in the renewal config. Recreate it or update the renewal config.
  • Permissions — Ensure the Certbot systemd timer runs as root: sudo systemctl cat certbot.timer.

Nginx Configuration Errors After Certbot

Symptom: nginx -t fails after Certbot modifies the configuration.

Fix: Inspect the Nginx config for syntax issues:

sudo nginx -t 2>&1

Common issues include duplicate listen 443 ssl directives or conflicting server blocks. Review the affected file, fix the conflict, and reload:

sudo nginx -t && sudo systemctl reload nginx

Certificate Chain Issues

Symptom: Browsers on older devices or specific clients report “certificate not trusted.”

Fix: Ensure you are using fullchain.pem (not cert.pem) in your web server configuration. The fullchain.pem file includes both your certificate and the intermediate CA certificate:

ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;

Verify the chain is complete:

openssl s_client -connect example.com:443 -servername example.com < /dev/null 2>/dev/null \
  | openssl x509 -noout -issuer -subject

Summary

Let’s Encrypt and Certbot have fundamentally changed how we manage SSL/TLS certificates. What previously required annual fees and manual processes is now free and fully automated. The key steps are:

  1. Install Certbot via snap (recommended) or apt.
  2. Obtain certificates using the Nginx/Apache plugin, standalone, or webroot method.
  3. Harden your TLS configuration with modern protocols, strong ciphers, OCSP stapling, and HSTS.
  4. Automate renewal with the built-in systemd timer and deploy hooks.
  5. Monitor expiration with scripts or external services to catch failures before they impact users.

With these practices in place, your server will maintain a strong security posture with minimal ongoing effort.