Website được thiết kế tối ưu cho thành viên chính thức. Hãy Đăng nhập hoặc Đăng ký để truy cập đầy đủ nội dung và chức năng. Nội dung bạn cần không thấy trên website, có thể do bạn chưa đăng nhập. Nếu là thành viên của website, bạn cũng có thể yêu cầu trong nhóm Zalo "CNTT" các nội dung bạn quan tâm.

Bài 11.1. Chuẩn hóa cơ chế ACME challenge (.well-known) dùng chung cho mọi site

ICT

ACME là viết tắt của:

Automatic Certificate Management Environment

Đây là một giao thức tiêu chuẩn cho phép:

  • Tự động cấp chứng chỉ SSL

  • Tự động gia hạn

  • Không cần xác nhận thủ công qua email

Certbot là client phổ biến nhất triển khai giao thức ACME.

ACME challengecơ chế xác thực quyền sở hữu tên miền do Let’s Encrypt (và các CA tự động khác) sử dụng trước khi cấp hoặc gia hạn chứng chỉ SSL/TLS.

 

1. Mục tiêu của bài 11.1

Khi hệ thống có hàng trăm domain, việc cấp/gia hạn chứng chỉ Let’s Encrypt phải đáp ứng 3 yêu cầu:

  1. Đồng nhất: domain nào cũng xác thực được theo cùng một cơ chế.

  2. Tách biệt: ACME challenge không phụ thuộc backend (QMS/AI) đang sống hay chết.

  3. Vận hành đơn giản: thêm domain mới không cần “nghĩ lại” cách làm.

Giải pháp tốt nhất trong mô hình Reverse Proxy là:
ACME challenge được phục vụ trực tiếp trên Reverse Proxy, từ một thư mục webroot dùng chung, thông qua đường dẫn chuẩn:

/.well-known/acme-challenge/


2. Vì sao không nên để ACME phụ thuộc backend

Nếu anh để Nginx proxy toàn bộ request (kể cả .well-known) về backend, thì khi:

  • Backend QMS lỗi,

  • Hoặc chuyển failover sang AI chưa kịp,

  • Hoặc backend đang deploy/restart,

Let’s Encrypt có thể không xác thực được domain, dẫn đến:

  • Không cấp được cert mới,

  • Không renew được cert sắp hết hạn,

  • Tăng rủi ro downtime hàng loạt.

Do đó, ACME challenge phải “độc lập” với backend.


3. Chuẩn hóa một webroot dùng chung cho toàn hệ thống

3.1. Tạo thư mục webroot dùng cho Let’s Encrypt

mkdir -p /var/www/_letsencrypt/.well-known/acme-challenge
chown -R www-data:www-data /var/www/_letsencrypt
chmod -R 755 /var/www/_letsencrypt

 

Ghi chú:

  • www-data là user chạy Nginx trên Debian.

  • Thư mục _letsencrypt được đặt riêng để tránh trộn với nội dung web khác.


3.2. Tạo file test để kiểm tra nhanh

echo "acme-ok" > /var/www/_letsencrypt/.well-known/acme-challenge/test.txt

4. Chuẩn hóa snippet ACME dùng chung

4.1. Tạo snippet ACME

File: /etc/nginx/snippets/letsencrypt-acme.conf

# Serve ACME challenge locally on Reverse Proxy (independent from backend)
location ^~ /.well-known/acme-challenge/ {
   root /var/www/_letsencrypt;
   default_type "text/plain";
   allow all;
}

Vai trò:

  • Mọi request đến /.well-known/acme-challenge/ sẽ được trả trực tiếp từ proxy.

  • Không đi qua backend.

  • Dùng chung cho toàn bộ domain.


5. Gắn snippet ACME vào mọi server block HTTP (80)

5.1. Mẫu server block HTTP chuẩn (kịch bản B)

server {
   listen 80;
   server_name qlcl.example.com;
   include snippets/letsencrypt-acme.conf;
   return 301 https://$host$request_uri;
}

Lý do đặt ở HTTP (80):

  • Let’s Encrypt HTTP-01 challenge mặc định xác thực qua cổng 80.

  • Dù domain đã chạy HTTPS, ACME vẫn cần “đường” trên port 80 khi gia hạn theo HTTP-01.


6. Gắn ACME cho mọi domain mà không sửa từng file: dùng include chung

Với hàng trăm site, nếu phải tự thêm include snippets/letsencrypt-acme.conf; vào từng file, rủi ro quên là rất cao. Có hai cách chuẩn hóa ở cấp hệ thống.

Cách 1 (khuyến nghị): include ở nginx.conf trong http {}

Mở file:

/etc/nginx/nginx.conf

Trong block http { ... }, thêm:

include /etc/nginx/snippets/letsencrypt-acme.conf;

Nhưng lưu ý kỹ thuật:
Snippet này chứa location, nên chỉ include được trong context server {}, không include trực tiếp ở http {}.

Vì vậy, cách 1 chỉ đúng khi snippet không dùng location. Với ACME dùng location, ta nên dùng cách 2.

Mình dùng cách 2


Cách 2 (thực tế nhất): tạo “HTTP template” dùng chung cho tất cả site

Ta chuẩn hóa cấu trúc để mọi file site đều có block 80 và include snippet ACME như một “quy ước bắt buộc”.

Ví dụ: mọi file site đều bắt đầu bằng:

server {
   listen 80;
   server_name <domain>;
   include snippets/letsencrypt-acme.conf;
   return 301 https://$host$request_uri;
}

Ưu điểm:

  • Rõ ràng, dễ audit.

  • Không phụ thuộc thủ thuật include phức tạp.


7. Kiểm tra ACME hoạt động đúng

Sau khi cấu hình, reload Nginx:

nginx -t && systemctl reload nginx

Kiểm tra từ bên ngoài Internet (hoặc từ máy có thể truy cập domain):

curl -i http://qlcl.example.com/.well-known/acme-challenge/test.txt

Kết quả mong đợi:

  • HTTP 200

  • Body: acme-ok

Nếu trả về 301 sang HTTPS vẫn ổn, miễn là:

  • Let’s Encrypt vẫn truy cập được nội dung (thường vẫn OK).
    Tuy nhiên để chắc chắn, khuyến nghị để location ACME đứng trước return redirect như mẫu.


8. Chọn phương thức Certbot phù hợp sau khi đã chuẩn hóa ACME

Sau khi có webroot dùng chung, anh có thể dùng 2 kiểu:

8.1. Dùng --nginx (tiện, nhanh)

certbot --nginx -d qlcl.example.com

8.2. Dùng --webroot (kiểm soát tuyệt đối, phù hợp hệ thống lớn)

certbot certonly --webroot -w /var/www/_letsencrypt -d qlcl.example.com

Khuyến nghị với hệ thống hàng trăm domain:

  • Ưu tiên certonly --webroot để Certbot không tự sửa file cấu hình.

  • Nginx cấu hình theo chuẩn kịch bản B.


9. Các lỗi thường gặp và cách xử lý

Lỗi 1: 404 khi truy cập đường dẫn challenge

Nguyên nhân:

  • Thiếu include snippet ACME trong server block 80

  • Sai root

Cách xử lý:

  • Kiểm tra file site có dòng:
    include snippets/letsencrypt-acme.conf;


Lỗi 2: 403 forbidden

Nguyên nhân:

  • Permission thư mục không cho Nginx đọc

Cách xử lý:

 
chown -R www-data:www-data /var/www/_letsencrypt
chmod -R 755 /var/www/_letsencrypt

Lỗi 3: Domain trỏ sai DNS / NAT chưa đúng

Nguyên nhân:

  • Domain chưa trỏ về IP public của modem/router

  • NAT chưa forward port 80 về proxy

Cách xử lý:

  • Xác thực DNS trước (A record đúng).

  • Xác thực port 80 từ ngoài Internet truy cập được.


Kết luận

Chuẩn hóa ACME challenge theo cơ chế:

  • một webroot dùng chung (/var/www/_letsencrypt)

  • một snippet dùng chung (snippets/letsencrypt-acme.conf)

  • mọi domain đều include snippet trong server block 80

giúp hệ thống:

  • Renew cert ổn định ngay cả khi backend lỗi,

  • Mở rộng hàng trăm domain không tăng rủi ro vận hành,

  • Giữ cấu hình Nginx sạch, đồng nhất theo template.