Tóm tắt: Cài Caddy v2, viết /etc/caddy/Caddyfile với một khối cho mỗi domain và reverse_proxy tới upstream. Chỉ cần DNS trỏ đúng và port 80/443 mở, Caddy tự xin và gia hạn chứng chỉ, tự redirect HTTP sang HTTPS, tự gửi X-Forwarded-*, hỗ trợ WebSocket và HTTP/2, HTTP/3 không cần cấu hình. Kiểm tra bằng caddy validate, áp dụng bằng systemctl reload caddy.
Luồng hoạt động
flowchart LR C[Client] -->|":80"| R["Redirect sang HTTPS + ACME HTTP-01"] C -->|":443 TLS (tự cấp)"| CA[Caddy] CA -->|"/api/* (handle_path)"| A["api 127.0.0.1:8000"] CA -->|"còn lại"| W["web 127.0.0.1:3000"] CA <-->|"ACME"| LE["Let's Encrypt / ZeroSSL"]
Cấu hình
Caddyfile cơ bản
{
email ops@example.com
# acme_ca https://acme-staging-v02.api.letsencrypt.org/directory # bật khi thử nghiệm
}
app.example.com {
encode zstd gzip
# /api/users -> upstream nhận /users (bỏ prefix)
handle_path /api/* {
reverse_proxy 127.0.0.1:8000
}
handle {
reverse_proxy 127.0.0.1:3000
}
header {
Strict-Transport-Security "max-age=31536000"
X-Content-Type-Options nosniff
-Server
}
log {
output file /var/log/caddy/app.access.log
format json
}
}
# Domain phụ chuyển hướng
www.app.example.com {
redir https://app.example.com{uri} permanent
}caddy fmt chuẩn hoá thụt lề về tab. Khối { ... } không có tên ở đầu file là global options, phải đứng trước mọi site.
Nhiều upstream, health check, upstream HTTPS
api.example.com {
reverse_proxy 127.0.0.1:8001 127.0.0.1:8002 {
lb_policy least_conn
health_uri /healthz
health_interval 10s
fail_duration 30s
}
}
legacy.example.com {
reverse_proxy https://backend.example.net {
header_up Host {upstream_hostport}
}
}
# Chỉ dùng nội bộ: chứng chỉ do CA cục bộ của Caddy ký
grafana.lab.example.com {
tls internal
reverse_proxy 127.0.0.1:3000
}Mặc định Caddy giữ nguyên Host của client khi gửi tới upstream. Upstream HTTPS dùng virtual host thì phải header_up Host {upstream_hostport}.
Lệnh
caddy version
caddy fmt --overwrite /etc/caddy/Caddyfile
caddy validate --config /etc/caddy/Caddyfile
caddy adapt --config /etc/caddy/Caddyfile --pretty # xem JSON config thật sự sinh ra
sudo systemctl reload caddy # reload không downtime
journalctl -u caddy -f # log xin chứng chỉ, lỗi ACME
# Thử nhanh không cần file
caddy reverse-proxy --from app.example.com --to 127.0.0.1:8000
# Basic auth: sinh hash rồi dán vào khối basic_auth
caddy hash-passwordadmin.example.com {
basic_auth {
admin <hash-tu-caddy-hash-password>
}
reverse_proxy 127.0.0.1:9000
}Chứng chỉ và account ACME của bản cài bằng package (user caddy) nằm trong /var/lib/caddy/.local/share/caddy. Chạy Docker thì phải mount volume /data (và /config), nếu không mỗi lần tạo lại container sẽ xin cert mới và dễ dính rate limit.
So sánh với nginx
| Tiêu chí | Caddy | nginx |
|---|---|---|
| HTTPS | Tự xin/gia hạn (ACME), tự redirect 80 → 443 | Cần certbot/acme.sh và cấu hình ssl_* |
| Header proxy | Tự gửi X-Forwarded-For/Proto/Host |
Tự viết proxy_set_header từng location |
| WebSocket | Tự động | Cần Upgrade/Connection header |
| HTTP/3 | Bật sẵn | Cần build/cấu hình quic |
| Cấu hình | Caddyfile ngắn, hoặc JSON qua admin API :2019 |
Linh hoạt, cực nhiều module, tài liệu và ví dụ |
| Hiệu năng, cache | Đủ cho hầu hết app | Mạnh hơn cho static lớn, proxy_cache |
| Phù hợp | VPS, homelab, vài service, cần TLS nhanh | Cấu hình phức tạp, đã có hạ tầng nginx |
Cấu hình tương đương bằng nginx: Nginx reverse proxy với TLS.
Lỗi thường gặp
| Triệu chứng | Nguyên nhân | Cách xử lý |
|---|---|---|
Log challenge failed, no such host hoặc connection refused khi xin cert |
DNS chưa trỏ về máy, hoặc port 80/443 bị firewall/NAT chặn | dig +short app.example.com, mở port 80 và 443 từ Internet |
listen tcp :443: bind: address already in use |
nginx/apache đang giữ port | Dừng dịch vụ kia hoặc đổi port |
Lỗi too many certificates / rate limit |
Thử nghiệm nhiều lần với CA production, mất volume /data |
Dùng acme_ca staging khi thử, giữ volume dữ liệu |
App sinh link http:// hoặc thấy IP client là 127.0.0.1 |
App không tin header từ proxy | Cấu hình app đọc X-Forwarded-* (vd uvicorn --proxy-headers) |
| Caddy đứng sau proxy khác (Cloudflare, LB) và log IP sai | Caddy không tin X-Forwarded-For đến từ ngoài |
Global option servers { trusted_proxies static <CIDR> } |
handle_path /api không khớp /api/users |
Matcher path khớp chính xác, thiếu * |
Dùng /api/* |
Trình duyệt cảnh báo chứng chỉ không tin cậy với localhost, IP hoặc tls internal |
Caddy dùng CA nội bộ cho các địa chỉ này | caddy trust (cài CA vào máy đang chạy Caddy), hoặc copy pki/authorities/local/root.crt trong thư mục dữ liệu Caddy sang client; hoặc dùng domain thật |
| Upstream HTTPS trả 404/421 | Gửi Host của client tới backend virtual host |
header_up Host {upstream_hostport} |