Certificate: tin cậy mạng từ ba dòng lệnh
Mọi thứ “có vẻ ổn” cho tới khi: trình duyệt báo “Your connection is not private”, CI không clone được repo qua HTTPS, hoặc app internal gọi API báo certificate verify failed lúc 2 giờ sáng.
Ba câu hỏi debug TLS trên Linux:
- Chain có đầy đủ không? (intermediate thiếu là nguyên nhân số một của lỗi mobile API)
- Cert hết hạn chưa? (và có tự động renew không)
- Hostname trên cert có khớp không? (wildcard, SAN, IP trong SAN)
Bài này đi từ openssl s_client debug thủ công, đến certbot tự động hóa, tới renewal strategy trong systemd timer và cron — tất cả trên Linux.
openssl s_client: debug không cần browser
Kiểm tra cert của một domain
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -dates -subject -issuer
Output:
notBefore=May 1 00:00:00 2026 GMT
notAfter=Jul 30 23:59:59 2026 GMT
subject=CN=example.com
issuer=C=US, O=Let's Encrypt, CN=R11
Cờ -servername bắt buộc cho SNI (Server Name Indication) — thiếu nó, nhiều server trả về default cert sai domain.
Kiểm tra toàn bộ chain
echo | openssl s_client -connect example.com:443 -showcerts 2>/dev/null
Cần thấy ít nhất 2 certificate: leaf (của bạn) + intermediate (của CA). Nếu chỉ thấy 1, server thiếu intermediate — mobile app và một số HTTP client sẽ fail.
Lấy thông tin cert từ file .pem
# Xem toàn bộ thông tin
openssl x509 -in /etc/letsencrypt/live/example.com/fullchain.pem -text -noout | head
# Kiểm tra ngày hết hạn
openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem -noout -enddate
# Kiểm tra SAN (Subject Alternative Names)
openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem -noout -ext subjectAltName
Private key: RSA, ECDSA, và quyền
# Kiểm tra key có khớp cert không
openssl x509 -noout -modulus -in cert.pem | openssl md5
openssl rsa -noout -modulus -in privkey.pem | openssl md5
# Hai hash phải giống nhau
# Kiểm tra loại key
openssl rsa -in privkey.pem -text -noout | head # RSA
openssl ec -in privkey.pem -text -noout | head # ECDSA
Permission tối thiểu: private key phải 600, chỉ owner đọc được. Fullchain có thể 644.
chmod 600 /etc/letsencrypt/live/example.com/privkey.pem
chmod 644 /etc/letsencrypt/live/example.com/fullchain.pem
Let’s Encrypt và certbot: 90% nhu cầu TLS
Cài đặt certbot
# Ubuntu/Debian
sudo apt install certbot python3-certbot-nginx
# RHEL/Fedora
sudo dnf install certbot python3-certbot-nginx
Ba challenge types
| Challenge | Khi dùng | Yêu cầu |
|---|---|---|
| HTTP-01 | Server có public port 80 | certbot tạm tạo file trong .well-known/acme-challenge/ |
| DNS-01 | Wildcard cert, server không public, hoặc sau CDN | Cần API key DNS provider (Cloudflare, Route53…) |
| TLS-ALPN-01 | Không cần port 80, reverse proxy hỗ trợ | Ít phổ biến hơn |
Lấy cert đầu tiên
# HTTP challenge, tự động sửa Nginx config
sudo certbot --nginx -d example.com -d www.example.com
# Chỉ lấy cert, không chạm config (dùng khi reverse proxy custom)
sudo certbot certonly --nginx -d example.com -d www.example.com
# DNS challenge cho wildcard
sudo certbot certonly --manual --preferred-challenges dns -d "*.example.com"
Đường dẫn mặc định
ls /etc/letsencrypt/live/example.com/
# cert.pem → leaf certificate
# chain.pem → intermediate certificate
# fullchain.pem → cert.pem + chain.pem (dùng trong hầu hết cấu hình)
# privkey.pem → private key
Gia hạn tự động: đừng để cert hết hạn
Let’s Encrypt cert có hiệu lực 90 ngày. Gia hạn phải là tự động.
Kiểm tra renewal timer
# systemd timer (cách certbot cài mặc định)
systemctl list-timers certbot.timer
systemctl status certbot.timer
# Xem log renewal gần nhất
sudo journalctl -u certbot.service --no-pager | tail
Test dry-run
sudo certbot renew --dry-run
Renewal hook: reload service sau khi cert mới
File /etc/letsencrypt/renewal/example.com.conf có thể thêm:
[renewalparams]
renew_hook = systemctl reload nginx
Hoặc dùng --deploy-hook khi chạy certbot:
sudo certbot renew --deploy-hook "systemctl reload nginx"
Nếu certbot timer không hoạt động
# Tạo systemd service + timer thủ công
# /etc/systemd/system/certbot-renewal.service
[Unit]
Description=Certbot renewal
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/bin/certbot renew --quiet --deploy-hook "/bin/systemctl reload nginx"
# /etc/systemd/system/certbot-renewal.timer
[Unit]
Description=Certbot renewal timer
[Timer]
OnCalendar=daily
RandomizedDelaySec=3600
Persistent=true
[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now certbot-renewal.timer
Debug lỗi TLS thường gặp
1. “certificate has expired”
openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem -noout -enddate
Nếu hết hạn: cert renewal fail. Kiểm tra certbot.timer, DNS record (cho DNS challenge), hoặc port 80 bị firewall chặn (cho HTTP challenge).
2. “unable to verify the first certificate” hoặc “self signed certificate in chain”
Server thiếu intermediate certificate. Fix: dùng fullchain.pem thay vì cert.pem trong cấu hình web server.
# Sai — thiếu intermediate
ssl_certificate /etc/letsencrypt/live/example.com/cert.pem;
# Đúng — leaf + intermediate
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
3. “certificate subject name does not match”
openssl s_client -connect host:443 -servername host 2>/dev/null | openssl x509 -noout -subject -ext subjectAltName
Kiểm tra SAN list. Nếu domain không có trong SAN, cert sai host.
4. OCSP stapling không hoạt động
echo | openssl s_client -connect example.com:443 -status 2>/dev/null | grep -A 20 "OCSP"
Nếu thấy OCSP Response Status: successful, OCSP stapling OK. Nếu không, kiểm tra cấu hình web server và connectivity tới OCSP responder.
curl debug TLS: một lệnh thay thế browser
# Verbose TLS handshake
curl -vI https://example.com 2>&1 | grep -E "SSL|expire|subject|issuer"
# Chỉ định CA bundle custom
curl --cacert /path/to/ca-bundle.pem https://internal-api.local
# Bỏ qua verify (CHỈ test, không bao giờ production)
curl -k https://example.com
Certificate chain và trust store của OS
Linux dùng /etc/ssl/certs/ (Debian/Ubuntu) hoặc /etc/pki/tls/certs/ (RHEL). CA bundle system-wide:
# Debian/Ubuntu
ls /etc/ssl/certs/
sudo update-ca-certificates
# RHEL/Fedora
ls /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust
Khi thêm internal CA (corporate, self-hosted service mesh):
# Debian/Ubuntu
sudo cp internal-ca.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates
# RHEL/Fedora
sudo cp internal-ca.crt /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust extract
Playbook: cert production sắp hết hạn
# 1. Audit toàn bộ cert đang dùng
for domain in example.com api.example.com admin.example.com; do
echo "=== $domain ==="
echo | openssl s_client -connect "$domain:443" -servername "$domain" 2>/dev/null \
| openssl x509 -noout -enddate -subject
done
# 2. Kiểm tra certbot renewal
sudo certbot renew --dry-run
sudo journalctl -u certbot.service --since "1 week ago" --no-pager | grep -i "error\|fail\|success"
# 3. Nếu cert trên load balancer / CDN (không do certbot quản lý)
# Dùng openssl từ xa hoặc script monitor
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
| openssl x509 -noout -enddate
# 4. Setup monitoring: script cron/timer gửi alert nếu < 14 ngày
DAYS=$(echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
| openssl x509 -noout -checkend 1209600)
# checkend trả exit code 0 nếu cert còn valid > 14 ngày
Liên hệ các bài trong loạt
- Trước: Phần 13: SELinux và AppArmor
- Liên quan: Phần 9: Firewall (port 80/443 phải mở cho HTTP challenge)
- Liên quan: Phần 11: Thời gian và clock skew (cert expired oan nếu đồng hồ sai)
- Liên quan: Phần 5: systemd timer (renewal timer)
Câu hỏi hay gặp
1. “Let’s Encrypt có dùng được cho internal service không?”
Có, nhưng cần DNS-01 challenge (vì internal service không public port 80). Hoặc dùng wildcard cert *.internal.example.com cấp một lần rồi copy tới các internal node.
2. “Nên dùng RSA hay ECDSA cho cert?”
ECDSA (P-256) nhẹ hơn, handshake nhanh hơn, được khuyến nghị cho web hiện đại. RSA 2048 vẫn phổ biến cho tương thích legacy. certbot mặc định dùng RSA; thêm --key-type ecdsa để chọn ECDSA.
3. “Certbot tự renew nhưng web server vẫn dùng cert cũ?”
Quên renew_hook hoặc --deploy-hook reload web server. Kiểm tra: openssl s_client để xem cert hiện tại server đang trả, không chỉ xem file trên disk.
4. “Làm sao biết cert sắp hết hạn trước khi user báo?”
Script checkend + cron/timer gửi alert. Hoặc dùng monitoring: UptimeRobot, Datadog Synthetics, hoặc Blackbox Exporter check TLS expiry.
Đọc thêm
- Let’s Encrypt documentation
- OpenSSL s_client manual
- Mozilla SSL Configuration Generator — sinh cấu hình TLS an toàn cho Nginx/Apache/HAProxy
- certbot.eff.org — hướng dẫn cài certbot theo distro + web server
← Phần 13: SELinux và AppArmor
→ Mục lục loạt Linux thực dụng