---
id: case-cert
title: "Trình duyệt báo certificate"
shortTitle: "Certificate hết hạn"
track: cases
trackTitle: "Tình huống vận hành"
order: 2
level: core
status: published
estimatedMinutes: 30
prerequisites:
  - networking-tls
next: case-disk
reviewedAgainst: 2026-10-12
sources:
  - title: "Let's Encrypt certificate lifetime"
    url: https://letsencrypt.org/docs/faq/#what-is-the-lifetime-of-a-let-s-encrypt-certificate
  - title: "RFC 5280"
    url: https://www.rfc-editor.org/rfc/rfc5280
---

# Trình duyệt báo certificate

User thấy cảnh báo trước khi app kịp chạy dòng code nào, nên request có thể không hề xuất hiện trong log của app. Việc của bạn là xác định certificate nào đang được gửi cho client, tên nào bị thiếu, và process nào phải reload sau khi thay file.

## Để làm gì

Bạn cần phân biệt ba lỗi: hết hạn, sai tên, và thiếu chain. Sau đó thay đúng file trên đúng máy đang terminate TLS, rồi reload process vẫn đang giữ certificate cũ trong bộ nhớ.

## Gồm những gì

- Dùng `curl -v` để đọc subject và ngày hết hạn.
- SAN, không chỉ CN.
- Nơi terminate TLS: load balancer, Nginx, hoặc chính app.

Bài không dựng CA nội bộ và không gia hạn certificate trên domain thật trong lab.

## Dùng khi nào

Client báo "certificate expired" hoặc "name mismatch", hoặc một client cũ bị fail trong khi trình duyệt của bạn vẫn vào được. Let's Encrypt cấp certificate có hạn 90 ngày, nên certificate hết hạn đúng lịch là việc vận hành, không phải bug của ứng dụng.

Nếu certificate lỗi nằm trên load balancer phía trước, đừng sửa config Nginx của app. App phía sau có thể chỉ đang nghe HTTP.

## Kiến thức cốt lõi

TLS handshake phải xong thì HTTP mới bắt đầu. Vì vậy access log của app trống không có nghĩa là app chết; có thể client đã dừng ngay ở bước handshake.

Ba lỗi hay gặp:

| Client báo | Nguyên nhân thật |
| --- | --- |
| expired | Ngày `notAfter` đã qua. Gia hạn, cài file mới, rồi reload |
| name mismatch | SAN không có hostname mà user đang gọi. Certificate cho `www` không dùng được cho apex nếu SAN không có apex |
| unknown issuer hoặc chain | Thiếu certificate trung gian. Máy này tin, máy kia không, vì mỗi máy cache certificate trung gian khác nhau |

Thứ tự điều tra:

1. Đọc certificate mà client thật sự nhận được, trên đúng hostname user đang gọi. Bước này đứng đầu vì nó cho biết lỗi thuộc loại nào trong ba loại trên, và không làm thay đổi gì trên hệ thống.
2. Xác định ai terminate TLS. Nếu load balancer giữ certificate, bạn phải sửa trên load balancer; sửa file trên Nginx phía sau không thay đổi thứ client nhìn thấy. Phải biết điều này trước khi đụng vào file nào.
3. Thay file, kiểm tra config, rồi reload. Process chỉ đọc certificate lúc start hoặc lúc reload. Nếu bạn ghi file mới mà không reload, client vẫn nhận bản cũ cho tới khi process mở lại file. Chạy `nginx -t` rồi reload. Đừng `restart` nếu reload là đủ, trừ khi bạn biết unit không hỗ trợ reload.
4. Đọc lại certificate từ phía client như ở bước 1. Bạn chỉ coi là xong khi ngày hết hạn mới và SAN đúng hiện ra từ chính hostname đó.

Gia hạn tự động thường fail vì port 80 bị chặn lúc xác minh HTTP-01, hoặc bản ghi DNS dùng cho DNS-01 đã bị xóa. Hãy sửa đường xác minh, đừng né lỗi bằng cách tắt kiểm tra certificate ở phía client.

## Lệnh và code mẫu

```bash
curl -vI --max-time 10 https://example.com/ 2>&1 | awk 'BEGIN{IGNORECASE=1} /subject:|expire date|issuer:|SSL certificate verify/'
```

Nếu ngày hết hạn vẫn còn ở tương lai mà user báo name mismatch, hãy nhìn SAN thay vì nhìn ngày:

```bash
echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -noout -ext subjectAltName -dates
```

Máy không có `openssl` thì dừng ở `curl -v`. Lệnh đó đủ để thấy subject và ngày hết hạn của `example.com`.

## Thực hành

1. Chạy `curl -vI` tới `https://example.com/`. Ghi lại subject và ngày hết hạn.
2. Trả lời: nếu user gọi `shop.example.com` mà SAN chỉ có `example.com`, client fail ở bước nào? Đáp án: ở handshake, trước khi có HTTP.
3. Viết một dòng runbook: file mới nằm ở đâu, lệnh kiểm tra config là gì, lệnh reload là gì. Không ghi mật khẩu.

## Kết quả mong đợi

Bạn khoanh được lỗi ở tầng TLS và không đi sửa code app. Bạn biết reload là bước bắt buộc sau khi thay file.

## Checklist

- [ ] Đọc ngày hết hạn và SAN trên đúng hostname user đang gọi.
- [ ] Sửa certificate ở nơi terminate TLS, không phải ở mọi máy phía sau.
- [ ] Chạy `nginx -t` hoặc lệnh kiểm tra tương đương, rồi reload.
- [ ] Đọc lại certificate từ phía client sau khi reload.
- [ ] Có alert trước ngày hết hạn, đừng đợi trình duyệt của khách báo.

## Lỗi thường gặp

**Cài certificate mới nhưng vẫn giữ key cũ không khớp.** Handshake fail với lỗi key không đi cùng certificate. Certificate và key phải là một cặp được cấp cùng lần.

**Chỉ một CDN edge còn giữ certificate cũ.** User ở vùng đó vẫn thấy cảnh báo, còn máy bạn thì không. Hãy kiểm tra certificate trên đúng hostname, và nếu có CDN thì purge hoặc đợi edge lấy bản mới. Đừng kết luận đã xong chỉ vì laptop của bạn vào được.

## Nguồn

1. [Let's Encrypt FAQ](https://letsencrypt.org/docs/faq/#what-is-the-lifetime-of-a-let-s-encrypt-certificate) — certificate 90 ngày.
2. [RFC 5280](https://www.rfc-editor.org/rfc/rfc5280) — tên và chuỗi certificate.

## Tiếp theo

[Disk đầy vì log](./03-disk.md)
