SSD Nodes Learn Hosting plans →
Hướng dẫn Matt ConnorBởi Matt Connor

Cấp chứng chỉ wildcard với Certbot qua DNS-01

Cấu hình Certbot cấp wildcard qua DNS-01: tạo TXT tại _acme-challenge, chọn plugin DNS và tự động gia hạn thay vì nhập bản ghi thủ công.

Vì sao wildcard certificate cần DNS-01

Wildcard certificate bao phủ mọi subdomain cấp đầu tiên của một domain: *.example.com khớp với app.example.com, blog.example.com và mọi hostname khác chỉ sâu một label. Let's Encrypt chỉ cấp wildcard certificate thông qua challenge DNS-01, vì vậy Certbot phải chứng minh quyền kiểm soát DNS của domain bằng cách publish một TXT record tại _acme-challenge.example.com. Challenge HTTP-01 không đáp ứng được yêu cầu này, vì việc phục vụ một token file chỉ chứng minh quyền kiểm soát một hostname, tức hostname mà validation server đã truy cập để lấy file. Wildcard là xác nhận cho mọi tên có thể có ở một cấp dưới domain, và DNS là public record duy nhất đại diện cho toàn bộ namespace đó.

Yêu cầu này quyết định mọi phần còn lại trong bài. Để vượt qua DNS-01, bạn phải có khả năng tạo TXT record trong zone của domain, hoặc trong một validation zone riêng được _acme-challenge trỏ tới bằng CNAME (sẽ nói bên dưới), thủ công hoặc thông qua API (application programming interface) của DNS provider. Cách thủ công chỉ hoạt động cho lần cấp đầu tiên rồi sẽ fail khi renewal, vì lý do cụ thể được trình bày bên dưới. Cách dùng API thông qua Certbot DNS plugin có thể renewal tự động, không cần can thiệp, và đây là cấu hình bạn nên dùng khi hoàn tất.

Đây là chương về wildcard trong loạt hướng dẫn Certbot của chúng tôi. Certificate cho một hostname thông thường, cấu hình web server và các rule cho port 80 được trình bày trong Certbot với nginx trên Ubuntu 24.04 và Certbot với Apache trên Ubuntu 24.04.

Cách hoạt động của bản ghi TXT _acme-challenge

Khi Certbot yêu cầu *.example.com, Let's Encrypt trả về một token ngẫu nhiên. Certbot kết hợp token đó với account key ACME (automatic certificate management environment) của bạn, băm kết quả bằng SHA-256 rồi tạo ra một giá trị văn bản ngắn. Giá trị đó phải xuất hiện dưới dạng bản ghi TXT tại _acme-challenge.example.com. Sau đó, Let's Encrypt truy vấn các name server authoritative của domain từ hạ tầng của chính họ. Nếu bản ghi mà hệ thống đọc được khớp với giá trị mà hệ thống mong đợi, bạn đã chứng minh quyền kiểm soát zone. Quyền kiểm soát zone được chấp nhận là quyền kiểm soát mọi name bên dưới zone đó.

Có 2 chi tiết gây ra phần lớn lỗi:

  • Yêu cầu example.com và *.example.com trong cùng một certificate nghĩa là có 2 challenge riêng biệt. Cả 2 bản ghi TXT đều nằm tại cùng một name, _acme-challenge.example.com. Cả 2 phải tồn tại đồng thời. Thêm bản ghi thứ hai là đúng; thay thế bản ghi thứ nhất bằng bản ghi thứ hai sẽ khiến challenge thứ nhất thất bại.
  • Validation đọc dữ liệu từ các server authoritative của bạn, nhưng control panel của provider có thể mất 1 phút hoặc lâu hơn để đẩy bản ghi mới đến các server đó. Hãy kiểm tra từ bên ngoài trước khi cho validation chạy:
dig +short TXT _acme-challenge.example.com @1.1.1.1

Khi lệnh đó in ra giá trị mà Certbot yêu cầu, validation có thể thành công. Khi lệnh không in ra gì, hãy chờ rồi chạy lại.

Xem hoạt động một lần: chế độ thủ công

Chế độ thủ công yêu cầu bạn tự chỉnh sửa DNS. Đây là cách tốt nhất để hiểu cơ chế trước khi tự động hóa:

sudo certbot certonly --manual --preferred-challenges dns -d example.com -d '*.example.com'

Dấu ngoặc kép quanh wildcard ngăn shell xử lý * như một mẫu tên tệp. Certbot sẽ tạm dừng và hiển thị hướng dẫn:

Please deploy a DNS TXT record under the name:
_acme-challenge.example.com.
with the following value:
Jx9mQ2wLr8vTn5cKp0aYdG3hB7fZs4eN1oiRuXqMk6E

Tạo bản ghi TXT đó trong bảng điều khiển của nhà cung cấp DNS, xác nhận bản ghi đã hiển thị bằng lệnh dig ở trên, rồi mới nhấn Enter. Vì lần chạy này yêu cầu domain gốc và wildcard, Certbot sẽ hỏi 2 lần. Giữ cả 2 bản ghi cho đến khi quá trình cấp chứng chỉ hoàn tất. Khi thành công, đầu ra sẽ kết thúc bằng các dòng quen thuộc:

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/example.com/fullchain.pem

Vì sao chế độ thủ công không thể tự gia hạn

Mỗi lần gia hạn là một thử thách mới với token mới, nên giá trị TXT thay đổi mỗi lần. Bản ghi bạn dán hôm nay sẽ vô dụng sau 60 ngày. Bộ hẹn giờ gia hạn chạy Certbot ở chế độ không cần giám sát 2 lần mỗi ngày, và không có ai ngồi trước bàn phím để dán giá trị mới. Vì vậy, chứng chỉ được cấp ở chế độ thủ công sẽ gia hạn thất bại với lỗi chính xác sau:

Failed to renew certificate example.com with error: The manual plugin is not
working; there may be problems with your existing configuration.
The error was: PluginError('An authentication script must be provided with
--manual-auth-hook when using the manual plugin non-interactively.')

Bạn có thể đáp ứng yêu cầu đó bằng cách viết các script --manual-auth-hook gọi API của nhà cung cấp DNS, nhưng khi đó bạn đang tự xây dựng lại một DNS plugin. Hãy dùng chế độ thủ công để tìm hiểu quy trình, hoặc dùng cho một lần duy nhất trên domain mà bạn chưa thể tự động hóa DNS, đồng thời đặt lời nhắc trước ngày 90 thật lâu, vì Let's Encrypt không còn gửi email thông báo sắp hết hạn. Với mọi trường hợp khác, hãy dùng plugin.

Route dùng plugin: certbot-dns-cloudflare trên Ubuntu 24.04

Một DNS plugin lưu credential API của nhà cung cấp DNS và tự thực hiện toàn bộ quy trình tạo bản ghi TXT, khi cấp chứng chỉ và trong mỗi lần gia hạn. Ở đây dùng Cloudflare làm ví dụ vì đây là provider plugin mà nhiều người cần nhất và đã được đóng gói trong Ubuntu.

Các hướng dẫn Certbot của chúng tôi khuyến nghị dùng các gói apt trên Ubuntu 24.04. Khuyến nghị này cũng áp dụng cho Cloudflare:

sudo apt update
sudo apt install certbot python3-certbot-dns-cloudflare

Có một điểm cần lưu ý về version. Kho lưu trữ của 24.04 cung cấp plugin này ở version 2.0.0 cùng với Certbot 2.9.0; apt policy python3-certbot-dns-cloudflare hiển thị version của bạn. Việc lệch version này không gây vấn đề. Scoped API token vẫn hoạt động vì thư viện python3-cloudflare bên dưới trong 24.04 là version 2.11.1, cao hơn version 2.3.1 mà plugin cần để hỗ trợ token. Trên các bản Ubuntu cũ hơn, thư viện đó quá cũ để hỗ trợ token. Đây là nguyên nhân của các cảnh báo bạn có thể thấy trên mạng về việc apt plugin buộc phải dùng Global API Key. Trên 24.04, các cảnh báo đó không còn áp dụng.

Trong Cloudflare dashboard, hãy tạo một scoped API token, không dùng Global API Key: vào My Profile, chọn API Tokens, rồi chọn Create Token. Chỉ cấp permission Zone / DNS / Edit và giới hạn token cho zone duy nhất bạn sẽ cấp chứng chỉ. Lưu token vào một file chỉ root có thể đọc:

sudo mkdir -p /root/.secrets
sudo tee /root/.secrets/cloudflare.ini > /dev/null <<'EOF'
dns_cloudflare_api_token = paste_your_scoped_token_here
EOF
sudo chmod 600 /root/.secrets/cloudflare.ini

Certbot kiểm tra mode của file và cảnh báo về Unsafe permissions on credentials configuration file nếu những user khác có thể đọc file. Bây giờ hãy cấp chứng chỉ:

sudo certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /root/.secrets/cloudflare.ini \
  -d example.com -d '*.example.com'

Plugin tạo các bản ghi TXT thông qua API, chờ một khoảng thời gian ngắn để DNS propagation, cho phép quá trình validation chạy, rồi xóa các bản ghi. Nếu name server của zone cập nhật thay đổi chậm, hãy tăng thời gian chờ bằng --dns-cloudflare-propagation-seconds 60. Certificate được lưu tại /etc/letsencrypt/live/example.com/. Bạn trỏ nginx hoặc Apache đến fullchain.pem và privkey.pem đúng như trong các hướng dẫn cơ bản, bao gồm cả deploy hook.

Nếu plugin của provider không có trong apt

Archive của 24.04 chỉ đóng gói plugin cho một số provider, trong đó có Cloudflare, Route 53, DigitalOcean và interface RFC 2136 generic. Chạy apt search certbot-dns để xem danh sách. Nếu không có provider của bạn, đây là trường hợp duy nhất chúng tôi không áp dụng đầy đủ khuyến nghị ưu tiên apt: hãy cài Certbot và plugin từ snap, đồng thời gỡ Certbot cài bằng apt trước để hai renewal timer không cùng xử lý /etc/letsencrypt:

sudo apt remove certbot python3-certbot-dns-cloudflare
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
sudo snap set certbot trust-plugin-with-root=ok
sudo snap install certbot-dns-yourprovider

Snap plugin chỉ kết nối với Certbot của snap; nó không thể mở rộng Certbot của apt. Vì vậy, không được để hai bản cài đặt cùng tồn tại. Nếu DNS host của bạn hoàn toàn không có API, bạn có ba lựa chọn thực tế. Bạn có thể chuyển DNS của domain sang provider có API. Bạn có thể tự chạy name server và trỏ plugin rfc2136 vào đó. Hoặc bạn có thể giữ DNS host hiện tại và thêm một CNAME một lần từ _acme-challenge.example.com tới một validation zone có API, chẳng hạn dịch vụ acme-dns được host sẵn hoặc acme-dns server do bạn tự chạy. Let's Encrypt sẽ theo CNAME này trong quá trình validation, nên TXT record nằm trong zone được ủy quyền và main zone của bạn không cần thay đổi lần nào nữa.

Gia hạn: chứng minh ngay bây giờ, không phải sau 60 ngày

Certbot ghi lại cách mỗi certificate được cấp trong /etc/letsencrypt/renewal/example.com.conf, bao gồm authenticator = dns-cloudflare và đường dẫn đến credentials, để timer chạy 2 lần mỗi ngày có thể gia hạn certificate mà không cần bạn can thiệp. Hãy diễn tập toàn bộ quy trình với staging environment:

sudo certbot renew --dry-run

Kết quả thành công cho biết credential hoạt động và quá trình validation hoàn tất từ đầu đến cuối; lần gia hạn thực tế sau 60 ngày sẽ đi theo cùng quy trình. Hôm nay nên thực hiện thêm 2 việc. Thứ nhất, certificate đã gia hạn trên disk chưa có hiệu lực cho đến khi web server reload certificate đó, vì vậy hãy cấu hình deploy hook được mô tả trong các hướng dẫn về nginx và Apache. Thứ hai, hãy bảo vệ credentials file: bất kỳ ai có thể đọc file này đều có thể chỉnh sửa DNS zone của bạn. Việc đó đủ để chuyển hướng mail hoặc tự vượt qua các DNS-01 challenge. Đặt file ở mode 600 trong /root, giới hạn token cho một zone và rotate token nếu bạn nghi ngờ credentials đã bị lộ.

Khi bạn không cần wildcard

Wildcard phù hợp với nhiều subdomain hoặc các subdomain bạn không thể dự đoán trước. Đây không phải lựa chọn mặc định phù hợp cho mọi trường hợp khác.

  • Một subdomain hoặc một số ít subdomain đã biết: certificate SAN (subject alternative name) thông thường đơn giản hơn. certbot --nginx -d example.com -d www.example.com -d app.example.com hỗ trợ tối đa 100 tên qua HTTP-01 thông thường và không cần lưu credential của DNS API trên server.
  • Wildcard chỉ khớp đúng một label. *.example.com không bao phủ example.com không có label, vì vậy các lệnh ở trên yêu cầu cả hai tên này. Wildcard cũng không bao phủ a.b.example.com; trường hợp đó cần *.b.example.com.
  • Một private key được dùng cho mọi subdomain. Nếu máy lưu private key đó bị breach, mọi tên được wildcard bao phủ sẽ đồng thời bị ảnh hưởng.
  • Nếu Traefik xử lý TLS (transport layer security) cho các container, bạn không cần Certbot: Traefik tự yêu cầu wildcard certificate qua DNS-01, bằng loại provider token tương tự.

Wildcard thực sự hữu ích cho các subdomain theo từng khách hàng hoặc ứng dụng, được tạo nhanh hơn tốc độ bạn muốn issue lại certificate, và cho các host nội bộ không có port 80 public, chẳng hạn các service chỉ có thể truy cập qua VPN WireGuard. DNS-01 không bao giờ kết nối đến host đang được cấp certificate, vì vậy ngay cả một máy hoàn toàn private cũng có thể giữ certificate được public trust.

FAQ

Certbot có thể cấp wildcard certificate bằng HTTP-01 không?

Không. HTTP-01 chứng minh quyền kiểm soát một hostname, vì validation server lấy token file từ chính hostname đó. Wildcard bao phủ mọi tên cấp đầu tiên dưới domain, nên Let's Encrypt yêu cầu challenge DNS-01 cho trường hợp này, còn các authenticator --nginx, --apache, --webroot và --standalone đều dựa trên HTTP. Cách duy nhất là tạo TXT record tại _acme-challenge.example.com (hoặc trong zone mà nó được delegate bằng CNAME), thủ công hoặc bằng DNS plugin.

Wildcard certificate có bao phủ root domain không?

Không. Wildcard chỉ khớp chính xác một label, nên *.example.com bao phủ www.example.com nhưng không bao phủ example.com dạng bare, và cũng không bao phủ a.b.example.com. Hãy request cả hai tên trên cùng một certificate bằng -d example.com -d '*.example.com'. Việc này tạo ra hai challenge, và cả hai TXT record đều nằm tại cùng tên _acme-challenge.example.com, vì vậy hãy thêm record thứ hai mà không xóa record đầu tiên.

Vì sao wildcard certificate của tôi không tự động renewal?

Vì certificate được cấp bằng --manual. Mỗi lần renewal cần một giá trị TXT mới hoàn toàn, nhưng timer chạy unattended không có cách nào tự nhập giá trị đó, nên renewal dừng lại với lỗi An authentication script must be provided with --manual-auth-hook when using the manual plugin non-interactively. Hãy cấp lại certificate bằng DNS plugin như certbot-dns-cloudflare, hoặc cung cấp các script --manual-auth-hook và --manual-cleanup-hook để chỉnh sửa record thông qua API của provider.

_acme-challenge TXT record mất bao lâu để xuất hiện?

Thời gian phụ thuộc vào DNS provider: từ vài giây đến vài phút. Validation đọc từ authoritative server của zone, vì vậy hãy kiểm tra bằng dig +short TXT _acme-challenge.example.com @1.1.1.1 và chờ đến khi giá trị mong đợi xuất hiện trước khi tiếp tục lần chạy thủ công. Khi dùng plugin, hãy tăng thời gian chờ tích hợp sẵn thông qua propagation option của plugin, chẳng hạn --dns-cloudflare-propagation-seconds 60, nếu validation báo không tìm thấy record.

Wildcard certificate có kém an toàn hơn certificate thông thường không?

Mật mã học giống hệt nhau. Khác biệt nằm ở vận hành: một private key bao phủ mọi subdomain, nên khi bị compromise, phạm vi ảnh hưởng sẽ rộng hơn; ngoài ra, credential của DNS API mà automation yêu cầu cũng là secret nhạy cảm được lưu trên server. Nếu bạn chỉ chạy một vài subdomain đã biết, SAN certificate sẽ tránh được cả hai rủi ro. Đây chính là trường hợp mà hướng dẫn này khuyến nghị không dùng wildcard.