Skip to main content

2.12 — Mini case study

Summary

Một tình huống rất hay gặp khi mới làm backend: người dùng đăng nhập thành công nhưng mọi trang sau đó đều báo 401 Unauthorized. Bài này lần theo request qua đúng năm chặng — trình duyệt, DNS, HTTPS, backend, database — và cho thấy cách loại trừ dần từng chặng thay vì đoán. Nguyên nhân cuối cùng hoá ra không nằm ở code xác thực chút nào, mà ở một thứ ít ai nghĩ tới khi mới học: đồng hồ của server lệch 12 phút, khiến token vừa cấp đã bị coi là hết hạn. Điều đáng học nhất là phương pháp: mỗi chặng đều có một cách kiểm tra riêng, và kiểm tra theo thứ tự tiết kiệm rất nhiều thời gian.

Mục tiêu bài học​

Sau bài này bạn có thể:

  • Lần theo một request qua năm chặng của hệ thống web.
  • Dùng công cụ phù hợp để kiểm tra từng chặng.
  • Loại trừ dần thay vì đoán nguyên nhân.
  • Nhận ra những nguyên nhân nằm ngoài code.

Nội dung bài học​

2.12.1 — Triệu chứng​

Người dùng: "Tôi đăng nhập được, nhưng bấm vào bất kỳ menu nào
đều bị đẩy về trang đăng nhập."

Kiểm tra ban đầu:
- POST /api/auth/login -> 200 OK, trả về token
- GET /api/leads -> 401 Unauthorized
- Xảy ra với MỌI người dùng
- Mới bắt đầu sáng nay, hôm qua vẫn bình thường

Hai thông tin trong phần cuối đã thu hẹp phạm vi rất nhiều: mọi người dùng nghĩa là không phải vấn đề tài khoản cá nhân; mới bắt đầu hôm nay nghĩa là có gì đó vừa thay đổi.

2.12.2 — Năm chặng của một request​

Nguyên tắc: kiểm tra từ ngoài vào trong, vì mỗi chặng loại trừ được là bớt một nửa phạm vi tìm kiếm.

2.12.3 — Chặng 1 và 2: trình duyệt, DNS, HTTPS​

Mở DevTools, tab Network, xem request GET /api/leads:

Request URL:     https://crm.company.com/api/leads
Status Code: 401 Unauthorized
Request Headers:
Authorization: Bearer eyJhbGciOiJIUzI1NiIs... <- token CÓ gửi đi

Hai kết luận ngay:

  • DNS và HTTPS không phải vấn đề — request đã tới server và nhận được phản hồi. Nếu DNS sai, bạn sẽ thấy lỗi ERR_NAME_NOT_RESOLVED; nếu chứng chỉ sai, ERR_CERT_*.
  • Frontend có gửi token — nếu thiếu header Authorization, vấn đề nằm ở frontend, không phải backend.

Đây là bước kiểm tra nhanh nhất và loại trừ được hai chặng. Luôn bắt đầu từ đây (bài 2.5).

2.12.4 — Chặng 3: request có tới backend không?​

# Gửi request trực tiếp, bỏ qua trình duyệt
curl -i https://crm.company.com/api/leads \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer error="invalid_token",
error_description="The token expired at '09/24/2026 02:03:11'"

Đây là manh mối quyết định. Header WWW-Authenticate nói rõ lý do: token đã hết hạn.

Nhưng token vừa được cấp cách đây 30 giây. Nghĩa là thời gian hết hạn được tính sai — hoặc lúc cấp, hoặc lúc kiểm tra.

curl hữu ích ở đây vì nó loại bỏ mọi thứ của trình duyệt: cache, cookie, extension, CORS. Nếu curl cho kết quả khác trình duyệt thì vấn đề nằm ở phía trình duyệt.

2.12.5 — Chặng 4: backend xử lý thế nào​

# Giai ma phan payload cua JWT (phan giua, ma hoa base64)
echo "eyJzdWIiOiIxMjMiLCJleHAiOjE3NjQ5NDY1OTF9" | base64 -d
{ "sub": "123", "exp": 1764946591, "iat": 1764945691 }
# Đổi timestamp sang thời gian đọc được
date -u -d @1764946591 # exp: 02:03:11 UTC
date -u -d @1764945691 # iat: 01:48:11 UTC (cấp lúc này)
date -u # BÂY GIỜ: 02:15:33 UTC <-- !!

Token được cấp lúc 01:48:11 với hạn 15 phút, tức hết hạn lúc 02:03:11. Nhưng "bây giờ" theo đồng hồ máy là 02:15:33 — muộn hơn 12 phút so với lúc token còn hạn.

Kiểm tra đồng hồ server:

timedatectl status
               Local time: Wed 2026-09-24 02:15:33 UTC
Time zone: UTC (UTC, +0000)
System clock synchronized: no <-- KHÔNG đồng bộ
NTP service: inactive <-- NTP ĐÃ TẮT

Nguyên nhân gốc: dịch vụ NTP bị tắt trong một đợt cập nhật hệ thống đêm qua. Đồng hồ server trôi dần, và tới sáng nay nó lệch đủ để mọi token vừa cấp đã bị coi là hết hạn.

2.12.6 — Vì sao lệch đồng hồ gây ra chuyện này​

JWT chứa exp là một mốc thời gian tuyệt đối (số giây từ 1970). Bên cấp và bên kiểm tra đều so nó với đồng hồ của mình. Nếu hai đồng hồ lệch nhau, kết luận sẽ khác nhau.

Server cấp token:    dùng đồng hồ A -> exp = A + 15 phút
Server kiểm tra: dùng đồng hồ B

Nếu B nhanh hơn A 20 phút -> token vừa cấp đã "hết hạn"

Trong ca này, cả hai là cùng một server — nhưng đồng hồ trôi giữa lúc cấp và lúc kiểm tra, và độ trôi lớn hơn khoảng ClockSkew mặc định.

Sửa:

sudo timedatectl set-ntp true
sudo systemctl restart systemd-timesyncd
timedatectl status | grep synchronized # xác nhận: yes

Vấn đề biến mất ngay, không cần deploy lại gì.

Phòng ngừa: thêm kiểm tra đồng bộ thời gian vào health check, để lần sau hệ thống tự báo thay vì chờ người dùng phát hiện (bài 18.13).

2.12.7 — Bảng công cụ theo chặng​

ChặngCâu hỏiCông cụ
Trình duyệtRequest có được gửi đúng không?DevTools → Network
DNSTên miền phân giải ra IP nào?nslookup, dig
HTTPSChứng chỉ có hợp lệ không?Ổ khoá trên thanh địa chỉ, openssl s_client
HTTPServer trả về gì, kèm header nào?curl -i
BackendXử lý tới đâu thì lỗi?Log ứng dụng, breakpoint
DatabaseTruy vấn có chạy không?Log truy vấn, profiler

Thứ tự quan trọng. Bắt đầu từ chặng gần người dùng nhất và đi vào trong. Nhảy thẳng vào đọc code backend khi chưa biết request có tới đó không là cách tốn thời gian nhất.

2.12.8 — Rà lại code của bạn​

Danh sách rà soát khi chẩn đoán lỗi web

  • •Đã xem DevTools Network trước khi đọc code.
  • •Đã xác nhận request có tới server và có kèm header cần thiết.
  • •Đã dùng curl để loại trừ yếu tố trình duyệt.
  • •Đã đọc kỹ thông điệp lỗi trong header phản hồi, không chỉ mã trạng thái.
  • •Đã kiểm tra những thứ ngoài code: đồng hồ, biến môi trường, chứng chỉ.
  • •Đã hỏi cái gì vừa thay đổi gần đây nhất.
  • •Kiểm tra theo thứ tự từ ngoài vào trong, không nhảy cóc.
  • •Sau khi sửa, đã thêm cảnh báo để lần sau hệ thống tự báo.

Bài tập áp dụng​

Bài 1 — Lần theo một request​

Mở DevTools trên một trang bất kỳ và ghi lại đầy đủ năm chặng của một request API.

Tiêu chí hoàn thành: bạn có số liệu cho từng chặng, và chỉ ra được chặng nào chiếm nhiều thời gian nhất — thường không phải chặng bạn đoán.

Gợi ý và lời giải — Bài 1

Gợi ý. Tab Network → bấm vào một request → tab Timing. Nó chia sẵn thời gian theo từng giai đoạn.

Lời giải — đọc bảng Timing:

Request: GET https://api.crm.vn/api/leads?page=1

Queueing 0,42 ms
Stalled 1,18 ms
DNS Lookup 24,00 ms <- chặng 2
Initial connection 48,00 ms <- chặng 2 (TCP)
SSL 31,00 ms <- chặng 2 (TLS, nằm trong Initial connection)
Request sent 0,21 ms <- chặng 3
Waiting (TTFB) 186,00 ms <- chặng 4: MÁY CHỦ XỬ LÝ
Content Download 12,00 ms <- chặng 5
---------
Total 271,81 ms

Ánh xạ sang năm chặng ở mục 2.12.2:

ChặngTrong DevToolsThời gianTỉ lệ
1. Trình duyệt chuẩn bịQueueing + Stalled1,6 ms0,6%
2. DNS + TCP + TLSDNS + Initial connection72,0 ms26,5%
3. Gửi requestRequest sent0,2 ms0,1%
4. Máy chủ xử lýWaiting (TTFB)186,0 ms68,4%
5. Nhận phản hồiContent Download12,0 ms4,4%

Ba điều bảng này nói ra:

1. Chặng 4 chiếm gần 70% — và đó là chặng duy nhất bạn kiểm soát được.

Waiting (TTFB) = thời gian từ lúc request tới máy chủ cho tới byte đầu tiên về

Nó gồm: định tuyến, middleware, truy vấn database, xử lý nghiệp vụ
-> đây là chỗ mọi kỹ thuật tối ưu backend tác động vào

2. Chặng 2 tốn 72 ms — nhưng chỉ ở request ĐẦU TIÊN.

# Gọi lại cùng endpoint ngay sau đó
DNS Lookup            0 ms    <- đã có trong bộ nhớ đệm DNS
Initial connection 0 ms <- kết nối được dùng lại (keep-alive)
Waiting (TTFB) 182 ms
Content Download 11 ms
-------
Total 193 ms <- giảm 79 ms so với lần đầu

Đây là lý do đo một request đơn lẻ dễ gây hiểu lầm: request đầu tiên gánh toàn bộ chi phí thiết lập, những request sau thì không.

3. Và 12 ms tải nội dung cho một phản hồi 4,2 kB là chậm hơn bạn tưởng.

4,2 kB trên một kết nối bình thường: dưới 1 ms
12 ms nghĩa là phản hồi đến thành nhiều gói, hoặc mạng có độ trễ cao

-> nếu phản hồi là 400 kB thay vì 4 kB, con số này thành hàng trăm ms
-> và đó là lúc projection (chỉ lấy cột cần) trở nên quan trọng

Bốn chặng đầu đo được mà không cần trình duyệt:

curl -s -o /dev/null -w "\
dns: %{time_namelookup}s\n\
tcp: %{time_connect}s\n\
tls: %{time_appconnect}s\n\
ttfb: %{time_starttransfer}s\n\
tong: %{time_total}s\n" \
https://api.crm.vn/api/leads?page=1
dns:      0.024301s
tcp: 0.072118s
tls: 0.103447s
ttfb: 0.289512s
tong: 0.301776s
Lưu ý: các con số này CỘNG DỒN, không phải thời lượng từng phần.
thời gian TLS riêng = time_appconnect − time_connect = 31 ms
thời gian máy chủ = time_starttransfer − time_appconnect = 186 ms

Và một chặng mà DevTools không hiện ra, nhưng thường là nguyên nhân thật:

Waiting (TTFB) = 186 ms

Nhưng 186 ms đó gồm gì?
- middleware xác thực: ?
- truy vấn database: ?
- tuần tự hoá JSON: ?

DevTools không biết. Nó chỉ thấy "máy chủ mất 186 ms".

Để chia nhỏ chặng 4, cần đo từ phía máy chủ:

app.Use(async (ctx, next) =>
{
var sw = Stopwatch.StartNew();
await next();
ctx.Response.Headers["Server-Timing"] =
$"app;dur={sw.Elapsed.TotalMilliseconds:F1}";
});
Header Server-Timing hiện THẲNG trong tab Timing của DevTools
-> nối được số liệu phía máy chủ với số liệu phía trình duyệt
-> và đó là cách nhanh nhất để biết 186 ms đi đâu

Bài 2 — Giải mã JWT​

Lấy một token thật, tách phần payload và giải mã base64. Đọc exp và iat rồi đổi sang thời gian.

Tiêu chí hoàn thành: bạn đọc được nội dung token mà không cần khoá bí mật, và rút ra được điều đó có nghĩa gì với dữ liệu bạn đặt trong token.

Gợi ý và lời giải — Bài 2

Gợi ý. JWT có ba phần ngăn bởi dấu chấm. Hai phần đầu chỉ là base64url — ai cũng đọc được.

Lời giải — một token mẫu:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4ODQyIiwibmFtZSI6Ik5ndXllbiB
WYW4gQW4iLCJ0ZW5hbnQiOiJtZWdhY29ycCIsImlhdCI6MTc5MDAwMDAwMCwibmJmIjoxNzkwMDAw
MDAwLCJleHAiOjE3OTAwMDA5MDB9.xsch3d9zSYqoCf_cI5kCcATAJkq7XA-7RQ3WHQuq4Zk
# Tách và giải mã phần payload (phần thứ hai)
echo "$TOKEN" | cut -d. -f2 | tr '_-' '/+' | base64 -d 2>/dev/null | jq
{
"sub": "8842",
"name": "Nguyen Van An",
"tenant": "megacorp",
"iat": 1790000000,
"nbf": 1790000000,
"exp": 1790000900
}
date -u -d @1790000000 +%FT%TZ    # iat và nbf
date -u -d @1790000900 +%FT%TZ # exp
iat/nbf : 2026-09-21T14:13:20Z     (issued at — lúc phát hành)
exp : 2026-09-21T14:28:20Z (expires — hết hạn)

Thời hạn sống: 900 giây = 15 phút

Và phần header, cũng đọc được như vậy:

{ "alg": "HS256", "typ": "JWT" }

Điều này có nghĩa gì — ba hệ quả:

1. JWT KHÔNG mã hoá. Nó chỉ được KÝ.

Chữ ký đảm bảo: nội dung KHÔNG BỊ SỬA
Chữ ký KHÔNG đảm bảo: nội dung được giữ kín

-> bất kỳ ai cầm token đều đọc được mọi claim trong đó
-> kể cả JavaScript trong trình duyệt, kể cả một proxy trung gian

2. Vì vậy, không đặt dữ liệu nhạy cảm vào token.

Đặt đượcKhông đặt
Id người dùngSố căn cước, số thẻ
Tên hiển thịSố điện thoại, địa chỉ
Mã tenantLương, hạn mức tín dụng
Vai tròMật khẩu hoặc băm mật khẩu
Quyền hạn ở mức thôGhi chú nội bộ về khách hàng
Câu hỏi phân loại: "Nếu dòng này hiện trên màn hình người dùng,
có vấn đề gì không?"

3. Và sửa được không? Không — nhưng thử thì thấy rõ vì sao.

# Sửa "megacorp" thành "admin" trong payload rồi ghép lại
Máy chủ tính lại chữ ký từ header + payload MỚI bằng khoá bí mật
-> ra một chữ ký khác với chữ ký gửi lên
-> 401 Unauthorized

Muốn sửa mà vẫn hợp lệ thì phải có KHOÁ BÍ MẬT.

Ba trường iat, nbf, exp và vai trò của từng cái:

TrườngNghĩaMáy chủ kiểm tra
iatLúc phát hànhThường chỉ để ghi nhận, và để thu hồi theo mốc thời gian
nbfKhông hợp lệ trước thời điểm nàyToken chưa tới giờ dùng → từ chối
expHết hạn sau thời điểm nàyToken quá hạn → từ chối
Cả ba đều là giây tính từ 1970-01-01 UTC (Unix epoch).
Chúng KHÔNG có múi giờ — luôn là UTC.

Vì sao thời hạn 15 phút, không phải 24 giờ:

JWT không thu hồi được. Đã phát ra là hợp lệ cho tới khi hết hạn.

Người dùng bị khoá tài khoản lúc 14:15
-> token phát lúc 14:13, hết hạn 14:28
-> họ vẫn truy cập được 13 phút nữa

Thời hạn càng dài, cửa sổ đó càng rộng.
Cách thường dùng:
access token 15 phút -> cửa sổ rủi ro nhỏ
refresh token 7–30 ngày, LƯU TRONG DATABASE -> thu hồi được ngay

Refresh token lưu trong database là chỗ bù lại điểm yếu của JWT: nó tra cứu được, nên khoá tài khoản có hiệu lực ngay ở lần làm mới token kế tiếp.

Và một mẹo nhỏ khi gỡ lỗi xác thực: xem token bằng một lệnh.

jwt() { echo "$1" | cut -d. -f2 | tr '_-' '/+' | base64 -d 2>/dev/null | jq; }
jwt "$TOKEN"
Dán vào jwt.io cũng được, NHƯNG:
-> đừng dán token thật của production vào một trang web
-> nó là thông tin đăng nhập đang còn hiệu lực

Bài 3 — Tái hiện lệch đồng hồ​

Trên một máy ảo, tắt NTP và đặt đồng hồ lệch 20 phút, rồi thử đăng nhập vào ứng dụng có JWT.

Tiêu chí hoàn thành: bạn thấy lỗi xảy ra theo hai chiều khác nhau tuỳ đồng hồ nhanh hay chậm, và biết vì sao ClockSkew mặc định của .NET là 5 phút.

Gợi ý và lời giải — Bài 3

Gợi ý. Nghĩ xem máy nào lệch: máy phát token hay máy kiểm tra token. Hai trường hợp cho hai lỗi khác nhau.

Lời giải — dựng thí nghiệm:

# Trên máy ảo, tắt đồng bộ thời gian rồi đẩy đồng hồ đi 20 phút
sudo timedatectl set-ntp false
sudo date -s "+20 minutes"

timedatectl status | grep -E "Local time|synchronized"
Local time: Mon 2026-09-21 14:33:20 +07
System clock synchronized: no

Trường hợp A — máy KIỂM TRA token nhanh hơn 20 phút:

Máy phát:   phát token lúc 14:13, exp = 14:28
Máy kiểm: đồng hồ chỉ 14:33
-> 14:33 > 14:28 -> token ĐÃ HẾT HẠN
401 Unauthorized
WWW-Authenticate: Bearer error="invalid_token",
error_description="The token expired at '09/21/2026 14:28:20'"
Triệu chứng với người dùng: đăng nhập thành công,
rồi request ngay sau đó bị đẩy ra
-> vòng lặp đăng nhập

Trường hợp B — máy KIỂM TRA token chậm hơn 20 phút:

Máy phát:   phát token lúc 14:13, nbf = 14:13
Máy kiểm: đồng hồ chỉ 13:53
-> 13:53 < 14:13 -> token CHƯA CÓ HIỆU LỰC
401 Unauthorized
WWW-Authenticate: Bearer error="invalid_token",
error_description="The token is not valid before '09/21/2026 14:13:20'"
Triệu chứng: giống hệt trường hợp A với người dùng,
nhưng thông điệp lỗi hoàn toàn khác

Hai thông điệp lỗi này là manh mối quan trọng nhất:

"The token expired at ..."           -> máy kiểm tra NHANH hơn máy phát
"The token is not valid before ..." -> máy kiểm tra CHẬM hơn máy phát
Và dấu hiệu chung cho cả hai: thời gian trong thông điệp lỗi
KHÔNG khớp với thời gian thật lúc bạn gặp lỗi.

-> nếu lỗi nói "hết hạn lúc 14:28" mà đồng hồ của bạn đang là 14:20,
thì có một đồng hồ đang sai.

Vì sao ClockSkew mặc định của .NET là 5 phút:

builder.Services.AddAuthentication().AddJwtBearer(o =>
{
o.TokenValidationParameters = new TokenValidationParameters
{
ClockSkew = TimeSpan.FromMinutes(5), // mặc định
};
});
Trong hệ phân tán, đồng hồ các máy KHÔNG BAO GIỜ khớp tuyệt đối.
NTP giữ sai lệch ở mức mili giây tới vài chục mili giây trong điều kiện tốt,
nhưng máy ảo bị tạm dừng, máy vừa khởi động, hoặc NTP hỏng có thể lệch nhiều hơn.

5 phút là khoảng đệm đủ rộng để chịu được lệch thực tế,
đủ hẹp để không làm mất ý nghĩa của thời hạn token.
Với thí nghiệm lệch 20 phút: 5 phút đệm không đủ -> vẫn lỗi
Với lệch 2 phút: đệm hấp thụ hết -> không ai nhận ra có vấn đề

Ba cấu hình liên quan, và cách chọn:

Cấu hìnhKhi nào
ClockSkew = TimeSpan.ZeroKhi mọi máy chắc chắn đồng bộ NTP, và bạn muốn thời hạn chính xác
ClockSkew = 5 phút (mặc định)Phần lớn trường hợp
ClockSkew lớn hơn 5 phútGần như luôn là dấu hiệu che giấu một vấn đề về đồng hồ
Đặt ClockSkew = Zero với access token 15 phút:
một máy lệch 30 giây -> token hết hạn sớm 30 giây so với dự kiến
-> người dùng gặp lỗi ngẫu nhiên, khó tái hiện

Cách phòng đúng: sửa đồng hồ, không nới ClockSkew.

sudo timedatectl set-ntp true
timedatectl status
System clock synchronized: yes
NTP service: active
# Trong Kubernetes, node đồng bộ thời gian — pod dùng chung đồng hồ của node
# Nên vấn đề thường nằm ở NODE, không ở container

Và một cách phát hiện sớm, trước khi người dùng báo lỗi:

// Ghi độ lệch giữa thời gian phát token và thời gian máy chủ hiện tại
o.Events = new JwtBearerEvents
{
OnTokenValidated = ctx =>
{
var iat = ctx.Principal?.FindFirst("iat")?.Value;
if (long.TryParse(iat, out var giay))
{
var lech = (DateTimeOffset.UtcNow - DateTimeOffset.FromUnixTimeSeconds(giay)).TotalSeconds;
if (Math.Abs(lech) > 60)
ctx.HttpContext.RequestServices
.GetRequiredService<ILogger<Program>>()
.LogWarning("Lệch đồng hồ nghi ngờ: {Lech} giây", lech);
}
return Task.CompletedTask;
}
};
Cảnh báo này xuất hiện TRƯỚC khi độ lệch vượt ClockSkew
-> bạn sửa NTP trong lúc hệ thống vẫn chạy bình thường,
thay vì lúc mọi người đang không đăng nhập được

Và đây là ý chung của mục 2.12.6: lệch đồng hồ không gây lỗi từ từ — nó hoạt động hoàn hảo cho tới khi vượt ngưỡng, rồi hỏng hoàn toàn. Một cảnh báo ở ngưỡng thấp hơn là cách duy nhất để thấy nó đang tới.

Tự kiểm tra​

Frequently asked questions

Vì sao nên kiểm tra từ ngoài vào trong?

Vì mỗi chặng loại trừ được là bớt một nửa phạm vi tìm kiếm. Nhảy thẳng vào đọc code backend khi chưa biết request có tới đó không là cách tốn thời gian nhất.

DevTools Network cho biết những gì trong ca này?

Rằng DNS và HTTPS không phải vấn đề vì request đã tới server và nhận được phản hồi, và rằng frontend có gửi header Authorization. Hai kết luận đó loại trừ được hai chặng đầu ngay lập tức.

Vì sao dùng curl thay vì chỉ thử trên trình duyệt?

Vì curl loại bỏ mọi yếu tố của trình duyệt như cache, cookie, extension và CORS. Nếu curl cho kết quả khác trình duyệt thì vấn đề nằm ở phía trình duyệt chứ không phải server.

Vì sao lệch đồng hồ làm token vừa cấp đã hết hạn?

Vì JWT chứa exp là mốc thời gian tuyệt đối, và bên kiểm tra so nó với đồng hồ của mình. Nếu đồng hồ trôi nhanh hơn giữa lúc cấp và lúc kiểm tra, và độ trôi lớn hơn khoảng dung sai cho phép, token sẽ bị coi là hết hạn.

Thông tin nào trong triệu chứng ban đầu giúp thu hẹp phạm vi nhất?

Rằng lỗi xảy ra với mọi người dùng, nên không phải vấn đề tài khoản cá nhân, và rằng nó mới bắt đầu hôm nay, nên có gì đó vừa thay đổi. Hai thông tin đó hướng ngay tới thay đổi hệ thống chứ không phải bug code.

Nên làm gì sau khi sửa xong sự cố kiểu này?

Thêm kiểm tra đồng bộ thời gian vào health check, để lần sau hệ thống tự báo thay vì chờ người dùng phát hiện. Mỗi sự cố nên để lại một cảnh báo mới.

Kết luận​

Ba điều đáng nhớ nhất:

  1. Kiểm tra từ ngoài vào trong. DevTools trước, code sau.
  2. Đọc kỹ thông điệp lỗi, không chỉ mã trạng thái — nó thường nói thẳng nguyên nhân.
  3. Nguyên nhân có thể nằm ngoài code: đồng hồ, biến môi trường, chứng chỉ.

Tham khảo​

Điều hướng​