TL;DR
Bạn đang gặp lỗi SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE vì một trong các chứng chỉ CA trung gian trong chuỗi của máy chủ đã hết hạn. Có thể CA của bạn đã phát hành chứng chỉ trung gian thay thế, hoặc bạn đang phục vụ sai chứng chỉ trung gian. Cách xử lý nhanh:
- Tải bundle chứng chỉ trung gian mới từ CA của bạn.
- Cập nhật cấu hình máy chủ để sử dụng bundle mới.
- Reload/restart web server.
Nếu bạn đang dùng Let's Encrypt và gặp lỗi này sau tháng 9 năm 2021, gần như chắc chắn bạn cần loại bỏ chuỗi cross-signed DST Root CA X3 đã hết hạn — hãy đọc tiếp.
Điều Gì Đang Xảy Ra
Khi trình duyệt xác thực chứng chỉ TLS của bạn, nó duyệt lên chuỗi: chứng chỉ lá → một hoặc nhiều CA trung gian → root đáng tin cậy. Nếu bất kỳ chứng chỉ trung gian nào trong chuỗi đó có ngày notAfter trong quá khứ, Firefox sẽ ném lỗi SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE thay vì chấp nhận kết nối.
Các nguyên nhân phổ biến:
- CA của bạn đã luân chuyển chứng chỉ trung gian nhưng bạn chưa cập nhật bundle trên máy chủ.
- Chuỗi cross-signed DST Root CA X3 cũ của Let's Encrypt đã hết hạn (ngày 30 tháng 9 năm 2021) — một số cấu hình vẫn đang phục vụ chuỗi cũ này.
- Bạn đã ghim một chứng chỉ trung gian cụ thể trong quá trình triển khai và nó đã hết hạn lúc nào không hay.
- Một chứng chỉ trung gian PKI nội bộ đã hết hạn mà không ai để ý cho đến khi Firefox bắt đầu chặn.
Chẩn Đoán Trước — Đừng Đoán Mò
Chạy lệnh này trước khi làm bất cứ điều gì:
# Kiểm tra toàn bộ chuỗi mà máy chủ đang thực sự gửi
openssl s_client -connect yourdomain.com:443 -showcerts 2>/dev/null | openssl x509 -noout -text | grep -A2 'Validity'
# Tốt hơn: xem từng chứng chỉ trong chuỗi cùng với ngày hết hạn
openssl s_client -connect yourdomain.com:443 -showcerts 2>/dev/null \
| awk '/BEGIN CERTIFICATE/,/END CERTIFICATE/' \
| csplit -z -f cert- - '/-----BEGIN CERTIFICATE-----/' '{*}' 2>/dev/null
for f in cert-*; do
echo "=== $f ==="
openssl x509 -noout -subject -issuer -dates -in "$f"
done
Xem dòng notAfter của từng chứng chỉ. Cái nào có ngày trong quá khứ chính là thủ phạm.
Kiểm tra nhanh bằng curl nếu bạn chỉ cần câu trả lời nhanh:
curl -vI https://yourdomain.com 2>&1 | grep -E 'expire|SSL|certificate'
Cách Sửa 1 — Cập Nhật Bundle Trung Gian (Phổ Biến Nhất)
Lấy chứng chỉ trung gian hiện tại từ website của CA. Với Let's Encrypt:
# Tải chuỗi ISRG Root X1 đang hoạt động (không phải chuỗi cross-signed DST đã hết hạn)
wget https://letsencrypt.org/certs/lets-encrypt-r3.pem -O /etc/ssl/intermediate.pem
# Hoặc chuỗi đầy đủ (intermediate + root)
wget https://letsencrypt.org/certs/lets-encrypt-r3-cross-signed.pem
Với các CA thương mại, lấy bundle từ cổng hỗ trợ của họ. Thường được gán nhãn là "intermediate certificate" hoặc "CA bundle".
Apache
# Trong block VirtualHost của bạn
SSLCertificateFile /etc/ssl/certs/yourdomain.crt
SSLCertificateKeyFile /etc/ssl/private/yourdomain.key
SSLCertificateChainFile /etc/ssl/intermediate.pem
# Kiểm tra cấu hình trước khi reload
apachectl configtest
systemctl reload apache2
Nginx
# Nginx yêu cầu chứng chỉ lá + intermediate trong một file duy nhất
cat yourdomain.crt intermediate.pem > /etc/ssl/certs/yourdomain_bundle.crt
# Trong block server
ssl_certificate /etc/ssl/certs/yourdomain_bundle.crt;
ssl_certificate_key /etc/ssl/private/yourdomain.key;
nginx -t
systemctl reload nginx
HAProxy
# HAProxy cần key + cert + chain tất cả trong một file PEM
cat yourdomain.key yourdomain.crt intermediate.pem > /etc/haproxy/yourdomain.pem
# Trong section frontend/backend
bind *:443 ssl crt /etc/haproxy/yourdomain.pem
systemctl reload haproxy
Cách Sửa 2 — Dành Riêng Cho Let's Encrypt (DST Root CA X3 Hết Hạn)
Nếu certbot vẫn đang tạo chứng chỉ sử dụng chuỗi cross-signed DST Root CA X3 đã hết hạn, hãy buộc nó dùng chuỗi ưu tiên ISRG Root X1:
# Buộc gia hạn với chuỗi chưa hết hạn
certbot renew --preferred-chain "ISRG Root X1" --force-renewal
# Hoặc thêm vào /etc/letsencrypt/cli.ini để áp dụng vĩnh viễn
preferred-chain = ISRG Root X1
Sau đó rebuild bundle và reload máy chủ.
Cách Sửa 3 — PKI Nội Bộ (CA Tự Quản Lý)
Nếu bạn tự vận hành CA, bạn cần phát hành chứng chỉ trung gian mới, phân phối nó và cập nhật mọi máy chủ trong hệ thống. Các bước thực hiện:
# Tạo chứng chỉ trung gian mới (lần này có hiệu lực 5 năm)
openssl genrsa -out intermediate.key 4096
openssl req -new -key intermediate.key -out intermediate.csr
# Ký bằng root CA
openssl x509 -req -in intermediate.csr \
-CA rootCA.crt -CAkey rootCA.key -CAcreateserial \
-out intermediate.crt -days 1825 -sha256 \
-extfile intermediate_ext.cnf
# Phân phối intermediate.crt đến tất cả máy chủ
ansible all -m copy -a "src=intermediate.crt dest=/etc/ssl/intermediate.crt" \
&& ansible all -m service -a "name=nginx state=reloaded"
Xác Minh Sau Khi Sửa
Sau khi reload, xác nhận chuỗi đã sạch:
# Tất cả ngày phải ở trong tương lai
openssl s_client -connect yourdomain.com:443 -showcerts 2>/dev/null \
| openssl x509 -noout -dates
# Dùng SSL Labs để kiểm tra toàn bộ chuỗi (đạt A hoặc A+)
# https://www.ssllabs.com/ssltest/analyze.html?d=yourdomain.com
# Hoặc dùng testssl.sh ở local
./testssl.sh --chain yourdomain.com
Trong Firefox, mở DevTools → tab Security sau khi hard reload (Ctrl+Shift+R). Bạn sẽ thấy "Connection secure" mà không có cảnh báo chứng chỉ nào.
Ngăn Ngừa Lần Sau
- Đặt lịch nhắc nhở 60 ngày trước ngày
notAftercủa CA trung gian. - Thêm cron job cảnh báo khi bất kỳ chứng chỉ nào trong chuỗi hết hạn trong vòng 90 ngày:
# /etc/cron.weekly/check-chain-expiry
#!/bin/bash
HOST="yourdomain.com"
EXPIRY=$(openssl s_client -connect $HOST:443 -showcerts 2>/dev/null \
| openssl x509 -noout -enddate | cut -d= -f2)
DAYS=$(( ($(date -d "$EXPIRY" +%s) - $(date +%s)) / 86400 ))
[ $DAYS -lt 90 ] && echo "WARNING: chain cert on $HOST expires in $DAYS days" | mail -s "Cert Expiry Alert" ops@yourcompany.com
- Sử dụng bộ hẹn giờ tự gia hạn của Certbot (
systemctl status certbot.timer) và xác minh nó đang thực sự chạy. - Giám sát bằng dịch vụ bên ngoài như UptimeRobot hoặc Checkly — chúng sẽ phát hiện chứng chỉ sắp hết hạn trước khi người dùng gặp vấn đề.

