Sửa lỗi Nginx 404 khi Certbot không thể xác minh .well-known/acme-challenge để gia hạn SSL

intermediate Nginx2026-07-22| Ubuntu 20.04/22.04, Debian 11/12, Nginx 1.18+, Certbot 1.x/2.x, Let's Encrypt

Error Message

open() "/var/www/html/.well-known/acme-challenge/xxxxxxxxxxxxxxxx" failed (2: No such file or directory)
#nginx#certbot#letsencrypt#acme-challenge#ssl-renewal#404

Lỗi gặp phải

Chạy certbot renew và nhận thông báo lỗi:

open() "/var/www/html/.well-known/acme-challenge/xxxxxxxxxxxxxxxx" failed (2: No such file or directory)

Let's Encrypt đặt một file token vào URL đã biết, rồi tải nó qua HTTP cổng 80 để xác nhận bạn kiểm soát domain. Nginx trả về 404. Xác thực thất bại. Chứng chỉ không được gia hạn.

Nguyên nhân

Ba nguyên nhân chính, sắp xếp theo mức độ phổ biến:

  • Redirect HTTP→HTTPS chặn request challenge — ACME server luôn truy cập cổng 80. Nếu Nginx redirect toàn bộ traffic cổng 80 sang HTTPS trước, lệnh 302 được kích hoạt trước khi file challenge được kiểm tra. Đây là nguyên nhân phổ biến nhất.
  • Sai đường dẫn webroot — Certbot ghi token vào một thư mục, nhưng chỉ thị root của Nginx lại trỏ đến nơi khác. Token tồn tại trên đĩa nhưng Nginx không thể tìm thấy.
  • Thiếu thư mục — Thư mục .well-known/acme-challenge/ hoàn toàn không tồn tại trên đĩa.

Cách sửa nhanh

Bước 1: Kiểm tra webroot mà Certbot đang trỏ đến

cat /etc/letsencrypt/renewal/yourdomain.com.conf

Tìm dòng webroot_path. Nó phải khớp với thư mục mà Nginx thực sự phục vụ file. Không khớp? Hãy sửa đường dẫn trong file này hoặc cập nhật chỉ thị root của Nginx.

Bước 2: Tạo thư mục challenge nếu chưa tồn tại

mkdir -p /var/www/html/.well-known/acme-challenge
chmod 755 /var/www/html/.well-known/acme-challenge

Bước 3: Thêm location block rõ ràng trước mọi redirect

Mở server block Nginx cho cổng 80. Thêm location challenge phía trên dòng return 301 hoặc rewrite bất kỳ:

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

    # ACME challenge — phải đặt trước redirect HTTPS
    location ^~ /.well-known/acme-challenge/ {
        root /var/www/html;
        default_type "text/plain";
        try_files $uri =404;
    }

    # Mọi request còn lại chuyển sang HTTPS
    return 301 https://$host$request_uri;
}

Modifier ^~ yêu cầu Nginx dừng xử lý regex khi block này khớp. Redirect sẽ không bao giờ kích hoạt cho các URL challenge.

Bước 4: Kiểm tra config và reload

nginx -t && systemctl reload nginx

Bước 5: Thử lại với dry run

certbot renew --dry-run

Khắc phục vĩnh viễn: Snippet dùng chung cho mọi domain

Quản lý nhiều domain? Tạo một snippet có thể tái sử dụng để không server block nào bỏ sót phần này:

# Tạo file: /etc/nginx/snippets/acme-challenge.conf
location ^~ /.well-known/acme-challenge/ {
    root /var/www/html;
    default_type "text/plain";
    try_files $uri =404;
}

Sau đó include vào mọi server block HTTP:

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

    include snippets/acme-challenge.conf;

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

Chỉ một dòng include. Mọi domain mới tự động kế thừa config challenge đúng.

Trường hợp đặc biệt: Nhiều domain với webroot khác nhau

Gia hạn chứng chỉ bao gồm các domain nằm trong thư mục riêng biệt? Kiểm tra phần [[webroot_map]] trong config gia hạn:

# /etc/letsencrypt/renewal/yourdomain.com.conf
[renewalparams]
authenticator = webroot
webroot_path = /var/www/html,

[[webroot_map]]
yourdomain.com = /var/www/html
www.yourdomain.com = /var/www/html
api.yourdomain.com = /var/www/api

Mỗi domain ánh xạ đến thư mục mà Nginx phục vụ nó. Một đường dẫn sai sẽ làm thất bại toàn bộ quá trình gia hạn — không chỉ riêng domain đó.

Kiểm tra

Đừng đoán mò. Tạo file test và tải nó qua HTTP trực tiếp:

# Tạo file token thử nghiệm
echo "ok" > /var/www/html/.well-known/acme-challenge/healthcheck

# Tải qua HTTP — phải trả về 200, không phải redirect
curl -v http://yourdomain.com/.well-known/acme-challenge/healthcheck

Bạn muốn thấy HTTP/1.1 200 OK với body là ok. Vẫn nhận 404 hoặc redirect? Chạy các lệnh sau:

# Kiểm tra location block trong config đang hoạt động
nginx -T | grep -A5 'well-known'

# Xem đường dẫn root mà cổng 80 đang dùng
nginx -T | grep root

Khi curl trả về 200, chạy dry run:

certbot renew --dry-run

Dry run thành công nghĩa là lịch gia hạn tự động từ certbot.timer cũng sẽ hoạt động. Dọn dẹp sau đó:

rm /var/www/html/.well-known/acme-challenge/healthcheck

Related Error Notes