HTTP cache header điều khiển trình duyệt lưu tài nguyên bao lâu và xác thực lại như thế nào. Cache-Control, ETag và Last-Modified là 3 header cốt lõi.
Cache header — tổng quan 4 lớp lưu đệm
HTTP cache có 4 lớp: cache trình duyệt (bộ nhớ + ổ đĩa), cache proxy CDN, cache phía máy chủ (Redis, Memcached), và cache trang (plugin page cache).
Header Cache-Control, ETag và Last-Modified điều khiển 2 lớp đầu — trình duyệt và proxy CDN.
Luồng quyết định cache
- Trình duyệt yêu cầu tài nguyên.
- Kiểm tra cache trình duyệt. Còn hạn → trả về từ cache, thời gian phản hồi gần 0ms.
- Cache hết hạn → gửi yêu cầu có điều kiện kèm If-None-Match (ETag).
- Máy chủ trả về 304 (không thay đổi) hoặc 200 với nội dung mới.
- Cập nhật cache → hiển thị.
Vì sao cache header quan trọng với shop VN
Shop WooCommerce trung bình có 80-150 tài nguyên tĩnh mỗi trang — CSS, JS, font, ảnh. Không có cache header đúng, trình duyệt tải lại toàn bộ mỗi lần người dùng chuyển trang.
Cache header đúng giúp tài nguyên tĩnh tải từ ổ đĩa cục bộ, giảm đáng kể số lượng yêu cầu mạng.
Cache-Control — directive chính
Cache-Control là header chuẩn HTTP/1.1, thay thế Pragma và Expires cũ. Các directive phân cách bằng dấu phẩy điều khiển hành vi cache.
Bảng directive phổ biến
| Directive | Mục đích | Ví dụ |
|---|---|---|
| max-age=N | Cache N giây kể từ thời điểm phản hồi | max-age=31536000 (1 năm) |
| s-maxage=N | Ghi đè max-age cho proxy/CDN | s-maxage=86400 (1 ngày CDN) |
| public | Cache chia sẻ (CDN, proxy) | Tài nguyên không gắn với người dùng cụ thể |
| private | Chỉ cache tại trình duyệt, không CDN | Dữ liệu riêng từng người dùng |
| no-cache | Cache nhưng phải xác thực lại trước khi phục vụ | HTML cập nhật thường xuyên |
| no-store | Không cache bất kỳ đâu | Dữ liệu nhạy cảm (phiên đăng nhập) |
| immutable | Tài nguyên không bao giờ thay đổi (đã có version) | style.abc123.css |
| must-revalidate | Xác thực lại nghiêm ngặt, không phục vụ bản hết hạn | Tài nguyên cần luôn mới |
Tổ hợp directive phổ biến
- CSS/JS có hash trong tên file:
public, max-age=31536000, immutable - Ảnh upload (tên file cố định):
public, max-age=2592000(1 tháng) - Trang HTML:
public, max-age=0, must-revalidate(luôn xác thực lại) - API JSON động:
private, no-cache - Phiên đăng nhập/bảo mật:
no-store
Directive immutable — tiết kiệm băng thông đáng kể
Khi tên file CSS/JS chứa hash (ví dụ style.a3f91b.css), nội dung không bao giờ thay đổi sau khi build. Directive immutable báo trình duyệt không gửi yêu cầu xác thực lại trong suốt thời gian max-age.
Điều này tiết kiệm hoàn toàn lượt yêu cầu mạng cho tài nguyên đã cache — ngay cả khi người dùng nhấn F5.
ETag và Last-Modified — validator xác thực lại
Khi cache hết hạn, trình duyệt gửi yêu cầu có điều kiện kèm validator. Máy chủ kiểm tra và trả về 304 nếu không thay đổi (tiết kiệm băng thông) hoặc 200 với nội dung mới.
ETag — validator mạnh
- Máy chủ tính hash nội dung (MD5, SHA) hoặc kết hợp inode + kích thước + thời gian sửa đổi.
- Phản hồi:
ETag: "abc123def456". - Yêu cầu tiếp theo từ trình duyệt:
If-None-Match: "abc123def456". - Máy chủ so sánh ETag → trả 304 hoặc 200.
- Strong ETag: khớp byte-by-byte. Weak ETag (W/…): tương đương về ngữ nghĩa.
Last-Modified — validator yếu
- Máy chủ phản hồi:
Last-Modified: Wed, 21 Oct 2025 07:28:00 GMT. - Yêu cầu tiếp theo:
If-Modified-Since: Wed, 21 Oct 2025 07:28:00 GMT. - Máy chủ kiểm tra thời gian sửa đổi file → trả 304 hoặc 200.
- Ít chính xác hơn ETag (độ phân giải 1 giây), có thể sai nếu khôi phục file từ bản sao lưu giữ nguyên thời gian.
Khi nào nên tắt ETag
Trên hệ thống nhiều máy chủ (load balancer), mỗi máy tính ETag khác nhau từ inode riêng. Trình duyệt nhận ETag từ máy A, gửi xác thực đến máy B → không khớp → trả 200 thay vì 304.
Trong trường hợp này, tắt ETag và dùng immutable kết hợp hash tên file sẽ hiệu quả hơn.
Khuyến nghị cache header theo loại tài nguyên
| Loại tài nguyên | Cache-Control | Validator | Lý do |
|---|---|---|---|
| CSS/JS có hash tên file | public, max-age=31536000, immutable | Không cần | Tên file đổi khi nội dung đổi |
| Ảnh upload | public, max-age=2592000 | ETag | Cache 1 tháng, xác thực lại sau |
| Font woff2 | public, max-age=31536000, immutable | Không cần | Font ít thay đổi |
| Trang HTML | public, max-age=0, must-revalidate | ETag | Cập nhật thường, luôn xác thực lại |
| API JSON | private, no-cache | ETag | Dữ liệu riêng từng người dùng |
| wp-admin/ | private, no-store | Không cần | Phiên quản trị nhạy cảm |
Checklist cache header cho shop WordPress
- Xác nhận CSS/JS build có hash trong tên file (WP Rocket, Webpack, Vite tự làm).
- Set
max-age=31536000, immutablecho CSS/JS có hash. - Set
max-age=2592000cho ảnh trong wp-content/uploads/. - Set
max-age=0, must-revalidatecho HTML. - Set
no-storecho wp-admin/ và /checkout/. - Kiểm tra header qua DevTools → Network tab → chọn resource → xem Response Headers.
- Xác minh không có
no-cachesai vị trí trên CSS/JS tĩnh. - Nếu dùng load balancer: cân nhắc tắt ETag, dùng immutable thay.
Thiết lập cache header trong WordPress
WordPress core không tự set Cache-Control chặt chẽ cho tài nguyên tĩnh. Cần cấu hình ở web server hoặc plugin.
Cấu hình qua Apache .htaccess
<IfModule mod_expires.c>
ExpiresActive On
# CSS/JS — 1 năm
ExpiresByType text/css "access plus 1 year"
ExpiresByType application/javascript "access plus 1 year"
# Ảnh — 1 tháng
ExpiresByType image/webp "access plus 1 month"
ExpiresByType image/jpeg "access plus 1 month"
ExpiresByType image/png "access plus 1 month"
# Font — 1 năm
ExpiresByType font/woff2 "access plus 1 year"
</IfModule>
<IfModule mod_headers.c>
# CSS/JS có hash — thêm immutable
<FilesMatch ".(css|js)$">
Header append Cache-Control "public, immutable"
</FilesMatch>
</IfModule>
Cấu hình qua Nginx
server {
# CSS/JS có hash — cache 1 năm
location ~* .(css|js)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# Ảnh — cache 1 tháng
location ~* .(jpg|jpeg|png|webp|svg)$ {
expires 30d;
add_header Cache-Control "public";
}
# Font — cache 1 năm
location ~* .(woff2|woff)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
Cấu hình qua plugin
WP Rocket và LiteSpeed Cache có giao diện cấu hình cache header trực quan. WP Rocket tự nhận diện và áp đặt header phù hợp cho CSS/JS minified.
Kết hợp cache header đúng với Cloudflare CDN giúp tài nguyên tĩnh gần như không bao giờ phải tải lại từ máy chủ gốc. Xem thêm hướng dẫn cấu hình Cloudflare cho WordPress.
Kiểm tra header qua DevTools
Mở DevTools (F12) → tab “Network” → chọn resource → xem mục “Response Headers”. Tìm dòng cache-control để xác nhận giá trị đúng.
Tải lại trang lần hai và kiểm tra cột “Size”. Nếu hiển thị “(disk cache)” hoặc “(memory cache)” thay vì số byte, cache đang hoạt động đúng.
Cache header và các chỉ số hiệu năng
Cache header ảnh hưởng trực tiếp đến LCP và TTFB — hai chỉ số quan trọng nhất trong Core Web Vitals 2026. Tài nguyên tĩnh cache đúng giúp LCP giảm rõ rệt trên lượt xem trang thứ hai.
Đọc thêm về tối ưu TTFB tại TTFB — phân tích chuyên sâu và cách Redis hỗ trợ cache phía server tại Redis cache cho WordPress.
Bài liên quan
- Cloudflare cấu hình WordPress — cache tại edge CDN
- Redis cache cho WordPress — giảm tải cơ sở dữ liệu
- Service Worker cache — chiến lược offline-first
- TTFB — phân tích và tối ưu thời gian phản hồi
- LCP — cách đo và tối ưu
Câu hỏi thường gặp
WordPress mặc định dùng Cache-Control gì?
WordPress core không set Cache-Control chặt cho tài nguyên tĩnh. Apache/.htaccess hoặc Nginx và plugin (WP Rocket, LiteSpeed Cache) phải ghi đè.
Mặc định thường chỉ là max-age=600 (10 phút) — không đủ tốt.
Plugin WP Rocket khi kích hoạt sẽ tự động thêm rule .htaccess để set max-age 1 năm cho CSS/JS và ảnh.
ETag có nên tắt không?
Tắt ETag hợp lý khi dùng nhiều máy chủ phía sau load balancer. Mỗi máy tính ETag khác nhau (từ inode riêng), gây ra cache miss không cần thiết.
Thay vào đó: dùng immutable kết hợp hash tên file cho CSS/JS, và Last-Modified cho ảnh. Hiệu quả hơn ETag trong môi trường đa máy chủ.
no-cache khác no-store ở điểm nào?
no-cache nghĩa là cache được, nhưng phải xác thực lại với máy chủ trước mỗi lần phục vụ. no-store là tuyệt đối không lưu cache bất kỳ đâu — kể cả trình duyệt, proxy, CDN.
Dùng no-store cho dữ liệu nhạy cảm (phiên đăng nhập, thông tin thanh toán). Dùng no-cache cho HTML cập nhật thường xuyên nhưng không nhạy cảm.
Cache-Control được set ở đâu trong WordPress?
Có 3 nơi để set:
- Web server (.htaccess Apache hoặc nginx.conf) — áp dụng mọi request không qua PHP, hiệu quả nhất.
- Plugin (WP Rocket, LiteSpeed Cache) — có giao diện cấu hình trực quan.
- PHP header —
header('Cache-Control: ...')trong code tùy biến.
Cấu hình ở web server luôn được ưu tiên vì không qua PHP và áp dụng sớm nhất trong chuỗi xử lý.
Sau khi cập nhật plugin, cache CSS/JS cũ bị xóa chưa?
Nếu CSS/JS có hash tên file (WP Rocket, Webpack tự làm) — tên file mới khác tên cũ, trình duyệt tự tải mới. Nếu không có hash, trình duyệt vẫn phục vụ bản cũ cho đến khi hết TTL.
Giải pháp: bật “Query String Versioning” trong WP Rocket, hoặc cấu hình build tool tạo hash file tự động.
Cần kiểm tra cache header toàn bộ shop đang hoạt động như thế nào? Web22 tư vấn audit tối ưu Core Web Vitals bao gồm kiểm tra cache header và CDN end-to-end.


