Có lúc lệnh certbot quen thuộc trên balancer bỗng báo Connection Timeout cho mọi domain. Nguyên nhân không nằm ở certbot hay nginx: kết nối quốc tế của dải IP chứa server bị chặn, nên Let's Encrypt không gọi vào được, có khi server cũng không gọi ra được. Lối thoát là cơ chế xác thực DNS-01. Tài liệu này giải thích vì sao HTTP-01 thất bại, DNS-01 hoạt động thế nào, quy trình cấp từng bước, cách tự gia hạn và lỗi hay gặp.
1. Tình huống điển hình: server bị chặn kết nối quốc tế
Thông thường, chứng chỉ được cấp ngay trên balancer bằng plugin nginx của certbot, như trong bài SSL cho hàng trăm domain với Certbot:
certbot certonly --nginx -d example.com -d www.example.comKhi mạng quốc tế bị chặn, lệnh trên treo rồi báo timeout cho mọi domain mới. Cần kiểm tra riêng từng chiều:
- Chiều ra: balancer không mở được kết nối tới API ACME của Let's Encrypt (
acme-v02.api.letsencrypt.org:443), nên certbot không tạo được đơn cấp chứng chỉ. Thử bằngcurl -sv https://acme-v02.api.letsencrypt.org/directory. - Chiều vào: trung tâm dữ liệu chặn truy cập từ IP nước ngoài vào cổng 80/443. Kể cả khi đơn được tạo, máy xác thực của Let's Encrypt cũng không gọi vào được, và báo lỗi Timeout during connect (likely firewall problem).
Bật proxy Cloudflare cũng không giúp được: edge của Cloudflare kết nối về balancer từ IP nước ngoài, cũng bị chặn, và khách truy cập gặp lỗi 521/522. Cần một cách xác thực quyền sở hữu domain không cần chạm vào server.
2. Vì sao HTTP-01 thất bại khi server bị chặn kết nối quốc tế
HTTP-01 là kiểu xác thực mặc định của certbot. Luồng của nó như sau:
certbot (trên balancer) Let's Encrypt (ở nước ngoài)
│ 1. tạo đơn, nhận token ───────────► API ACME
│ 2. đặt file token tại
│ /.well-known/acme-challenge/<token>
│ 3. báo "đã sẵn sàng" ───────────► API ACME
│ │ 4. máy xác thực ở nhiều nơi trên thế giới
│ ◄───────────────────────────────────────┘ GET http://example.com/.well-known/acme-challenge/<token>
│ 5. khớp nội dung → ký chứng chỉBước 1 và 3 cần chiều ra, bước 4 cần chiều vào cổng 80 từ nước ngoài. Let's Encrypt kiểm tra từ nhiều điểm mạng khác nhau để chống giả mạo, nên mở cổng cho riêng một dải IP xác thực cũng không được. Chặn một chiều là HTTP-01 đã hỏng.
3. DNS-01 là gì và hoạt động thế nào
Với DNS-01, Let's Encrypt không gọi vào web server. Thay vào đó, nó yêu cầu bạn chứng minh quyền kiểm soát DNS của domain bằng một bản ghi TXT có tên _acme-challenge.<domain>. Giá trị là chuỗi băm SHA-256 (base64url) của token và dấu vân tay khoá tài khoản ACME, theo RFC 8555. Let's Encrypt hỏi nameserver có thẩm quyền, thấy khớp thì ký chứng chỉ.
máy làm việc (có mạng quốc tế) DNS có thẩm quyền (Cloudflare) Let's Encrypt
│ 1. tạo đơn DNS-01 ──────────────────────────────────────────────► API ACME
│ 2. thêm TXT _acme-challenge.example.com ──► lưu bản ghi
│ 3. báo "đã sẵn sàng" ──────────────────────────────────────────────► API ACME
│ ◄── 4. truy vấn TXT ───────┘
│ 5. tải chứng chỉ về, copy lên balancer qua mạng nội bộ / VPNServer web không xuất hiện trong luồng này: balancer bị chặn kiểu gì thì việc cấp chứng chỉ vẫn chạy được. Tuy vậy, DNS-01 chỉ giải quyết chiều vào. Client ACME vẫn phải gọi được API của Let's Encrypt, nên nếu chiều ra cũng bị chặn, hãy chạy nó trên một máy có mạng quốc tế bình thường (máy làm việc, máy vận hành) rồi đẩy cert lên balancer.
| Tiêu chí | HTTP-01 | DNS-01 |
|---|---|---|
| Let's Encrypt cần gọi vào cổng 80 của server | Có | Không |
Cấp được wildcard *.example.com | Không | Có |
| Cần quyền sửa DNS | Không | Có (tay hoặc qua API) |
| Chạy client ở đâu | Trên chính web server | Bất kỳ máy nào gọi được API ACME |
Tài liệu chính thức so sánh các kiểu xác thực có tại trang Challenge Types của Let's Encrypt.
4. Quy trình cấp chứng chỉ DNS-01 từng bước
Ví dụ dưới đây giả định DNS của domain quản lý ở Cloudflare và dùng acme.sh trên máy làm việc, vì nó chỉ là shell script, không kéo theo thư viện Python. Lưu ý: CA mặc định của acme.sh là ZeroSSL, nên phải chỉ rõ --server letsencrypt.
Bước 1: tạo đơn DNS-01 ở chế độ thủ công
git clone --depth 1 https://github.com/acmesh-official/acme.sh.git ~/acme_src
~/acme_src/acme.sh --issue --server letsencrypt \
-d example.com -d www.example.com \
--dns --yes-I-know-dns-manual-mode-enough-go-ahead-pleaseacme.sh in ra hai bản ghi cần thêm, mỗi tên miền trong chứng chỉ một bản ghi:
Domain: '_acme-challenge.example.com' TXT value: '<chuỗi thứ nhất>'
Domain: '_acme-challenge.www.example.com' TXT value: '<chuỗi thứ hai>'Bước 2: thêm TXT trên Cloudflare và kiểm tra tại nameserver có thẩm quyền
Trên trang quản trị DNS, thêm hai bản ghi TXT tên _acme-challenge và _acme-challenge.www, TTL để Auto. Sau đó hỏi thẳng nameserver của zone, không qua resolver công cộng:
NS=$(dig +short NS example.com | head -1)
dig @"$NS" TXT _acme-challenge.example.com +short
dig @"$NS" TXT _acme-challenge.www.example.com +shortBước 3: yêu cầu xác thực và nhận chứng chỉ
~/acme_src/acme.sh --renew --server letsencrypt \
-d example.com -d www.example.com \
--yes-I-know-dns-manual-mode-enough-go-ahead-pleaseThành công thì acme.sh báo Cert success, bộ chứng chỉ nằm trong ~/.acme.sh/example.com_ecc/ (acme.sh mặc định sinh khoá ECDSA), gồm fullchain.cer và example.com.key.
Bước 4: đưa lên balancer và trỏ nginx
Cert cấp ngoài certbot được để riêng ở /etc/nginx/ssl-custom/<domain>/, tránh lẫn với /etc/letsencrypt/ do certbot quản lý:
D=/etc/nginx/ssl-custom/example.com
ssh root@balancer "mkdir -p $D"
scp ~/.acme.sh/example.com_ecc/fullchain.cer root@balancer:$D/fullchain.pem
scp ~/.acme.sh/example.com_ecc/example.com.key root@balancer:$D/privkey.pem
ssh root@balancer "chmod 600 $D/privkey.pem"
# trong khối 443 của site: ssl_certificate và ssl_certificate_key trỏ về $D
ssh root@balancer "nginx -t && systemctl reload nginx"
echo | openssl s_client -connect 10.0.0.12:443 -servername example.com 2>/dev/null \
| openssl x509 -noout -subject -issuer -datesKết quả mong đợi: issuer của Let's Encrypt, hạn 90 ngày. Dùng reload chứ không restart để không cắt kết nối của các site khác.
5. Dùng certbot cho DNS-01: chế độ manual và plugin DNS
Nếu đã quen certbot, dùng --manual --preferred-challenges dns để làm thủ công, hoặc plugin DNS để tự động:
# Plugin Cloudflare (gói python3-certbot-dns-cloudflare): tự thêm và xoá TXT
certbot certonly --dns-cloudflare \
--dns-cloudflare-credentials /root/.secrets/cloudflare.ini \
-d example.com -d www.example.comBẫy: chạy certbot trên chính balancer đang bị chặn chiều ra thì vẫn timeout vì không gọi được API ACME. Nơi chạy client ACME quan trọng ngang với kiểu xác thực. Chi tiết tham số plugin có trong tài liệu sử dụng certbot.
6. Wildcard bắt buộc phải dùng DNS-01
Let's Encrypt chỉ cấp chứng chỉ wildcard qua DNS-01, vì không có URL nào đại diện cho "mọi subdomain" để HTTP-01 kiểm tra. Một chứng chỉ cho cả apex và wildcard cần hai giá trị TXT trên cùng một tên _acme-challenge.example.com:
~/acme_src/acme.sh --issue --server letsencrypt \
-d example.com -d '*.example.com' \
--dns --yes-I-know-dns-manual-mode-enough-go-ahead-pleaseHai bản ghi TXT trùng tên là hợp lệ, đừng xoá bản này để thêm bản kia. Với site cần OV hoặc chứng chỉ mua sẵn, dùng chứng chỉ trả phí như trong bài Cài SSL thương mại: wildcard và OV.
7. Tự động gia hạn chứng chỉ DNS-01
DNS-01 thủ công đủ để chữa cháy, nhưng 90 ngày sau lại phải làm lại. Có hai hướng:
- Lặp lại thủ công bước 1–4 với giá trị TXT mới.
- Dùng API DNS: hook
dns_cfcủa acme.sh tự thêm TXT, chờ xác thực, lấy cert rồi xoá TXT. Token chỉ cấp quyềnZone.DNS:Editcho đúng zone cần, lưu file quyền 600, không commit vào git.
# Token đọc từ file riêng, không gõ trên dòng lệnh
export CF_Token="$(cat ~/.secrets/cf-dns-edit.token)"
~/acme_src/acme.sh --issue --server letsencrypt --dns dns_cf -d example.com -d www.example.com
# Bước deploy: chép cert và reload nginx sau mỗi lần gia hạn
~/acme_src/acme.sh --install-cert -d example.com --ecc \
--fullchain-file /tmp/example.com/fullchain.pem --key-file /tmp/example.com/privkey.pem \
--reloadcmd "scp /tmp/example.com/*.pem root@balancer:/etc/nginx/ssl-custom/example.com/ && ssh root@balancer 'nginx -t && systemctl reload nginx'"Bản clone ở bước 1 không tự chạy định kỳ. Muốn tự gia hạn, cài bằng ./acme.sh --install trên một máy luôn bật và có mạng quốc tế để nó tạo cron hằng ngày, không phải máy cá nhân.
Một điểm dễ quên: pipeline gia hạn định kỳ (xem bài Jenkins làm "control plane" cho hàng trăm website) thường chỉ quét cấu hình trỏ tới /etc/letsencrypt/. Cert trong ssl-custom cần cách theo dõi riêng, tối thiểu là:
for f in /etc/nginx/ssl-custom/*/fullchain.pem; do
openssl x509 -in "$f" -noout -checkend 1296000 >/dev/null || echo "Sắp hết hạn (< 15 ngày): $f"
done8. Lỗi thường gặp với DNS-01 và cách khắc phục
- TTL và propagation: bản ghi đã thêm nhưng DNS-01 vẫn báo "Incorrect TXT record". Thường do kiểm tra quá sớm hoặc nameserver cập nhật chậm. Kiểm tra trên từng nameserver có thẩm quyền; resolver công cộng có thể còn cache câu trả lời "không có bản ghi".
- Tên bản ghi bị nhân đôi: giao diện DNS tự nối tên zone, gõ đủ tên sẽ thành
_acme-challenge.example.com.example.com. Chỉ gõ phần_acme-challenge. - DNS của domain không nằm trong tay mình: không thể mỗi 90 ngày lại nhờ khách thêm TXT. Cách gọn là CNAME delegation: khách tạo một lần bản ghi
_acme-challenge.example.com CNAME _acme-challenge.acme.example.nettrỏ sang một zone do đội vận hành quản lý. acme.sh dùng--challenge-alias acme.example.netđể ghi TXT vào zone đích. - Bản ghi CAA chặn Let's Encrypt: nếu domain có CAA chỉ cho phép một CA khác, đơn DNS-01 bị từ chối dù TXT đúng. Kiểm tra bằng
dig CAA example.com +short. - Quên tên trong chứng chỉ: cấp cho apex mà quên
wwwthì trình duyệt báo lỗi tên. Kiểm tra cả hai tên sau khi reload.
9. Đánh đổi khi dùng DNS-01
DNS-01 cho phép cấp chứng chỉ ngay cả khi balancer bị cô lập khỏi mạng quốc tế, không cần đụng tới firewall hay Cloudflare proxy. Đổi lại:
- Có thêm một loại cert sống ngoài certbot, với vị trí và lịch gia hạn riêng. Không theo dõi thì sẽ hết hạn mà không ai hay.
- Token API DNS có quyền sửa bản ghi của domain, nên phải giữ kỹ như giữ mật khẩu root.
- Khi mạng thông trở lại, HTTP-01 vẫn là cách ít việc nhất cho domain thường. Giữ DNS-01 cho wildcard, cho domain Let's Encrypt không gọi vào được, và làm dự phòng.
Một quy trình chỉ chạy được khi mạng "bình thường" thì nên có phương án B. Hãy thử DNS-01 trên một domain thử nghiệm trước khi thật sự cần.
Bài tiếp theo
Rate limit theo User-Agent: thuần hoá bot crawler. Phân loại bot tốt và bot xấu ngay trên balancer, giới hạn theo họ User-Agent thay vì theo IP, và cách không vô tình chặn Googlebot.
