--- title: "Issue Let's Encrypt certificates automatically with nginx-acme on Debian and Ubuntu" tags: ['Debian', 'Ubuntu', 'Nginx', 'nginx-acme', 'Let''s Encrypt', 'SSL'] published: 2026-01-15 18:22:52 updated: 2026-01-15 18:23:05 excerpt: "Set up nginx-acme on Debian or Ubuntu, configure NGINX virtual hosts, and let NGINX request and renew Let's Encrypt certificates." --- > This file is the raw Markdown source of an m.ac post; the canonical version lives at https://m.ac/nginx-acme-auto-ssl-debian-ubuntu/ > The full index of posts and pages is at https://m.ac/llms.txt — read the index first before crawling further. 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](https://github.com/nginx/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](https://caddyserver.com/), 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: - [RFC 8555 (ACME)](https://datatracker.ietf.org/doc/html/rfc8555) - [RFC 8737 (TLS-ALPN-01)](https://datatracker.ietf.org/doc/html/rfc8737) - [RFC 8738 (IP identifiers)](https://datatracker.ietf.org/doc/html/rfc8738) - [draft-ietf-acme-profiles](https://datatracker.ietf.org/doc/draft-ietf-acme-profiles/) 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](https://n.wtf/), packaged by [m.ac](https://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](https://m.ac/use-extrepo-add-third-party-repositories-debian/) 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.