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:

  1. Chain có đầy đủ không? (intermediate thiếu là nguyên nhân số một của lỗi mobile API)
  2. Cert hết hạn chưa? (và có tự động renew không)
  3. 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

ChallengeKhi dùngYêu cầu
HTTP-01Server có public port 80certbot tạm tạo file trong .well-known/acme-challenge/
DNS-01Wildcard cert, server không public, hoặc sau CDNCần API key DNS provider (Cloudflare, Route53…)
TLS-ALPN-01Khô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


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


Phần 13: SELinux và AppArmor
Mục lục loạt Linux thực dụng