Let’s Encrypt #

Before 2016, enabling HTTPS on a website was a complicated and expensive process. You had to buy a certificate from a commercial Certificate Authority (CA), upload files manually, go through slow ownership verification, and pay non-trivial annual fees. This changed with the arrival of Let’s Encrypt, a Certificate Authority that is free, automated, and open.

Let’s Encrypt lets us obtain trusted TLS certificates instantly, recognized by all modern browsers in the world. Combined with the Certbot tool, the process of issuing, installing to Nginx, and renewing certificates before expiry can be 100% automated. In this article, we’ll thoroughly dissect how the ACME protocol works, Certbot installation, the various verification methods (challenges), manual and automatic Nginx configuration, reliable automatic renewal, and wildcard certificate creation using DNS challenges.

How Does Let’s Encrypt Work? (The ACME Protocol) #

Let’s Encrypt uses the ACME (Automatic Certificate Management Environment) protocol to automate the interaction between our server (an ACME client) and Let’s Encrypt’s server (the CA).

For Let’s Encrypt to be willing to issue a certificate for our domain (e.g., example.com), its servers must verify that we truly control the domain. This verification is done through a method called a Challenge. The two most common challenge types are:

  1. HTTP-01 Challenge: Let’s Encrypt asks the ACME client (Certbot) to place a special random text file at a specific path on our web server, under the /.well-known/acme-challenge/ directory. Let’s Encrypt then makes an HTTP call (port 80) to http://example.com/.well-known/acme-challenge/[token]. If the file is found and its content matches, domain ownership is declared valid.
  2. DNS-01 Challenge: Let’s Encrypt asks us to create a new TXT DNS record named _acme-challenge.example.com containing a specific random value. Let’s Encrypt then queries our domain’s DNS servers. If the TXT record matches, the domain is verified. This method is mandatory if we want to issue a Wildcard Certificate (a certificate covering all subdomains, e.g., *.example.com).

HTTP-01 Challenge Workflow #

Here’s the detailed communication flow when using the HTTP-01 challenge to verify domain ownership:

sequenceDiagram
    autonumber
    actor Certbot as Certbot Client (Our Server)
    actor LE as Let's Encrypt CA Server
    actor Nginx as Nginx Web Server

    Certbot->>LE: Requests a certificate for domain.com (HTTP-01)
    LE->>Certbot: Sends a unique challenge token
    Certbot->>Nginx: Writes the token file to /.well-known/acme-challenge/[token]
    Note over LE: Performs external verification via the Internet
    LE->>Nginx: HTTP GET http://domain.com/.well-known/acme-challenge/[token]
    Nginx->>LE: Returns the token file content (HTTP 200 OK)
    LE->>LE: Validates the token match
    LE->>Certbot: Sends the newly issued digital certificate

DNS-01 Challenge Workflow #

Here’s the communication flow when issuing a certificate using the DNS-01 challenge (usually for wildcard domains):

sequenceDiagram
    autonumber
    actor Certbot as Certbot Client (Our Server)
    actor LE as Let's Encrypt CA Server
    actor DNS as DNS API Service (Cloudflare/Route53)

    Certbot->>LE: Requests a certificate for *.domain.com (DNS-01)
    LE->>Certbot: Sends the required TXT record value
    Certbot->>DNS: Creates the _acme-challenge.domain.com TXT record automatically via API
    Note over DNS: Waits for global DNS synchronization
    LE->>DNS: Queries DNS TXT for _acme-challenge.domain.com
    DNS->>LE: Returns the TXT record value
    LE->>LE: Validates the record value match
    Certbot->>DNS: Deletes the _acme-challenge.domain.com TXT record (Cleanup)
    LE->>Certbot: Sends the newly issued wildcard digital certificate

Step 1: Installing Certbot on the Server #

Before requesting a certificate, we need to install Certbot along with the Nginx plugin on our server’s operating system.

The official Certbot site strongly recommends installing via Snapd to make sure we always get the latest Certbot version:

# Make sure snapd is installed and updated
sudo apt update
sudo apt install snapd -y
sudo snap install core; sudo snap refresh core

# Remove the old apt-bundled certbot installation (if any)
sudo apt remove certbot -y

# Install Certbot using Snap
sudo snap install --classic certbot

# Create a symbolic link so the certbot command is globally accessible
sudo ln -s /snap/bin/certbot /usr/bin/certbot

CentOS / Rocky Linux / AlmaLinux #

# Enable the EPEL repository
sudo dnf install epel-release -y

# Install Certbot and the Nginx plugin
sudo dnf install certbot python3-certbot-nginx -y

Step 2: SSL Certificate Issuance Methods #

There are several ways to request a certificate from Let’s Encrypt using Certbot. We must choose the method that best fits our infrastructure needs.

Method A: Automatic Issuance and Configuration (--nginx) #

This is the easiest and most recommended way if we want the process to run instantly. Certbot will verify the domain, download the certificate, automatically edit our Nginx configuration to install the certificate, and add the HTTP to HTTPS redirect rule.

Before running this command, make sure we’ve created a plain HTTP (port 80) server block in Nginx with the server_name directive pointing to the target domain:

# Initial Nginx configuration at /etc/nginx/conf.d/example.com.conf
server {
    listen 80;
    server_name example.com www.example.com;
    root /var/www/html;
}

Run the configuration test command and run Certbot:

sudo nginx -t && sudo systemctl reload nginx

# Run Certbot with the Nginx plugin
sudo certbot --nginx -d example.com -d www.example.com

An interactive process will start:

  1. Email: Enter our email address (used for notifications if there are renewal problems or the certificate is about to expire).
  2. ToS: Accept the Terms of Service.
  3. Newsletter: Choose whether we want to share our email with the Electronic Frontier Foundation (EFF).
  4. Redirect: Certbot will ask whether we want to automatically redirect all HTTP traffic to HTTPS (highly recommended to choose option 2: Redirect).

Certbot will automatically modify our Nginx configuration file, adding SSL lines, pointing to the new certificate, and reloading Nginx.

Method B: Only Download the Certificate (certonly) #

If we have a complex custom Nginx configuration architecture, we might not want Certbot to mess with or automatically modify our Nginx configuration files. We can use the certonly option to just download the certificate files, then write the Nginx configuration manually.

1. Using the Webroot Method (--webroot) #

This method is very suitable for production web servers because it requires no downtime. Certbot places a temporary verification file in our site’s webroot directory, then Let’s Encrypt downloads it.

We must make sure Nginx allows access to the hidden /.well-known/ directory:

server {
    listen 80;
    server_name example.com www.example.com;
    root /var/www/html;

    # Allow access for the Let's Encrypt Challenge
    location ~ /.well-known/acme-challenge {
        allow all;
    }
}

Run Certbot by specifying our site’s root directory:

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

2. Using the Standalone Method (--standalone) #

If we don’t have a running web server yet, or if we want to request a certificate before configuring Nginx, we can use the standalone method. Certbot runs its own mini web server on port 80 to answer Let’s Encrypt challenges.

[!WARNING] Because the standalone method needs port 80, we must stop the Nginx service first if Nginx is running:

# Stop Nginx temporarily
sudo systemctl stop nginx

# Request the certificate
sudo certbot certonly --standalone -d example.com -d www.example.com

# Restart Nginx
sudo systemctl start nginx

Step 3: Manual Nginx Configuration for Let’s Encrypt #

After using the certonly method, our certificate is stored in the secure directory /etc/letsencrypt/live/. If our domain name is example.com, we’ll find the following files in the /etc/letsencrypt/live/example.com/ folder:

  • cert.pem: Contains only our server certificate.
  • chain.pem: Contains only the intermediate CA certificate.
  • privkey.pem: Our server’s Private Key file (highly confidential).
  • fullchain.pem: A combined file of cert.pem and chain.pem in sequence.

[!IMPORTANT] In Nginx, we must use the fullchain.pem file as the ssl_certificate directive value, not cert.pem. If we only use cert.pem, users’ browsers won’t be able to verify the Chain of Trust and will show a security error.

Here’s the recommended manual Nginx configuration for production:

# HTTPS Server Block
server {
    listen 443 ssl;
    server_name example.com www.example.com;

    # Let's Encrypt certificate
    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # Secure Protocols & Cipher Suites
    ssl_protocols TLSv1.2 TLSv1.3;
    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 off;

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

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

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

# Redirect HTTP to HTTPS
server {
    listen 80;
    server_name example.com www.example.com;
    
    location ~ /.well-known/acme-challenge {
        allow all;
        root /var/www/html;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

Step 4: Issuing Wildcard Certificates (DNS-01 Challenge) #

Wildcard certificates (*.example.com) are very useful if we manage many dynamic subdomains (e.g., blog.example.com, shop.example.com, api.example.com) using a single certificate.

Because HTTP-01 verification can’t be used to prove ownership of a wildcard domain name in general, Let’s Encrypt requires the DNS-01 Challenge.

Manual Method (Without a DNS API) #

If we rarely renew certificates or our DNS provider has no API, we can do the verification manually:

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

Certbot will pause and show instructions to go to our domain’s DNS panel:

  1. Create a new TXT DNS record.
  2. Name/Host: _acme-challenge.example.com.
  3. Value/Content: The long random string shown in the terminal by Certbot.
  4. Wait a bit (about 1-5 minutes) for DNS to propagate globally, then press Enter in the terminal to verify.

Automatic Method (Using the Cloudflare API) #

The manual process above has a major drawback: we can’t automate certificate renewal, because every 90 days we’d have to manually enter a new TXT record.

The best solution is to use a DNS API plugin. For example, if our domain is managed at Cloudflare, we can automate this step:

# Install the Cloudflare DNS plugin for Certbot
sudo snap set certbot trust-plugin-with-root=ok
sudo snap install certbot-dns-cloudflare

Create a secret Cloudflare API Token credentials file on our server (e.g., at /etc/letsencrypt/cloudflare.ini):

# Content of /etc/letsencrypt/cloudflare.ini
dns_cloudflare_api_token = 1234567890abcdefghijklmnopqrstuvwxyz_YOUR_TOKEN

Restrict this credentials file’s access permissions:

sudo chmod 600 /etc/letsencrypt/cloudflare.ini

Run Certbot to get a wildcard certificate automatically:

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

Certbot will automatically create the TXT record at Cloudflare via API, verify with Let’s Encrypt, and delete the TXT record after success. This process runs 100% automatically in the background!


Step 5: Automating Renewal (Auto-Renewal) #

Let’s Encrypt certificates are only valid for 90 days. This policy exists for security reasons (limiting the damage if a private key leaks) and to encourage full automation worldwide. Let’s Encrypt recommends renewing certificates every 60 days (when the remaining validity is 30 days).

When we install Certbot via Snap or apt, the system automatically installs a Systemd Timer (or a Cron Job on older distros) to check certificate renewal eligibility twice a day.

We can verify that the automatic renewal timer is active on our server:

# Check the list of active systemd timers
sudo systemctl list-timers | grep certbot

Performing a Renewal Simulation (Dry Run) #

To make sure the automatic renewal process runs smoothly without future firewall or DNS obstacles, we must do a simulation:

sudo certbot renew --dry-run

If the simulation ends with “Congratulations, all renewals succeeded”, our automation is safe.

Configuring Automatic Nginx Reload (Post-Renewal Hooks) #

When a certificate is renewed in the background, the new certificate files are written to disk. However, Nginx won’t load those new certificates until the Nginx service is reloaded. We certainly don’t want Nginx to keep serving an old expired certificate just because we forgot to reload the server.

We can configure Certbot to reload Nginx automatically right after a successful certificate renewal using a Deploy Hook:

# Register the deploy hook in the Certbot renewal configuration
sudo certbot renew --deploy-hook "systemctl reload nginx"

Or if we edit the per-domain renewal configuration in the /etc/letsencrypt/renewal/example.com.conf file, we can add the following line at the very bottom under [renewalparams]:

renew_hook = systemctl reload nginx

Let’s Encrypt Troubleshooting Table #

Here’s a list of common problems encountered when installing Let’s Encrypt certificates along with their solutions:

ProblemLikely CauseHow to Fix
Failed authorization procedure / HTTP 404Let’s Encrypt failed to download the verification file from port 80 of our server. Usually because the root directory is wrong or there’s an HTTPS redirect configuration blocking HTTP access before verification completes.Make sure the root directory in the port-80 Nginx configuration points to the right directory, and that /.well-known/acme-challenge/ isn’t redirected to HTTPS before the verification process completes.
Connection refused / TimeoutPort 80 or 443 on our server is closed by a firewall (UFW, iptables, AWS Security Group, or Cloudflare proxy).Make sure the firewall allows inbound traffic on ports 80 and 443 from any IP address on the internet: sudo ufw allow 80/tcpsudo ufw allow 443/tcp
Rate Limits ExceededLet’s Encrypt limits new certificate issuance (e.g., max 50 certificates per registered domain per week, or max 5 duplicate certificates per week).If you’re testing configurations, always use the --staging flag so you don’t trigger production rate limits. Staging certificates aren’t trusted by browsers, but don’t have strict rate limits.
DNS problem: NXDOMAIN looking up TXT for _acme-challengeThe TXT record created for DNS-01 verification hasn’t propagated to Let’s Encrypt’s DNS servers when validation happens.If using the manual method, wait a few minutes before pressing Enter. If using an API, make sure the API token has proper DNS edit access rights and set a longer wait time (propagation-seconds), e.g., --dns-cloudflare-propagation-seconds 60.
CAA record prevents issuanceOur domain has a CAA (Certification Authority Authorization) DNS record forbidding Let’s Encrypt from issuing certificates for the domain.Remove that CAA record from our domain’s DNS panel, or add a new CAA rule allowing Let’s Encrypt: example.com. IN CAA 0 issue "letsencrypt.org".

Summary #

  • Snapd for Certbot: Use the Snap installation method to guarantee Certbot always runs the latest version with up-to-date security features.
  • Use fullchain.pem: Always use fullchain.pem for Nginx’s ssl_certificate so browsers can successfully verify the certificate chain.
  • Use Webroot for Zero Downtime: Choose the --webroot method if you want to download a certificate without stopping the Nginx service on a production server.
  • Use an API for Wildcards: Automate wildcard certificate issuance with a DNS API plugin (like Cloudflare) so automatic renewal keeps working without manual intervention.
  • Don’t Forget the Reload Hook: Always install the systemctl reload nginx deploy hook so Nginx automatically loads new certificates after a successful renewal.

← Previous: Self-Signed Certificate   Next: SSL Configuration Optimization →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact