---
id: webserver-edge
title: "Load balancer, CDN và Caddy"
shortTitle: "LB, CDN, Caddy"
track: webserver
trackTitle: "Web Server, Proxy & Load Balancer"
order: 2
level: core
status: published
estimatedMinutes: 40
prerequisites:
  - webserver-apache
next: webserver-tomcat
reviewedAgainst: 2026-10-12
sources:
  - title: "Caddy — reverse_proxy"
    url: https://caddyserver.com/docs/caddyfile/directives/reverse_proxy
  - title: "Caddy — Automatic HTTPS"
    url: https://caddyserver.com/docs/automatic-https
  - title: "MDN — CDN"
    url: https://developer.mozilla.org/en-US/docs/Glossary/CDN
---

# Load balancer, CDN và Caddy

Nginx và Apache đã làm reverse proxy một upstream. Bài này là bước tiếp: nhiều upstream, cache ở biên, và một web server tự xin certificate.

## Để làm gì

Chọn được chỗ cắt TLS và chỗ chia request. Đọc một Caddyfile ngắn và biết nó khác Nginx ở chỗ tự HTTPS. Không nhầm CDN với load balancer.

## Gồm những gì

- Load balancer lớp 4 và lớp 7, health check.
- CDN: cache ở edge, origin là app của bạn.
- Caddyfile với `reverse_proxy`.

Tomcat và IIS có bài riêng ngay sau. Gặp Tomcat thì nó thường nghe 8080, Apache/Nginx đứng trước. IIS là web server trên Windows Server.

## Dùng khi nào

Một process không chịu nổi traffic, hoặc cần rút một máy ra mà không cắt user. CDN khi file tĩnh hoặc response được cache, user ở xa origin.

Caddy hợp server nhỏ, một người vận hành, muốn HTTPS mà không viết block `ssl_certificate`. Nginx vẫn hợp khi team đã có sẵn config và cần nhiều directive hơn.

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

Load balancer lớp 4 chuyển TCP hoặc UDP. Nó không đọc HTTP path. Lớp 7 đọc HTTP: chia theo path, theo header, gắn cookie session. Health check gọi một URL hoặc một port. Máy fail check thì bị rút khỏi pool. Không có health check thì balancer vẫn đổ request vào process đã chết.

CDN là mạng cache ở nhiều nơi. MDN mô tả CDN như nhóm server trải theo địa lý, đứng giữa user và origin. User lấy bản cache nếu còn hạn. `Cache-Control` trên response quyết định có được cache không. API có dữ liệu riêng từng user thì đừng để CDN cache chỉ vì "bật CDN cho nhanh".

Caddy nhận Caddyfile. Một site:

```caddy
api.example.com {
	reverse_proxy localhost:3000
}
```

Docs automatic HTTPS: Caddy tự xin certificate và redirect HTTP sang HTTPS khi site có tên miền thật, không phải IP, và port 80/443 mở ra Internet để ACME challenge chạy. `localhost` thì dùng certificate nội bộ của Caddy, browser sẽ cảnh báo nếu bạn không cài root đó. Lab dưới chỉ để hiểu file, không cần domain.

`reverse_proxy` nhận nhiều upstream thì Caddy chia request giữa chúng. Health check mặc định nhìn kết nối có thành công hay không. Chi tiết policy nằm trong docs của directive.

## Lệnh và code mẫu

Cài Caddy theo [cách chính thức](https://caddyserver.com/docs/install) của OS bạn. Kiểm tra `caddy version`.

`Caddyfile` trong `$HOME/devops-lab/webserver`:

```caddy
:8080 {
	reverse_proxy 127.0.0.1:3000
}
```

Port 8080, không tên miền, nên không đụng ACME. Terminal 1 vẫn là `python3 -m http.server 3000` từ bài Apache.

```bash
cd "$HOME/devops-lab/webserver"
caddy run --config Caddyfile
```

Terminal khác:

```bash
curl -sI http://127.0.0.1:8080/ | head -n 8
```

Tắt bằng `Ctrl-C`.

## Thực hành

1. Giữ app ở port 3000. `caddy run` với file trên.
2. `curl` qua 8080. Status 200.
3. Tắt `python3`. `curl` lại. Phải lỗi kết nối từ Caddy tới upstream, không phải "connection refused" từ chính port 8080. Caddy vẫn nghe.
4. Bật lại app. Sửa Caddyfile thêm một upstream không tồn tại `127.0.0.1:3999` trên cùng `reverse_proxy`, reload bằng cách chạy lại. Một phần request vẫn 200 vì upstream 3000 còn sống.
5. Viết một câu: file tĩnh nào của app bạn cho CDN cache được, API nào thì không.

## Kết quả mong đợi

- Port 8080 trả body của app ở 3000.
- App tắt thì 8080 vẫn mở, response là lỗi bad gateway.
- Bạn chỉ ra được ít nhất một path không được cache vì có dữ liệu theo user.

## Checklist

- [ ] Phân biệt load balancer lớp 4 và lớp 7.
- [ ] Nói được health check dùng để làm gì.
- [ ] Không cache response riêng từng user.
- [ ] Đọc được một Caddyfile `reverse_proxy`.
- [ ] Biết automatic HTTPS cần tên miền và port 80/443, không cần cho lab `localhost`.

## Thẻ ôn

### webserver-edge-lb
kind: distinguish
cue: Lớp 4 chuyển TCP hoặc UDP và không đọc path. Lớp 7 đọc HTTP nên chia được theo path, header, và gắn cookie session. Không có health check thì máy chết vẫn nhận request.

Q: Cần chia traffic: `/api` vào một nhóm máy, trang tĩnh vào nhóm khác, và giữ session bằng cookie. Load balancer lớp 4 làm được không, và nếu không có health check thì request vào process đã chết đi đâu?

A: Lớp 4 chuyển cả phiên TCP hoặc UDP. Nó không mở HTTP ra đọc, nên không biết path, không biết header, không gắn cookie. Việc bạn mô tả là lớp 7: balancer đọc HTTP rồi mới chọn nhóm máy. Không cấu hình health check thì balancer không có cách biết process đã chết. Nó tiếp tục đổ request vào đích đó cho đến khi có thứ khác gỡ máy ra. Check là phần của cấu hình, không phải thứ balancer tự có vì "nó là load balancer".

### webserver-edge-cdn
kind: trap
cue: Bật CDN cho API có dữ liệu riêng từng user thì user này có thể nhận JSON của user kia. `Cache-Control: private`, hoặc bỏ path `/api` khỏi cache.

Q: Site đã có CDN cho file tĩnh. Có người bật luôn CDN cho API, mà API trả dữ liệu khác nhau cho từng user đã đăng nhập. Rủi ro là gì, và CDN có phải một load balancer không?

A: CDN lưu response ở nhiều nơi và có thể trả lại bản đã lưu cho người hỏi sau. Nếu bản đó là JSON của user A, user B hỏi cùng URL có thể nhận đúng bản của A. Dữ liệu riêng không được cache chung. Đặt `Cache-Control: private`, hoặc cấu hình CDN bỏ qua path `/api`. CDN là mạng cache phân tán, không phải load balancer đứng chọn một máy app còn sống. Đừng bật nó cho mọi path chỉ vì file tĩnh đã nhanh hơn.

### webserver-edge-health
kind: command
cue: Health check trỏ vào `/` mà app redirect về trang login thì kết quả không đáng tin. Check một path như `/health`, chỉ trả `200` khi process sẵn sàng.

Q: Health check của balancer đang trỏ vào `/`. App thấy người lạ thì redirect về trang đăng nhập. Check này còn nói được process sẵn sàng phục vụ không?

A: Không. Balancer nhận `302`, rồi tùy sản phẩm mà coi là sống hoặc coi là chết. Cả hai cách đều không đo việc app sẵn sàng. Redirect login chỉ nói là app còn trả được một câu HTTP, không nói database còn đó hay request thật có được phục vụ. Đưa check tới một path dành riêng, ví dụ `/health`, và path đó chỉ trả `200` khi process thật sự sẵn sàng. Đừng dùng trang chủ làm health check.

### webserver-edge-caddy
kind: decide
cue: Caddy tự xin certificate khi có tên miền và tới được cổng xác thực. Firewall chặn port 80 thì ACME không xong. Lab `localhost` không cần automatic HTTPS.

Q: Caddyfile production khai một tên miền và trông chờ automatic HTTPS. Firewall đang chặn port 80, 443 thì mở. Certificate có được cấp không, và lab trên `localhost` có cần cơ chế đó không?

A: Chưa chắc. Automatic HTTPS của Caddy nhờ ACME xác minh bạn kiểm soát tên miền. Cách HTTP-01 cần tới được cổng 80. Firewall chặn cổng đó thì xác minh thất bại và certificate không được phát hành, dù 443 đã mở cho traffic sau này. Mở cả 80 và 443, hoặc chuyển sang DNS challenge nếu không mở được 80. Lab dùng `localhost` không đi xin certificate công cộng. Đừng bật automatic HTTPS cho tên mà không có DNS và không có cổng xác thực.

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

**Bật CDN cho cả API.** User A nhận JSON của user B. Đặt `Cache-Control: private` hoặc bypass path `/api`.

**Health check trỏ vào `/` của app có redirect login.** Check nhận 302 và vẫn coi là sống, hoặc coi là chết, tuỳ balancer. Check một path chỉ trả 200 khi process sẵn sàng, ví dụ `/health`.

**Đưa Caddyfile production dùng tên miền mà port 80 bị firewall chặn.** ACME không xác thực được, certificate không phát hành. Mở 80/443, hoặc dùng DNS challenge. Lab `localhost` không đụng việc này.

## Nguồn

1. [reverse_proxy](https://caddyserver.com/docs/caddyfile/directives/reverse_proxy) — Caddy, nhiều upstream.
2. [Automatic HTTPS](https://caddyserver.com/docs/automatic-https) — khi nào Caddy tự xin certificate.
3. [CDN](https://developer.mozilla.org/en-US/docs/Glossary/CDN) — MDN, cache ở biên và origin.

## Tiếp theo

[Tomcat](./03-tomcat.md)
