Sổ tay kỹ thuật

Caddy reverse proxy với HTTPS tự động

how-to#caddy#tls#linux· cập nhật 2026-11-06

Dùng khi cần reverse proxy có HTTPS Let's Encrypt tự cấp và tự gia hạn chỉ với vài dòng Caddyfile, thay cho nginx + certbot.

Sửa trang này
Mục lục

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

/etc/caddy/Caddyfile
{
	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

/etc/caddy/Caddyfile
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-password
admin.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}

Tham khảo