Phục vụ media qua Cloudflare CDN + Backblaze B2 (egress miễn phí)
Đặt Cloudflare làm CDN trước một bucket Backblaze B2 public để phục vụ ảnh, audio, video tại subdomain media riêng: cấu hình DNS, URL rewrite, CORS và cache rule, egress B2 miễn phí.
Mục lục
- Lợi ích và luồng request
- Tóm tắt thao tác nhanh
- Các bước thực hiện
- Bước 0: Tiền đề và tìm B2 download host
- Bước 1: DNS - trỏ subdomain media về B2
- Bước 2: SSL/TLS = Full
- Bước 3: URL Rewrite Rule - chèn tiền tố /file/<bucket>
- Bước 4: CORS - chỉ khi app fetch() file qua JavaScript
- Bước 5: Cache Rule - cache media lâu
- Bước 6: Verify
- Bước 7: Nối vào app
- Xử lý sự cố
- Bảo mật và lưu ý quan trọng
- Chi phí
Bài này cấu hình phục vụ file tĩnh dung lượng lớn (ảnh, audio, video, phụ đề, font,
JSON...) từ Backblaze B2 thông qua Cloudflare làm CDN, truy cập tại
https://media.example.com/<path>. Dùng khi app có nhiều media và bạn muốn tải nhanh
toàn cầu mà gần như không tốn tiền băng thông: bạn chỉ trả tiền lưu trữ B2.
Trong toàn bài, thay media.example.com (subdomain media), app.example.com (domain
app chính), <bucket> (tên bucket B2 của bạn) và fNNN.backblazeb2.com (B2 download
host của bạn - xem Bước 0) bằng giá trị thật của bạn.
Lợi ích và luồng request
- Cache toàn cầu của Cloudflare: tải nhanh, giảm tải về B2.
- Egress B2 miễn phí khi traffic đi qua Cloudflare (Bandwidth Alliance) - chỉ trả tiền lưu trữ, không trả tiền băng thông ra.
- HTTPS tự động + ẩn endpoint B2 thật khỏi người dùng cuối.
Luồng request:
Browser
-> https://media.example.com/<path> (qua Cloudflare, proxied)
-> [URL Rewrite Rule] đổi path -> /file/<bucket>/<path>
-> Backblaze B2 (bucket public) (egress free + có cache CF)Tóm tắt thao tác nhanh
Toàn bộ làm trong Cloudflare dashboard của domain (trừ bước verify dùng terminal).
0. Tiền đề: bucket B2 = Public + đã upload file; domain đã nằm trên Cloudflare.
Tìm B2 download host: B2 console -> Browse Files -> bấm 1 file -> xem "Friendly URL"
-> lấy phần fNNN.backblazeb2.com (vd f001, f002...).
URL native của B2: https://fNNN.backblazeb2.com/file/<bucket>/<key>
1. DNS (Cloudflare -> DNS -> Add record)
Type=CNAME Name=media Target=fNNN.backblazeb2.com Proxy status=Proxied (đám mây CAM)
2. SSL/TLS -> Overview -> Encryption mode = Full
3. URL Rewrite Rule (Rules -> Overview -> URL Rewrite Rules -> Create rule)
Rule name: media-b2-path-rewrite
If match: Wildcard pattern -> Request URL = https://media.example.com/*
Then: Path -> Target path = /* Path -> Rewrite to = /file/<bucket>/${1}
Query -> để TRỐNG cả 2 ô
Place at: Last -> Deploy
4. CORS (Rules -> Overview -> Response Header Transform Rules -> Create rule)
[CHỈ CẦN nếu app fetch()/XHR/<track> file qua JS: phụ đề, JSON, font]
Rule name: media-cors
If: Hostname equals media.example.com
Then: Set static -> Header name = Access-Control-Allow-Origin , Value = *
(chặt hơn: Value = https://app.example.com)
-> Deploy
5. Cache Rule (Caching -> Cache Rules -> Create rule)
Rule name: media-cache
If: Hostname equals media.example.com
Then: Cache eligibility = Eligible | Edge TTL = Override origin 1 month
| Browser TTL = Override origin 1 day
-> Deploy
6. Verify (Windows PowerShell dùng curl.exe, KHÔNG phải curl)
curl.exe -I https://media.example.com/<path> # dùng 1 file CÓ THẬT; 200 + content-type đúng; chạy LẦN 2 -> cf-cache-status: HIT
curl.exe -I -H "Range: bytes=0-1023" https://media.example.com/<video.mp4> # 206 Partial Content (tua video)
curl.exe -I https://media.example.com/<subtitle.vtt> # phải có header access-control-allow-origin (nếu làm Bước 4)Các bước thực hiện
Bước 0: Tiền đề và tìm B2 download host
- Bucket B2 đặt Public và đã upload file. Key (đường dẫn file trong bucket) có
dạng
<path>, ví dụimages/banner.jpghayaudio/lesson-01.mp3. - Domain đã nằm trên Cloudflare (đã trỏ nameserver về Cloudflare).
- Xác định B2 download host của bạn: vào B2 console -> Browse Files -> bấm vào
một file bất kỳ -> nhìn mục Friendly URL. Phần đầu có dạng
fNNN.backblazeb2.com(ví dụf001,f002...). Mỗi vùng/tài khoản B2 có sốfNNNkhác nhau, dùng đúng số của bạn. - URL native của B2 luôn có dạng
https://fNNN.backblazeb2.com/file/<bucket>/<key>.
Vì sao không cho app gọi thẳng URL B2 fNNN...? Gọi thẳng sẽ bị tính phí egress B2
(không qua Cloudflare nên mất ưu đãi Bandwidth Alliance) và lộ endpoint B2 thật. Đặt
một subdomain riêng đi qua Cloudflare thì vừa free egress vừa có cache + HTTPS.
Bước 1: DNS - trỏ subdomain media về B2
Cloudflare dashboard -> chọn domain -> DNS -> Add record:
| Field | Value |
|---|---|
| Type | CNAME |
| Name | media (sẽ thành media.example.com) |
| Target | fNNN.backblazeb2.com (host ở Bước 0) |
| Proxy status | Proxied (đám mây CAM) - bắt buộc |
| TTL | Auto |
Phải là Proxied (đám mây cam). Để "DNS only" (xám) thì traffic không qua Cloudflare: mất cache, mất egress free, và các Rule bên dưới không chạy.
Bước 2: SSL/TLS = Full
Cloudflare -> SSL/TLS -> Overview -> Encryption mode = Full.
Cloudflare nói chuyện với B2 qua HTTPS; B2 có chứng chỉ hợp lệ cho *.backblazeb2.com.
"Full (strict)" cũng chạy; nếu gặp lỗi 525/526 thì hạ về Full.
Bước 3: URL Rewrite Rule - chèn tiền tố /file/<bucket>
B2 phục vụ tại /file/<bucket>/<key>, nhưng ta muốn app gọi gọn
media.example.com/<key> (không kèm /file/<bucket>). Rule này chèn tiền tố đó vào lúc
Cloudflare chuyển request về B2.
Vào Rules -> Overview -> khối URL Rewrite Rules -> Create rule. Điền:
- Rule name:
media-b2-path-rewrite - If incoming requests match... -> chọn Wildcard pattern
- Request URL:
https://media.example.com/* - Then rewrite the path and/or query...
- Path -> Target path:
/* - Path -> Rewrite to:
/file/<bucket>/${1} - Query -> Target query: để TRỐNG
- Query -> Rewrite to: để TRỐNG (giữ nguyên query string)
- Path -> Target path:
- Place at:
Last - Bấm Deploy.
Giải thích: ${1} là phần mà dấu * trong Target path /* bắt được (toàn bộ path
sau dấu / đầu tiên).
Kết quả: media.example.com/images/banner.jpg -> origin nhận
/file/<bucket>/images/banner.jpg -> B2 trả file.
Cẩn thận dấu / thừa: ô Target path / Rewrite to thường có sẵn một dấu / xám ở đầu
(prefix cố định của Cloudflare). Giá trị cuối phải là /* và /file/<bucket>/${1} -
KHÔNG để thành //* hay //file/... (dư slash thì B2 trả 404). Sau khi Deploy, dùng
curl ở Bước 6 để xác nhận.
Bước 4: CORS - chỉ khi app fetch() file qua JavaScript
Cần CORS chỉ khi trình duyệt tải file bằng JS/XHR/fetch() hoặc thẻ <track> (ví
dụ file phụ đề .txt/.vtt, JSON, font tải động). Đây là request cross-origin (từ
app.example.com sang media.example.com), thiếu header CORS sẽ bị trình duyệt chặn.
Ảnh/audio/video qua thẻ <img> / <audio> / <video> thì KHÔNG cần CORS. Nếu app
của bạn chỉ dùng các thẻ này, có thể bỏ qua bước này.
Vào Rules -> Overview -> khối Response Header Transform Rules -> Create rule:
- Rule name:
media-cors - If...: Field
Hostname-equals-media.example.com - Then...: Set static -> Header name
Access-Control-Allow-Origin, Value* - Deploy.
Dùng * an toàn khi media là public và request không kèm credentials (xem mục
Bảo mật bên dưới). Muốn chặt hơn, đặt Value = https://app.example.com (chỉ cho domain
app của bạn).
Bước 5: Cache Rule - cache media lâu
Media gần như bất biến (không đổi nội dung) nên cache lâu để nhanh và đỡ tốn request về B2.
Vào Caching (menu trái) -> Cache Rules -> Create rule:
- Rule name:
media-cache - If...: Field
Hostname-equals-media.example.com - Then...:
- Cache eligibility:
Eligible for cache - Edge TTL:
Override origin->1 month - Browser TTL:
Override origin->1 day
- Cache eligibility:
- Deploy.
Nếu sau này bạn thay file nhưng giữ NGUYÊN tên (key), cache Cloudflare + trình duyệt có thể còn giữ bản cũ tới khi hết TTL. Cách tránh: đổi tên file khi đổi nội dung (ví dụ thêm version/hash vào tên), hoặc Purge Cache thủ công trong Cloudflare.
Bước 6: Verify
Chạy trên terminal. Windows PowerShell phải dùng curl.exe, không phải curl; trên
macOS/Linux dùng curl bình thường.
1. File thường (dùng một file CÓ THẬT): phải trả 200 và content-type đúng. Chạy
LẦN 2 phải thấy header cf-cache-status: HIT.
curl.exe -I https://media.example.com/<path>2. Video Range (tua video): phải trả 206 Partial Content.
curl.exe -I -H "Range: bytes=0-1023" https://media.example.com/<video.mp4>3. CORS (nếu làm Bước 4): response phải có header access-control-allow-origin.
curl.exe -I https://media.example.com/<subtitle.vtt>Đạt hết cả ba là xong.
Bước 7: Nối vào app
- Trong code, build URL media =
https://media.example.com/<path>. - Nếu framework inline biến lúc build (ví dụ Vite
import.meta.env.VITE_*, NextNEXT_PUBLIC_*), đặt biến base URL (ví dụMEDIA_BASE_URL=https://media.example.com) ở build time, đừng hardcode trong code. - Luôn truy cập qua subdomain proxied. Nếu lỡ trỏ thẳng URL
fNNN.backblazeb2.comthì sẽ bị tính egress B2 (mất ưu đãi free) và lộ endpoint.
Xử lý sự cố
| Triệu chứng | Cách xử lý |
|---|---|
| 404 "file not found" | Sai URL Rewrite Rule (Bước 3). Test thẳng B2: curl.exe -I https://fNNN.backblazeb2.com/file/<bucket>/<key> - nếu lệnh này trả 200 mà qua media.example.com lại 404 thì lỗi nằm ở rule rewrite path. |
| 404 dù rule "trông đúng" | Ô Target path / Rewrite to bị dư dấu / ở đầu (thành //file/...). Sửa lại đúng /* và /file/<bucket>/${1}. |
| Console báo "blocked by CORS" | Thiếu CORS rule (Bước 4) hoặc sai hostname trong điều kiện. Chỉ ảnh hưởng file tải qua JS (phụ đề/JSON/font). |
| 525 / 526 (SSL) | Đổi SSL/TLS mode về Full (Bước 2). |
cf-cache-status luôn MISS | Kiểm tra Cache Rule (Bước 5). B2 có thể gửi Cache-Control ngắn; Edge TTL đặt "Override origin" sẽ ghi đè. |
| 401 / 403 từ B2 | Bucket chưa Public - vào B2 đặt lại bucket = Public. |
| DNS không phân giải | Record media chưa Proxied, hoặc chưa lưu / chưa kịp lan truyền. |
Bảo mật và lưu ý quan trọng
- Bucket Public = ai có URL đều tải được. Chỉ để file công khai (media của app) trong bucket này; KHÔNG để file nhạy cảm/riêng tư.
- Cookie phiên của app để host-only: đừng set
Domain=.example.comcho cookie đăng nhập. Để host-only thì cookie chỉ gửi choapp.example.com, KHÔNG bị gửi kèm sangmedia.example.com. Nhờ vậy CORSAccess-Control-Allow-Origin: *mới an toàn (request media không mang theo credentials). - Không bao giờ trả về
*cho CORS nếu cần gửi credentials. Trình duyệt chặn*+ credentials; khi đó phải đặt origin cụ thể (https://app.example.com) và bậtAccess-Control-Allow-Credentials- nhưng với media public thì không nên dùng credentials. - Luôn đi qua subdomain proxied, đừng để URL
fNNN.backblazeb2.comlọt vào code/HTML public (mất free egress + lộ endpoint).
Chi phí
Traffic Cloudflare <-> B2 nằm trong Bandwidth Alliance nên egress B2 miễn phí khi đi qua Cloudflare (proxied). Người dùng tải qua cache Cloudflare. Gói Cloudflare Free đủ cho nhu cầu thông thường. Bạn chỉ trả tiền lưu trữ B2 (rẻ), gần như không trả tiền băng thông.