Phần lớn website trên mô hình Cloud VPS dùng chứng chỉ Let's Encrypt cấp và gia hạn tự động. Nhưng vẫn có khách cần SSL thương mại: doanh nghiệp muốn chứng chỉ thể hiện tên công ty (OV), hoặc đã mua sẵn một chứng chỉ wildcard cho toàn bộ subdomain. Tài liệu này mô tả quy trình cài SSL thương mại lên balancer chạy nginx: chọn loại chứng chỉ, tạo CSR và private key, ghép chain, đặt file, cấu hình nginx, kiểm tra, rollback và những lỗi hay gặp.

1. SSL thương mại khác gì Let's Encrypt?

Về mặt mã hoá, không khác gì: cùng loại khoá thì kết nối TLS mạnh như nhau. Khác biệt của SSL thương mại nằm ở mức xác minh danh tính, cách cấp và cách gia hạn:

LoạiCA xác minh điều gìKhi nào dùng
DV (Domain Validation)Chỉ xác minh quyền kiểm soát domain (file HTTP, bản ghi DNS hoặc email)Đa số website. Let's Encrypt chỉ cấp loại này
OV (Organization Validation)Thêm bước xác minh pháp nhân: tên, địa chỉ doanh nghiệp được ghi vào trường SubjectDoanh nghiệp cần chứng chỉ thể hiện tên công ty
EV (Extended Validation)Quy trình xác minh pháp nhân chặt hơn OVNgân hàng, tài chính. Trình duyệt hiện đại không còn hiển thị thanh địa chỉ màu xanh
Wildcard (*.example.com)DV hoặc OV, phủ mọi subdomain một cấpSite có nhiều subdomain thay đổi thường xuyên

Hai cấu hình SSL thương mại hay gặp: chứng chỉ OV có SAN gồm example.com và *.example.com, hoặc chứng chỉ wildcard DV có SAN gồm *.site-a.example và site-a.example. Lưu ý: wildcard *.example.com không tự phủ domain gốc example.com. Cần kiểm tra SAN có cả hai.

Điểm mà nhiều người chưa để ý: theo lộ trình rút ngắn thời hạn của CA/Browser Forum, từ năm 2026 chứng chỉ công khai có hiệu lực tối đa 200 ngày, và mức này còn tiếp tục giảm trong các năm sau. Dù mua SSL thương mại theo năm, bạn vẫn phải nhận chứng chỉ cấp lại (reissue) và cài lại ít nhất hai lần mỗi năm, nên quy trình cần đủ gọn để lặp lại.

2. Tạo CSR và private key cho SSL thương mại: nên tự làm

CSR (Certificate Signing Request) chứa public key và thông tin domain, gửi cho CA để ký. Private key không bao giờ được gửi đi, nên cách an toàn nhất là tự tạo cặp này:

# RSA 2048 bit, không đặt passphrase (nginx cần đọc key khi reload)
openssl req -new -newkey rsa:2048 -nodes \
    -keyout example.com.key \
    -out example.com.csr \
    -subj "/C=VN/L=Ha Noi/O=Ten Cong Ty/CN=example.com" \
    -addext "subjectAltName=DNS:example.com,DNS:*.example.com"

chmod 600 example.com.key

# xem lại nội dung CSR trước khi gửi cho CA
openssl req -in example.com.csr -noout -text | grep -E "Subject:|DNS:"

Thực tế không phải lúc nào cũng được như vậy. Có khi đại lý tạo sẵn CSR và private key rồi gửi chung trong một file văn bản cùng chứng chỉ, có khi khách gửi chứng chỉ, key và một bản PFX kèm file mật khẩu. Cài vẫn được, nhưng private key đã đi qua email và nhiều máy trung gian. Khuyến nghị: đề nghị khách để đội vận hành tạo CSR và chỉ gửi CSR cho đại lý. Nếu key đã bị gửi qua kênh không an toàn, hãy coi như lộ: tạo CSR mới và xin CA cấp lại.

Nếu nhận file PFX/PKCS#12, có thể tách ra bằng openssl pkcs12:

openssl pkcs12 -in cert.pfx -clcerts -nokeys -out domain.crt
openssl pkcs12 -in cert.pfx -cacerts -nokeys -out intermediate.pem
openssl pkcs12 -in cert.pfx -nocerts -nodes -out privkey.pem

3. Ghép chain SSL thương mại: leaf trước, intermediate sau

CA thường gửi ba thứ: chứng chỉ của domain (leaf), một hoặc nhiều chứng chỉ trung gian (intermediate) và chứng chỉ gốc (root). Trình duyệt có sẵn root nhưng không phải lúc nào cũng có intermediate, nên server phải gửi kèm intermediate khi bắt tay TLS. Với nginx, leaf và intermediate được ghép vào một file, leaf đứng đầu:

# 1. kiểm tra chain hợp lệ trước khi ghép
openssl verify -CAfile root.pem -untrusted intermediate.pem domain.crt
# domain.crt: OK

# 2. ghép: leaf trước, intermediate sau, KHÔNG cần root
cat domain.crt intermediate.pem > fullchain.pem

# 3. đếm số chứng chỉ trong file ghép
grep -c "BEGIN CERTIFICATE" fullchain.pem
# 2

Không đưa root vào fullchain.pem, thêm vào chỉ làm gói bắt tay nặng hơn. Tài liệu cấu hình HTTPS server của nginx giải thích rõ chuyện chain và thứ tự này.

So khớp private key với chứng chỉ

Trước khi đưa lên server, luôn xác minh key và cert là một cặp. Với khoá RSA, so modulus của hai bên:

openssl x509 -noout -modulus -in domain.crt | openssl md5
openssl rsa  -noout -modulus -in privkey.pem | openssl md5
# hai dòng MD5 phải giống hệt nhau

# cách dùng được cho cả RSA lẫn ECDSA: so public key
diff <(openssl x509 -in domain.crt -noout -pubkey) \
     <(openssl pkey -in privkey.pem -pubout) && echo "KHOP"

Chỉ khi chứng chỉ SSL thương mại qua cả hai bước này mới đụng tới balancer.

4. Đặt chứng chỉ SSL thương mại trên balancer

SSL kết thúc ở tầng balancer (xem bài Nginx reverse proxy và SSL offload trên balancer), nên chứng chỉ chỉ cần đặt trên balancer đang phục vụ domain. Giữ ba nguyên tắc:

  1. Thư mục riêng /etc/nginx/ssl-custom/<domain>/, tách khỏi /etc/letsencrypt/ để Certbot không ghi đè.
  2. Không xoá chứng chỉ Let's Encrypt cũ, giữ làm đường lui: rollback chỉ là trả lại hai dòng cấu hình.
  3. Backup file cấu hình site trước khi sửa.
# trên balancer phục vụ domain
cp /etc/nginx/conf.d/example.com.conf \
   /etc/nginx/conf.d/example.com.conf.bak_$(date +%Y%m%d%H%M%S)

mkdir -p /etc/nginx/ssl-custom/example.com
# (copy fullchain.pem và privkey.pem lên bằng scp)
chmod 600 /etc/nginx/ssl-custom/example.com/privkey.pem
chown root:root /etc/nginx/ssl-custom/example.com/*

Đuôi .bak_* không khớp mẫu include conf.d/*.conf nên nginx không nạp nhầm bản backup. Bản backup chứa private key, không bao giờ commit vào repo.

Cấu hình nginx

Trong file cấu hình site, chỉ đổi đúng hai dòng trỏ sang chứng chỉ SSL thương mại:

server {
    listen 443 ssl http2;
    server_name example.com www.example.com;

-   ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
-   ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
+   ssl_certificate     /etc/nginx/ssl-custom/example.com/fullchain.pem;
+   ssl_certificate_key /etc/nginx/ssl-custom/example.com/privkey.pem;
    ...
}
nginx -t && systemctl reload nginx

Luôn nginx -t trước và dùng reload chứ không restart. Nếu nginx -t báo lỗi thì dừng lại, các site khác vẫn chạy với cấu hình cũ. Ý nghĩa các chỉ thị nằm trong tài liệu module ngx_http_ssl của nginx.

5. Kiểm tra sau khi cài SSL thương mại

Trình duyệt trên máy mình không đủ để kết luận SSL thương mại đã cài đúng, vì nó có thể đã cache intermediate từ lần truy cập trước. Hãy kiểm tra bằng openssl s_client từ một máy bên ngoài:

# issuer, subject, ngày hiệu lực của chứng chỉ server đang trả về
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
  | openssl x509 -noout -issuer -subject -dates

# toàn bộ chain server gửi về: phải thấy 0 (leaf) và 1 (intermediate)
echo | openssl s_client -connect example.com:443 -servername example.com -showcerts 2>/dev/null \
  | grep -E "^ *[0-9] s:|^ *i:"

# kết quả xác minh: mong đợi "Verify return code: 0 (ok)"
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
  | grep "Verify return code"

Tham số -servername bắt buộc: thiếu SNI thì nginx trả về chứng chỉ của server mặc định, và bạn kiểm tra nhầm chứng chỉ của site khác. Với chứng chỉ OV, dòng subject phải có tên tổ chức. Với wildcard, subject là CN=*.site-a.example.

6. Lỗi thường gặp khi cài SSL thương mại

Triệu chứngNguyên nhânCách xử lý
Chrome vẫn ổn, nhưng Android cũ, curl hoặc webhook báo unable to get local issuer certificateThiếu intermediate: chỉ đặt domain.crt vào ssl_certificateGhép domain.crt + intermediate.pem thành fullchain.pem, kiểm tra lại bằng -showcerts
nginx -t báo key values mismatchKey không khớp cert: lấy nhầm key của CSR khácSo modulus như mục 3. Mất key thì tạo CSR mới và xin reissue
nginx -t báo cùng lỗi key values mismatch dù key đúngSai thứ tự chain: intermediate đứng trước leaf trong fullchain.pem, nginx so key với chứng chỉ đầu tiênGhép lại theo thứ tự leaf → intermediate
Bản www hoặc domain gốc báo sai tênSAN thiếu tên đó, ví dụ wildcard không kèm domain gốcĐọc SAN bằng openssl x509 -noout -ext subjectAltName trước khi cài
PEM_read_bio, no start lineFile có ký tự lạ do copy từ email, xuống dòng kiểu WindowsChuyển về dạng PEM chuẩn, kiểm tra bằng openssl x509 -in domain.crt -noout -text
Chứng chỉ hết hạn mà không ai báoScript gia hạn chỉ quét các site dùng đường dẫn letsencryptTheo dõi hạn chứng chỉ SSL thương mại riêng, đặt lịch cài lại trước hạn

Lỗi cuối cùng đáng nói thêm. Script gia hạn tự động kiểu trong bài SSL cho hàng trăm domain với Certbot thường quét các file cấu hình có 443 và đường dẫn letsencrypt. Sau khi một site đổi sang ssl-custom, nó rơi khỏi danh sách quét. Certbot vẫn gia hạn bản Let's Encrypt cũ, vô hại vì nginx không còn dùng bản đó, nhưng hạn của chứng chỉ SSL thương mại phải được theo dõi bằng cách khác, ví dụ một script đọc ngày hết hạn của mọi file trong ssl-custom:

# cảnh báo chứng chỉ còn dưới 30 ngày (2592000 giây)
for c in /etc/nginx/ssl-custom/*/fullchain.pem; do
  openssl x509 -in "$c" -noout -checkend 2592000 >/dev/null \
    || echo "SAP HET HAN: $c $(openssl x509 -in "$c" -noout -enddate)"
done

7. Rollback và dọn dẹp sau khi đổi sang SSL thương mại

# quay lại Let's Encrypt trong vài giây
cp /etc/nginx/conf.d/example.com.conf.bak_<timestamp> /etc/nginx/conf.d/example.com.conf
nginx -t && systemctl reload nginx

Vì chứng chỉ cũ chưa bị xoá, site trở lại trạng thái trước ngay sau lần reload. Khi chứng chỉ mới chạy ổn, xoá file .bak_*, còn chứng chỉ Let's Encrypt cũ giữ lại làm dự phòng.

8. Checklist cài SSL thương mại

  • Kiểm tra bằng openssl trước, đụng server sau. Thiếu chain, key lệch, SAN thiếu tên của SSL thương mại đều phát hiện được trước khi upload.
  • Tách thư mục, giữ đường lui. ssl-custom/<domain>/ cộng chứng chỉ Let's Encrypt còn nguyên: mỗi lần thay chỉ đổi hai dòng.
  • Private key là thứ cần bảo vệ nhất. Tự tạo CSR, chmod 600, không gửi key qua email, không commit vào repo.
  • Trả tiền không có nghĩa là quên đi. Với thời hạn tối đa ngày càng ngắn, SSL thương mại cũng cần lịch theo dõi và cảnh báo như mọi chứng chỉ khác.

Bài tiếp theo

Gom log truy cập Nginx về Loki với Fluent Bit. Đưa access log của các balancer về một nơi để tra cứu, vẽ dashboard và tìm IP gây tải bằng vài câu truy vấn.