m.ac

Issue Let's Encrypt certificates automatically with nginx-acme on Debian and Ubuntu

381 words2 min read

This guide sets up TLS certificates on Debian Stable and Ubuntu LTS with nginx-acme, NGINX's official ACME module.

#1. What is nginx-acme?

nginx-acme is an official NGINX module that implements ACME. It lets NGINX request and renew TLS certificates without a separate ACME client.

If you have used Caddy, the idea will feel familiar: certificate automation lives in the web server config.

The module is implemented in Rust (unlike NGINX itself, which is written in C) and supports the following standards:

In my testing, it has behaved well enough for production use. Still watch your logs, because certificate automation is one of those things you only notice when it stops working.

#2. Install N.WTF (NGINX with nginx-acme)

This guide uses N.WTF, packaged by m.ac. Its nginx-extras package includes nginx-acme.

The commands below use sudo. If you prefer a root shell, run sudo -i first and drop sudo from the commands.

Install the required packages:

bash
sudo apt update
sudo apt upgrade -y
sudo apt install curl vim wget gnupg dpkg apt-transport-https lsb-release ca-certificates

Import the N.WTF signing key and add the APT repository:

bash
curl -sSL https://n.wtf/public.key | sudo gpg --dearmor -o /usr/share/keyrings/n.wtf.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/n.wtf.gpg] https://mirror-cdn.xtom.com/sb/nginx/ $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/n.wtf.list

On Debian, extrepo can add the same repository:

bash
sudo apt update
sudo apt install extrepo -y
sudo extrepo enable n.wtf

Install NGINX:

bash
sudo apt update
sudo apt install nginx-extras -y

#3. Prepare the certificate directory

Create a directory for ACME state and certificates. This example uses /var/cache/nginx/letsencrypt:

bash
sudo mkdir -p /var/cache/nginx/letsencrypt
sudo chown 33:33 /var/cache/nginx -R

Make sure your domain's A and AAAA records point to this server before you continue.

#4. Configure an NGINX site with a www redirect

This example assumes:

  • Your domains are example.com and www.example.com
  • You want www.example.com to redirect to example.com
  • Your contact email is user@example.com

Edit /etc/nginx/sites-enabled/default and use this configuration:

nginx
resolver 8.8.8.8:53 ipv6=off valid=5s; # Or replace to your preferred DNS resolver

acme_issuer letsencrypt {
    uri         https://acme-v02.api.letsencrypt.org/directory;
    contact     user@example.com;
    state_path  /var/cache/nginx/letsencrypt;
    accept_terms_of_service;
    ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
    ssl_verify on;
}

acme_shared_zone zone=ngx_acme_shared:1M;

server {
    # Listen on port 80 for ACME HTTP-01 validation and HTTP->HTTPS redirects
    listen 80 default_server;
    listen [::]:80 default_server;

    server_name _;

    # Let nginx-acme handle ACME HTTP-01 challenges.
    # Do not block /.well-known/acme-challenge/ here.
    location /.well-known/acme-challenge/ {
        # nginx-acme serves this internally.
        # Keeping the block empty avoids accidental 404s.
    }

    # Redirect all other HTTP requests to HTTPS
    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    # Standard TLS listening
    listen 443 ssl default_server;
    listen [::]:443 ssl default_server;

    # HTTP/2
    http2 on;

    # HTTP/3 (QUIC)
    listen 443 quic reuseport;
    listen [::]:443 quic reuseport;
    add_header Alt-Svc 'h3=":443"; ma=86400' always;
    add_header X-Protocol $server_protocol always;

    server_name example.com;

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

    # Modern TLS settings
    ssl_protocols TLSv1.3;
    ssl_ecdh_curve X25519:prime256v1:secp384r1;
    ssl_prefer_server_ciphers off;

    # Enable certificate automation for this server block
    acme_certificate letsencrypt;

    ssl_certificate       $acme_certificate;
    ssl_certificate_key   $acme_certificate_key;

    # Avoid parsing the certificate on every request
    ssl_certificate_cache max=2;
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;

    http2 on;

    listen 443 quic;
    listen [::]:443 quic;

    add_header Alt-Svc 'h3=":443"; ma=86400' always;
    add_header X-Protocol $server_protocol always;

    server_name www.example.com;
    return 301 https://example.com$request_uri;

    ssl_protocols TLSv1.3;
    ssl_ecdh_curve X25519:prime256v1:secp384r1;
    ssl_prefer_server_ciphers off;

    acme_certificate letsencrypt;

    ssl_certificate       $acme_certificate;
    ssl_certificate_key   $acme_certificate_key;

    ssl_certificate_cache max=2;
}

Test the configuration and reload NGINX:

bash
sudo nginx -t
sudo nginx -s reload

#5. Verify certificate issuance

After NGINX reloads, nginx-acme requests a certificate. Watch the access log while it validates:

bash
sudo tail -f /var/log/nginx/access.log

You should see requests to /.well-known/acme-challenge/ from Let's Encrypt validation servers. A few seconds later, https://example.com/ should answer with a valid certificate.

Example log entries:

log
23.178.112.210 - - [15/Jan/2026:16:08:18 +0000] "GET /.well-known/acme-challenge/blablablablablablablablablablablablablablab HTTP/1.1" 200 87 "-" "Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)"
34.212.137.78 - - [15/Jan/2026:16:08:18 +0000] "GET /.well-known/acme-challenge/blablablablablablablablablablablablablablab HTTP/1.1" 200 87 "-" "Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)"

#6. IP address certificates with the shortlived profile

To request a certificate for an IP address, add this line inside the acme_issuer letsencrypt {} block:

nginx
profile     shortlived;

Also set server_name to the full IP address. You cannot use server_name _ for an IP certificate.

Issue Let's Encrypt certificates automatically with nginx-acme on Debian and Ubuntu
Author
Xiufeng Guo
Published
Jan 15, 2026
Last updated
Jan 15, 2026
License
Short link
ADShare it the short way.URL shortener, file and text sharing, link in bio. Run by yours truly.S.EE →
Share