Slack nổ: “API trả lỗi ‘certificate has expired’”. Browser vào domain — Chrome hiện cảnh báo đỏ lòm “Your connection is not private”, error code NET::ERR_CERT_DATE_INVALID. Certificate hết hạn 2 giờ trước. Certbot auto-renew đã fail silent từ 3 tuần trước vì DNS challenge bị đổi API key mà không ai cập nhật. Toàn bộ API public chết, mobile app crash vì certificate pinning reject cert mới nếu có, mà cert cũ thì hết hạn.
Incident chỉ mất 15 phút để fix (renew thủ công), nhưng 2 giờ downtime vì không ai biết cert sắp hết hạn cho đến khi nó thực sự hết. TLS/HTTPS không phải thứ “setup một lần rồi quên” — nó cần hiểu đủ sâu để configure đúng, debug nhanh khi lỗi, và monitor liên tục. Bài này đi qua những gì backend developer thực sự cần biết về TLS, từ góc nhìn thực hành.
HTTPS không chỉ là “mã hoá”
Nhiều dev hiểu HTTPS đơn giản là “encrypt data giữa client và server”. Đúng nhưng chưa đủ — HTTPS qua TLS cung cấp ba đảm bảo quan trọng.
Confidentiality (bảo mật) — dữ liệu truyền giữa client và server được mã hoá, bên thứ ba trên đường truyền không đọc được. Đây là phần mọi người đều biết. Không có TLS, ai ở giữa (ISP, wifi công cộng, attacker trên cùng mạng) đều đọc được password, token, dữ liệu cá nhân dạng plaintext.
Integrity (toàn vẹn) — dữ liệu không bị sửa đổi trên đường truyền. Nếu ai đó cố inject quảng cáo vào HTTP response (ISP hay làm), hoặc sửa nội dung API response, TLS phát hiện và reject. MAC (Message Authentication Code) trong TLS đảm bảo mỗi byte nhận được đúng là byte server đã gửi.
Authentication (xác thực) — client biết chắc đang nói chuyện với đúng server, không phải attacker giả mạo. Certificate chứa domain name và được CA (Certificate Authority) ký xác nhận — client verify certificate để chắc chắn api.example.com thật sự là api.example.com, không phải máy attacker. Không có authentication, mã hoá vô nghĩa — bạn mã hoá gửi cho attacker thì attacker vẫn đọc được.
Ba đảm bảo này hoạt động cùng nhau. Thiếu bất kỳ cái nào thì hai cái còn lại giảm giá trị đáng kể. Đây là lý do mọi API production phải chạy HTTPS — không phải vì “best practice” mà vì thiếu nó thì user data không an toàn.
TLS handshake đơn giản hoá
Khi browser hoặc HTTP client mở connection HTTPS, trước khi gửi bất kỳ byte dữ liệu ứng dụng nào, client và server phải thực hiện TLS handshake — quá trình thoả thuận phiên bản TLS, cipher suite, xác thực certificate, và trao đổi khoá mã hoá.
Mình sẽ mô tả TLS 1.3 (phiên bản hiện tại) vì nó đơn giản hơn TLS 1.2 và là thứ bạn nên dùng. Handshake diễn ra trong một round-trip (1-RTT), nhanh hơn TLS 1.2 cần hai round-trip.
Bước 1 — ClientHello. Client gửi cho server: danh sách cipher suite mà nó hỗ trợ, phiên bản TLS cao nhất nó chấp nhận, một giá trị random, và key share (public key cho key exchange). Trong TLS 1.3, client gửi key share ngay từ đầu — đây là lý do chỉ cần 1-RTT thay vì 2 như TLS 1.2.
Bước 2 — ServerHello + Certificate. Server chọn cipher suite, gửi lại key share của mình, và gửi certificate (chứa public key của server, domain name, chữ ký của CA). Server cũng gửi CertificateVerify — chứng minh nó sở hữu private key tương ứng với certificate.
Bước 3 — Client verify. Client kiểm tra certificate: domain có khớp với hostname đang kết nối không? Certificate còn hạn không? Chữ ký CA có hợp lệ không? CA có nằm trong trust store không? Nếu mọi thứ OK, client và server tính session key từ key share đã trao đổi. Từ đây mọi dữ liệu được mã hoá bằng session key này.
Toàn bộ handshake TLS 1.3 mất khoảng 1 round-trip (vài chục millisecond trên cùng region). TLS 1.3 còn hỗ trợ 0-RTT cho connection tái lập — client gửi data ngay từ ClientHello dùng session ticket từ lần trước. Nhanh hơn nhưng có trade-off replay attack — chỉ dùng cho request idempotent.
Bạn không cần nhớ chi tiết từng byte trong handshake. Nhưng cần hiểu flow để debug: khi openssl s_client báo lỗi ở bước nào, bạn biết nguyên nhân nằm ở đâu.
Certificate chain: root → intermediate → leaf
Certificate không đứng một mình — nó nằm trong một chuỗi tin cậy (chain of trust) mà client duyệt từ dưới lên để xác nhận.
Leaf certificate (end-entity certificate) là certificate của domain bạn — api.example.com. Nó chứa public key của server, domain name (trong Subject Alternative Name — SAN), thời hạn, và chữ ký của CA cấp phát. Đây là certificate mà server gửi cho client trong handshake.
Intermediate certificate là CA trung gian — ký leaf certificate. Nó được ký bởi root CA hoặc intermediate CA khác cao hơn. Intermediate CA tồn tại vì lý do bảo mật: root CA quá quan trọng để ký trực tiếp cho mọi domain, nên uỷ quyền cho intermediate. Nếu intermediate bị compromise, chỉ cần revoke intermediate đó, root CA vẫn an toàn.
Root certificate là CA gốc — tự ký (self-signed) và được cài sẵn trong trust store của OS/browser. Chrome, Firefox, macOS, Windows, Android, iOS đều có danh sách root CA mà chúng tin tưởng. Khi client verify certificate chain, nó đi từ leaf → intermediate → root, kiểm tra chữ ký ở mỗi bước. Nếu cuối cùng gặp root CA có trong trust store → chain hợp lệ.
Sai lầm phổ biến nhất mà mình gặp: server chỉ gửi leaf certificate mà quên gửi intermediate. Client nhận leaf, cố tìm intermediate trong trust store — không có (vì trust store chỉ chứa root). Kết quả: “unable to verify the first certificate” hoặc “incomplete certificate chain”. Một số browser (Chrome, Firefox) tự fetch intermediate certificate thiếu nếu leaf có Authority Information Access (AIA) URL — nên trên browser thì OK. Nhưng curl, wget, hoặc code thì không — nên lỗi chỉ xảy ra khi gọi API từ server, không xảy ra trên browser. Bug này rất hay gây nhầm lẫn.
Fix đơn giản: configure server gửi cả leaf + intermediate certificate. Với Nginx:
ssl_certificate /etc/ssl/certs/fullchain.pem; # leaf + intermediate
ssl_certificate_key /etc/ssl/private/privkey.pem;
File fullchain.pem chứa leaf certificate ở trên, intermediate ở dưới — concatenate theo thứ tự. Let’s Encrypt cung cấp sẵn fullchain.pem — dùng file này thay vì cert.pem (chỉ có leaf).
Let’s Encrypt và auto-renewal
Let’s Encrypt thay đổi hoàn toàn cách thế giới dùng TLS — certificate miễn phí, tự động, và hết hạn sau 90 ngày (thay vì 1-2 năm như commercial CA). Thời hạn ngắn buộc bạn tự động hoá renewal — và đó là điều tốt, vì manual renewal sớm muộn cũng quên.
Certbot — cách phổ biến nhất
# Cài certbot
sudo apt install certbot python3-certbot-nginx
# Lấy cert cho Nginx, tự cấu hình
sudo certbot --nginx -d api.example.com -d www.example.com
# Chỉ lấy cert, không sửa config Nginx
sudo certbot certonly --webroot -w /var/www/html -d api.example.com
Certbot tự thêm cron job hoặc systemd timer cho auto-renewal. Kiểm tra renewal chạy đúng:
sudo certbot renew --dry-run
Nếu --dry-run thành công nghĩa là renewal sẽ hoạt động khi cert sắp hết hạn. Certbot renew chạy hàng ngày nhưng chỉ thực sự renew khi cert còn dưới 30 ngày — không tốn quota.
acme.sh — nhẹ hơn, nhiều DNS provider
# Dùng DNS challenge (không cần webserver)
acme.sh --issue --dns dns_cf -d api.example.com
# dns_cf là Cloudflare plugin, có hàng chục DNS provider khác
DNS challenge hữu ích khi server không expose port 80 ra internet (server nội bộ, wildcard cert). Mình dùng acme.sh cho wildcard certificate: *.example.com — phải dùng DNS challenge vì HTTP challenge không hỗ trợ wildcard.
cert-manager trong Kubernetes
Trong Kubernetes, cert-manager là chuẩn de facto. Nó tự động lấy certificate từ Let’s Encrypt (hoặc CA khác), lưu vào Kubernetes Secret, và auto-renew.
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: api-tls
spec:
secretName: api-tls-secret
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
dnsNames:
- api.example.com
Ingress controller (Nginx, Traefik) đọc Secret này và configure TLS tự động. Khi cert sắp hết hạn, cert-manager renew và update Secret — Ingress controller reload tự động. Mình từng chạy 50+ domain trên cùng cluster Kubernetes với cert-manager, không phải nhớ renew bất kỳ cert nào.
Bất kể dùng tool nào, nguyên tắc vàng là: renewal phải tự động và được test định kỳ. Mỗi tháng chạy --dry-run hoặc kiểm tra cert-manager log — đảm bảo pipeline renewal vẫn hoạt động. Cert hết hạn là incident 100% có thể tránh được.
Debug TLS bằng openssl s_client
openssl s_client là công cụ debug TLS mạnh nhất mà mình dùng gần như hàng tuần. Nó cho bạn thấy mọi thứ trong TLS handshake — certificate chain, cipher suite, TLS version, lỗi cụ thể.
Kiểm tra certificate cơ bản
openssl s_client -connect api.example.com:443 -servername api.example.com </dev/null 2>/dev/null | openssl x509 -noout -dates -subject -issuer
Output cho biết: subject (domain name), issuer (CA nào cấp), notBefore/notAfter (thời hạn). Dùng hàng ngày để verify cert mới đã được deploy đúng.
Kiểm tra certificate chain
openssl s_client -connect api.example.com:443 -servername api.example.com -showcerts </dev/null
-showcerts hiển thị tất cả certificate trong chain. Nếu bạn chỉ thấy một certificate (leaf) mà không có intermediate → chain không đầy đủ → client nào đó sẽ fail. Phải thấy ít nhất leaf + intermediate.
Kiểm tra TLS version và cipher
openssl s_client -connect api.example.com:443 -tls1_3
openssl s_client -connect api.example.com:443 -tls1_2
openssl s_client -connect api.example.com:443 -tls1_1 # nên fail nếu đã disable
Thử kết nối với từng TLS version để verify server chỉ chấp nhận version an toàn. TLS 1.0 và 1.1 nên trả lỗi handshake.
Debug connection failure
Khi curl hay code trả lỗi TLS mơ hồ, openssl s_client cho thông tin chi tiết hơn nhiều:
openssl s_client -connect api.example.com:443 -servername api.example.com
# Đọc output: "Verify return code: 0 (ok)" → OK
# "Verify return code: 10 (certificate has expired)" → cert hết hạn
# "Verify return code: 21 (unable to verify the first certificate)" → thiếu intermediate
Mình đã debug hàng chục lỗi TLS chỉ bằng openssl s_client — nó là dig của thế giới TLS.
Lỗi TLS phổ biến và cách xử lý
Certificate expired
Nguyên nhân phổ biến nhất gây outage TLS. Auto-renewal fail nhưng không ai để ý. Fix ngay: renew thủ công, reload web server. Fix dài hạn: monitor cert expiry, alert trước 14 ngày.
# Kiểm tra ngày hết hạn
echo | openssl s_client -connect api.example.com:443 -servername api.example.com 2>/dev/null | openssl x509 -noout -enddate
Hostname mismatch
Certificate cấp cho example.com nhưng client kết nối api.example.com. Lỗi: “hostname mismatch” hoặc “certificate is not valid for this domain”. Fix: cấp cert bao gồm tất cả domain cần dùng trong SAN (Subject Alternative Name), hoặc dùng wildcard cert *.example.com.
SNI mismatch
SNI (Server Name Indication) là extension trong ClientHello cho server biết client muốn kết nối domain nào — quan trọng khi một IP host nhiều domain. Nếu client không gửi SNI (client cũ, hoặc code quên set), server không biết trả certificate nào → trả default cert → domain không khớp.
Debug bằng openssl s_client với -servername:
# Có SNI
openssl s_client -connect 203.0.113.42:443 -servername api.example.com
# Không có SNI (thường lấy cert default)
openssl s_client -connect 203.0.113.42:443
Nếu kết quả khác nhau, server đang dùng SNI routing — đảm bảo client code set hostname đúng.
Incomplete chain
Server chỉ gửi leaf cert, thiếu intermediate. Browser có thể tự fetch intermediate nhưng curl, code backend, mobile app thì không. Debug:
openssl s_client -connect api.example.com:443 -servername api.example.com -showcerts </dev/null 2>&1 | grep -c "BEGIN CERTIFICATE"
# 1 → chỉ có leaf, thiếu intermediate
# 2+ → có chain
Fix: dùng fullchain.pem thay vì cert.pem trong config web server.
Self-signed certificate
Client không trust CA tự ký → reject kết nối. Hoàn toàn đúng cho production — self-signed cert nghĩa là không có bên thứ ba nào xác nhận danh tính server. Chỉ dùng self-signed cho development/testing local. Trên production, dùng Let’s Encrypt — miễn phí, không có lý do gì dùng self-signed.
mTLS — xác thực hai chiều giữa service
TLS thông thường chỉ xác thực một chiều: client verify server. Server không verify client — bất kỳ ai cũng kết nối được (authentication ở tầng application: token, session, API key).
Mutual TLS (mTLS) thêm chiều ngược lại: server yêu cầu client gửi certificate, server verify certificate đó. Cả hai bên đều chứng minh danh tính bằng certificate. Không có valid client cert → connection bị reject trước khi đến application code.
mTLS phổ biến trong giao tiếp giữa microservices — thay vì mỗi service phải implement JWT verification hoặc API key, infrastructure layer (service mesh) xử lý authentication tự động. Istio, Linkerd đều hỗ trợ mTLS giữa pod trong Kubernetes — bật lên thì mọi traffic giữa service được mã hoá và xác thực, code ứng dụng không cần sửa.
Cấu hình mTLS thủ công với Nginx:
server {
listen 443 ssl;
ssl_certificate /etc/ssl/server.pem;
ssl_certificate_key /etc/ssl/server-key.pem;
ssl_client_certificate /etc/ssl/ca.pem; # CA ký client cert
ssl_verify_client on; # bắt buộc client cert
}
Quản lý mTLS thủ công (generate CA, ký cert cho mỗi service, rotate, revoke) là công việc nặng nề. Service mesh (Istio, Linkerd) tự động hoá toàn bộ: sinh cert cho mỗi pod, auto-rotate, enforce mTLS policy — đây là lý do chính mà team adopt service mesh.
Khi nào cần mTLS? Khi bạn cần zero-trust network — không tin tưởng network nội bộ, mọi kết nối phải được xác thực. Hoặc khi compliance yêu cầu encryption in transit cho tất cả traffic nội bộ (PCI DSS, SOC2). Nếu service chạy trong trusted network nhỏ và chưa có service mesh, mTLS có thể overkill — evaluate effort vs benefit cụ thể.
TLS termination: ở đâu trong kiến trúc
TLS termination là nơi connection TLS được giải mã — từ điểm đó về phía backend, traffic có thể là plaintext HTTP hoặc vẫn là TLS tuỳ cấu hình.
Terminate ở load balancer / reverse proxy
Pattern phổ biến nhất: Nginx, HAProxy, AWS ALB, Cloudflare nhận TLS connection từ client, giải mã, rồi forward HTTP plaintext đến backend. Backend chỉ nghe port 80, không cần quản lý certificate.
Ưu điểm: quản lý cert tập trung ở một chỗ, backend đơn giản hơn, load balancer thường có hardware accelerator cho TLS. Nhược điểm: traffic giữa load balancer và backend là plaintext — nếu attacker vào được internal network, đọc được traffic. Đây thường acceptable trong trusted network (VPC, private subnet), nhưng không acceptable cho compliance chặt.
End-to-end TLS (re-encryption)
Load balancer terminate TLS từ client, rồi mở connection TLS mới đến backend. Traffic luôn được mã hoá, kể cả internal. Load balancer cần trust backend cert (hoặc skip verify — không khuyến khích).
Ưu điểm: encryption mọi nơi, compliance-friendly. Nhược điểm: phức tạp hơn (hai bộ cert), latency tăng nhẹ (hai lần handshake), debug khó hơn.
TLS passthrough
Load balancer forward TLS connection thẳng đến backend mà không decrypt. Backend tự handle TLS. Load balancer không thể inspect HTTP headers (vì encrypted), nên routing chỉ dựa trên SNI hoặc IP.
Dùng khi backend cần control hoàn toàn TLS (ví dụ mTLS), hoặc khi load balancer không cần inspect traffic. Kubernetes Ingress với ssl-passthrough annotation là ví dụ.
Lựa chọn phổ biến mà mình thấy: terminate ở load balancer cho traffic từ internet, mTLS giữa service nội bộ qua service mesh. Đơn giản, đủ an toàn, không quá phức tạp.
HSTS — ép HTTPS và chống downgrade
HSTS (HTTP Strict Transport Security) là HTTP header nói với browser: “từ giờ chỉ kết nối domain này qua HTTPS, dù user gõ http:// hoặc click link http://”. Browser tự chuyển sang HTTPS mà không gửi request HTTP nào — loại bỏ hoàn toàn khả năng bị SSL stripping attack.
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
max-age=31536000 — browser nhớ policy này trong 1 năm. includeSubDomains — áp dụng cho tất cả subdomain. preload — đăng ký domain vào danh sách HSTS preload của browser (Chrome, Firefox, Safari hardcode danh sách domain luôn dùng HTTPS).
Cẩn thận khi bật HSTS: nếu bạn bật rồi mà sau đó cần quay lại HTTP (cert issue, migration), browser từ chối kết nối HTTP và user không có cách nào override (trừ clear browser data). Bắt đầu với max-age nhỏ (vài phút) để test, tăng dần lên 1 năm khi chắc chắn HTTPS hoạt động ổn định.
HSTS preload là cam kết mạnh nhất — domain được hardcode trong browser source code. Gỡ ra khỏi preload list mất hàng tháng. Chỉ submit preload khi bạn chắc chắn 100% domain sẽ luôn dùng HTTPS — không phải quyết định nhẹ nhàng.
TLS version: tại sao disable TLS 1.0/1.1
TLS 1.0 (1999) và TLS 1.1 (2006) có lỗ hổng bảo mật đã biết — BEAST, POODLE, và nhiều attack khác exploit weakness trong cipher suite và protocol design. Tất cả browser hiện đại đã drop support TLS 1.0/1.1 từ 2020. PCI DSS yêu cầu disable TLS 1.0 từ 2018.
TLS 1.2 (2008) vẫn an toàn nếu cấu hình cipher suite đúng. TLS 1.3 (2018) an toàn hơn, nhanh hơn (1-RTT handshake), và loại bỏ nhiều cipher suite yếu. Cấu hình recommend: chấp nhận TLS 1.2 và 1.3, từ chối mọi thứ cũ hơn.
Với Nginx:
ssl_protocols TLSv1.2 TLSv1.3;
Nếu bạn vẫn cần hỗ trợ TLS 1.0/1.1 cho client cũ (máy quét POS, thiết bị IoT), đó là quyết định business — nhưng phải hiểu rủi ro và document rõ ràng. Mình từng thấy team giữ TLS 1.0 cho “2% client cũ” mà không ai kiểm tra 2% đó là bao nhiêu user thật — hoá ra là bot scanner, không phải user.
Cipher suite và cấu hình
Cipher suite là tổ hợp thuật toán dùng trong TLS session: key exchange, authentication, encryption, MAC. Ví dụ TLS_AES_256_GCM_SHA384 nghĩa là AES-256 GCM cho encryption, SHA-384 cho MAC. TLS 1.3 đơn giản hoá bằng cách chỉ cho phép 5 cipher suite, tất cả đều mạnh — bạn không cần chọn.
TLS 1.2 thì phức tạp hơn — có hàng chục cipher suite, một số yếu. Cấu hình sai dẫn đến accept cipher yếu mà attacker exploit được.
Đừng tự nghĩ ra cipher suite list. Dùng Mozilla SSL Configuration Generator (ssl-config.mozilla.org) — chọn server software (Nginx, Apache, HAProxy), chọn profile (Modern, Intermediate, Old), nó sinh config sẵn. Profile “Intermediate” phù hợp cho hầu hết production — hỗ trợ TLS 1.2/1.3 với cipher suite mạnh, tương thích với hầu hết client hiện đại.
# Mozilla Intermediate profile cho Nginx
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
Kiểm tra cấu hình sau khi deploy bằng Qualys SSL Labs (ssllabs.com/ssltest) — tool scan miễn phí, cho điểm A+ nếu cấu hình tốt, liệt kê chi tiết cipher suite và vấn đề. Mình chạy SSL Labs sau mỗi lần thay đổi TLS config — 5 phút scan cho peace of mind.
Certificate pinning trên mobile
Certificate pinning là kỹ thuật mà mobile app hardcode certificate hoặc public key của server vào trong app — thay vì trust bất kỳ CA nào trong system trust store, app chỉ chấp nhận certificate cụ thể đã biết trước.
Mục đích: ngăn attacker dùng certificate giả từ CA bị compromise hoặc CA do corporate proxy cài đặt. Nghe hay, nhưng trong thực tế pinning tạo ra vấn đề vận hành nghiêm trọng.
Khi bạn cần đổi certificate (renew, đổi CA, incident), app đã pin cert cũ sẽ reject cert mới → app chết cho đến khi user update app mới. Nếu user không update (Android thường chậm update), app chết nhiều tuần. Mình từng thấy team phải release emergency app update vì cert được pin đã hết hạn — nhưng 30% user vẫn chạy version cũ sau 2 tuần.
Nếu vẫn muốn pinning, pin public key (SPKI hash) thay vì certificate — public key không đổi khi renew cert (nếu bạn reuse key pair). Và luôn pin backup key — key pair dự phòng chưa dùng, để khi cần rotate thì đã có key trong app.
Lời khuyên thực tế: hầu hết app không cần certificate pinning. Nếu không phải app ngân hàng hoặc xử lý dữ liệu cực kỳ nhạy cảm, chi phí vận hành pinning thường lớn hơn lợi ích bảo mật. Google cũng đã gỡ bỏ yêu cầu pinning khỏi Android guidelines. Certificate Transparency (CT log) là giải pháp thay thế ít tốn kém hơn — monitor CT log để phát hiện certificate lạ cấp cho domain của bạn.
Monitor certificate và alert
Certificate hết hạn là incident 100% có thể phòng ngừa — nếu bạn monitor expiry date. Mà lạ thay, đây vẫn là nguyên nhân phổ biến gây outage ở nhiều tổ chức, kể cả lớn.
Cách đơn giản nhất — script chạy hàng ngày kiểm tra expiry:
#!/bin/bash
DOMAIN="api.example.com"
EXPIRY=$(echo | openssl s_client -connect "$DOMAIN:443" -servername "$DOMAIN" 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( (EXPIRY_EPOCH - NOW_EPOCH) / 86400 ))
if [ "$DAYS_LEFT" -lt 14 ]; then
echo "ALERT: $DOMAIN cert expires in $DAYS_LEFT days"
# Gửi Slack/PagerDuty alert
fi
Với nhiều domain, dùng tool chuyên dụng hơn: Prometheus blackbox_exporter có probe TLS lấy metric cert expiry — set alert ssl_cert_not_after - time() < 14 * 86400. Grafana dashboard hiển thị expiry date tất cả domain, alert trước 30 ngày (warning) và 14 ngày (critical).
cert-manager trong Kubernetes có metric sẵn cho Prometheus — certmanager_certificate_expiration_timestamp_seconds. Đặt alert tương tự.
Nguyên tắc: alert ít nhất 14 ngày trước khi cert hết hạn. Let’s Encrypt cert hạn 90 ngày, renew ở 60 ngày — nếu renewal fail, bạn có 30 ngày buffer. Alert ở 14 ngày cho bạn 2 tuần investigate và fix renewal pipeline trước khi cert thực sự hết.
Anti-pattern hay gặp
Ignore cert errors trong code. curl -k, requests.get(url, verify=False), NODE_TLS_REJECT_UNAUTHORIZED=0, InsecureSkipVerify: true trong Go. Mỗi lần bạn skip cert verification là một lần bạn tắt hoàn toàn authentication — client không biết đang nói chuyện với ai, man-in-the-middle đọc được mọi thứ. Dùng cho development local thì OK, nhưng tuyệt đối không để lọt vào production code. Mình từng review PR có verify=False với comment “fix later” — “later” không bao giờ đến. CI nên grep và reject pattern này.
Self-signed cert trên production. Let’s Encrypt miễn phí, auto-renew, hỗ trợ wildcard. Không có lý do nào để dùng self-signed cert cho service expose ra internet. Self-signed nghĩa là mọi client phải tự trust cert đó — thêm bước cấu hình, dễ quên, và user thường bị train để click “Accept anyway” trên browser — thói quen nguy hiểm.
Không monitor cert expiry. “Auto-renew mà, lo gì.” Auto-renew fail silent vì DNS API key đổi, vì permission file thay đổi, vì certbot không chạy được. Đến khi cert hết hạn mới biết. Monitor cert expiry giống như monitor disk space — không cần thiết cho đến khi bạn cần nó, và lúc cần mà không có thì rất đau.
Mixed content. Trang HTTPS load resource qua HTTP (ảnh, script, font). Browser block hoặc cảnh báo. Kiểm tra bằng browser DevTools → Console, tìm mixed content warning. Fix bằng cách dùng relative URL hoặc https:// cho tất cả resource.
Không set HSTS. User lần đầu truy cập qua HTTP, bị redirect sang HTTPS. Trong khoảng redirect đó, attacker có thể thực hiện SSL stripping — giữ connection HTTP với user, connection HTTPS với server. HSTS ngăn điều này sau lần truy cập đầu tiên. HSTS preload ngăn ngay cả lần đầu.
Certificate cho internal service không rotate. Internal cert thường có hạn dài (1-5 năm) và bị quên. Khi hết hạn, giao tiếp giữa service chết — và debug lâu hơn vì không ai nhớ cert nội bộ cũng có hạn. Service mesh auto-rotate giải quyết vấn đề này tự động.
Tóm tắt
HTTPS qua TLS cung cấp ba đảm bảo: mã hoá (confidentiality), toàn vẹn (integrity), xác thực (authentication) — thiếu bất kỳ cái nào thì bảo mật giảm đáng kể. TLS 1.3 handshake chỉ cần 1-RTT, nhanh hơn TLS 1.2, và loại bỏ cipher suite yếu — luôn ưu tiên TLS 1.3, disable TLS 1.0/1.1.
Certificate chain phải đầy đủ: leaf + intermediate. Thiếu intermediate là lỗi phổ biến nhất — browser tự sửa nhưng code backend thì không. Dùng fullchain.pem thay vì cert.pem. Debug TLS bằng openssl s_client — nó cho biết chính xác lỗi ở bước nào trong handshake.
Let’s Encrypt + auto-renewal (certbot, acme.sh, cert-manager) là bắt buộc — cert miễn phí, không có lý do dùng self-signed trên production. Monitor cert expiry, alert trước 14 ngày — cert hết hạn là incident 100% phòng ngừa được mà vẫn xảy ra ở rất nhiều tổ chức.
mTLS dùng cho giao tiếp giữa service khi cần zero-trust — service mesh (Istio, Linkerd) tự động hoá cert lifecycle. TLS termination ở load balancer là pattern phổ biến nhất, kết hợp mTLS nội bộ nếu compliance yêu cầu. HSTS ép browser dùng HTTPS, ngăn SSL stripping — bật với max-age nhỏ trước, tăng dần. Và tuyệt đối không bao giờ skip cert verification trong production code — đó là tắt toàn bộ authentication, nguy hiểm ngang mở cửa không khoá.