Server bạn đang chạy vài Docker app nhỏ: một app Node.js ở port 3000, một dashboard nội bộ ở port 8080, thêm vài service phụ phía sau. Nếu dùng Nginx, bạn phải viết server block, cấu hình Certbot, nhớ renew certificate và kiểm tra redirect HTTP sang HTTPS. Làm được, nhưng hơi nhiều việc cho một stack nhỏ.
Caddy reverse proxy giải quyết đúng pain point đó: config ngắn, đọc dễ, automatic HTTPS mặc định, tự xin và renew TLS certificate khi domain trỏ đúng về server. Với Docker Compose, bạn chỉ cần mount một Caddyfile, mở port 80/443 và proxy request về service nội bộ.
Bài này hướng dẫn cách deploy Caddy làm reverse proxy cho Docker app trên Ubuntu, kèm cấu hình production cơ bản, reload config an toàn và các lỗi thường gặp.

Khi nào nên dùng Caddy thay vì Nginx?
Caddy hợp nhất khi bạn cần reverse proxy nhanh, HTTPS tự động và config ít ceremony. Ví dụ: homelab public qua domain riêng, VPS chạy vài Docker app, staging environment, hoặc hệ thống nhỏ cần bật HTTPS chuẩn mà không muốn maintain Certbot riêng.
- Automatic HTTPS: Caddy tự lấy và gia hạn certificate cho public hostname qua ACME CA như Let’s Encrypt/ZeroSSL.
- Caddyfile ngắn: một site block thường chỉ cần domain và
reverse_proxy. - Docker friendly: container Caddy có thể proxy tới service name trong cùng Docker network.
- HTTP to HTTPS redirect: Caddy tự xử lý redirect khi HTTPS được bật cho hostname.
Nginx vẫn rất mạnh cho workload phức tạp, caching nâng cao hoặc hệ sinh thái module quen thuộc. Nhưng với nhiều VPS nhỏ, Caddy giúp giảm đáng kể thời gian cấu hình TLS và reverse proxy.
Prerequisites trước khi triển khai
Để automatic HTTPS hoạt động ổn định, bạn cần chuẩn bị đúng các điều kiện bên dưới. Nếu thiếu một trong các phần này, Caddy có thể chạy nhưng không xin được certificate.
- Ubuntu server có Docker và Docker Compose plugin.
- Domain/subdomain public, ví dụ
app.example.com. - DNS A/AAAA record trỏ về public IP của server.
- Port
80/tcpvà443/tcpmở từ Internet vào server. - Không có service khác đang chiếm port 80/443 trên host.
# Kiểm tra Docker
docker --version
docker compose version
# Kiểm tra port đang listen
sudo ss -tulpn | grep -E ':80|:443' || true
# Nếu dùng UFW
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw status

Tạo project Docker Compose cho Caddy reverse proxy
Tạo một thư mục riêng để quản lý Caddy. Trong ví dụ này, backend app chạy cùng Compose stack và listen ở port 3000 nội bộ. Bạn có thể thay image app bằng service thật của mình.
mkdir -p ~/stacks/caddy-proxy
cd ~/stacks/caddy-proxy
cat > docker-compose.yml <<'EOF'
services:
caddy:
image: caddy:2
container_name: caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp" # HTTP/3, optional
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
networks:
- proxy
app:
image: node:22-alpine
container_name: demo-app
restart: unless-stopped
working_dir: /app
command: sh -c "printf 'Hello from Docker app' > index.html && npx serve -l 3000 ."
expose:
- "3000"
networks:
- proxy
volumes:
caddy_data:
caddy_config:
networks:
proxy:
driver: bridge
EOF
Điểm quan trọng nhất là volume caddy_data. Caddy lưu certificate và ACME account trong đó. Nếu xóa volume này liên tục, bạn có thể bị rate limit khi xin lại certificate nhiều lần.
Viết Caddyfile: một domain, một backend
Caddyfile tối giản cho reverse proxy chỉ cần hostname và upstream. Khi hostname là domain public hợp lệ, Caddy tự bật HTTPS.
cat > Caddyfile <<'EOF'
app.example.com {
reverse_proxy app:3000
}
EOF
Trong Docker network, app là service name và 3000 là port app listen bên trong container. Bạn không cần publish port 3000 ra host, vì traffic public chỉ đi qua Caddy.
Khi dùng domain thật, thay app.example.com bằng subdomain của bạn. DNS phải trỏ về server trước khi start stack, hoặc ít nhất trước khi Caddy thực hiện ACME challenge.

Start stack và kiểm tra HTTPS
docker compose up -d
docker compose ps
docker compose logs -f caddy
Trong log, bạn sẽ thấy Caddy load config, mở listener 80/443 và bắt đầu quá trình obtain certificate. Nếu DNS và firewall đúng, truy cập https://app.example.com sẽ trả về app phía sau reverse proxy.
curl -I https://app.example.com
curl -I http://app.example.com
Request HTTP thường sẽ được redirect sang HTTPS. Nếu curl HTTPS lỗi certificate, kiểm tra lại DNS, port 80/443, log Caddy và service đang chiếm port.
Proxy nhiều Docker app bằng nhiều subdomain
Một Caddy container có thể proxy nhiều backend. Mỗi subdomain là một site block riêng trong Caddyfile.
api.example.com {
reverse_proxy api:8080
}
dashboard.example.com {
reverse_proxy dashboard:3000
}
app.example.com {
reverse_proxy app:3000
}
Với nhiều Compose project khác nhau, cách sạch nhất là tạo một Docker network external, cho Caddy và các app cùng join vào network đó.
docker network create proxy
# Trong compose của Caddy và app:
networks:
proxy:
external: true
Reload Caddyfile không downtime
Khi sửa Caddyfile, đừng restart bừa nếu server đang có traffic. Hãy validate config trước, sau đó reload.
docker compose exec caddy caddy validate --config /etc/caddy/Caddyfile
docker compose exec caddy caddy reload --config /etc/caddy/Caddyfile
Nếu validate fail, Caddy sẽ báo dòng lỗi cụ thể. Sửa xong validate lại rồi mới reload. Đây là thói quen nhỏ nhưng giúp tránh downtime không đáng có.
Thêm header bảo mật cơ bản
Caddy không ép bạn phải thêm security header, nhưng với app public, bạn nên có baseline tối thiểu. Ví dụ dưới đây thêm vài header phổ biến. Hãy test kỹ nếu app có iframe, asset cross-origin hoặc policy riêng.
app.example.com {
header {
X-Content-Type-Options "nosniff"
X-Frame-Options "DENY"
Referrer-Policy "strict-origin-when-cross-origin"
}
reverse_proxy app:3000
}
Với Content-Security-Policy, không nên copy paste quá mạnh ngay từ đầu vì dễ làm hỏng frontend. Hãy bật report/test trước nếu app đang production.
Troubleshooting lỗi thường gặp
Caddy không xin được certificate
- DNS A/AAAA chưa trỏ đúng public IP.
- Port 80 hoặc 443 bị firewall/security group chặn.
- Một Nginx/Apache khác đang chiếm port 80/443.
- Domain đang proxy qua CDN sai mode hoặc challenge không tới được server.
dig +short app.example.com
sudo ss -tulpn | grep -E ':80|:443'
docker compose logs caddy --tail=200
502 Bad Gateway
502 thường nghĩa là Caddy chạy được nhưng không kết nối được backend. Kiểm tra service name, port nội bộ, Docker network và app có thật sự listen không.
docker compose ps
docker compose logs app --tail=100
docker compose exec caddy wget -S -O- http://app:3000
WebSocket không hoạt động
reverse_proxy của Caddy hỗ trợ WebSocket khá tự nhiên. Nếu vẫn lỗi, thường nguyên nhân nằm ở backend base URL, trusted proxy setting, hoặc app tự kiểm tra scheme/host chưa đúng.
Xem thêm các bài liên quan: Docker Compose startup order đúng cách và systemd timer thay cron trên Linux.
Checklist production nhanh
- Domain trỏ đúng IP server.
- Port 80/443 mở từ Internet.
caddy_datađược giữ persistent.- Backend không publish port public nếu không cần.
- Caddyfile được validate trước khi reload.
- Log được theo dõi khi deploy hoặc đổi DNS.
- Có backup compose file và Caddyfile trong Git/private repo.
FAQ
Caddy có tự renew SSL certificate không?
Có. Với public domain hợp lệ và storage còn giữ nguyên, Caddy tự quản lý vòng đời certificate, bao gồm obtain và renew.
Có cần Certbot khi dùng Caddy không?
Thông thường không cần. Caddy đã tích hợp automatic HTTPS. Chỉ dùng Certbot riêng nếu bạn có kiến trúc đặc biệt và chủ động muốn quản lý certificate ngoài Caddy.
Caddy có dùng được cho wildcard certificate không?
Có thể, nhưng wildcard thường cần DNS-01 challenge và plugin DNS provider tương ứng. Với Docker image chính thức, bạn có thể cần build custom image có plugin, ví dụ plugin Cloudflare.
Nên dùng Caddy trực tiếp trên host hay trong Docker?
Nếu toàn bộ app chạy Docker, chạy Caddy trong Docker Compose rất gọn và dễ version config. Nếu server có nhiều service native ngoài Docker, cài Caddy trực tiếp trên host cũng hợp lý.
Kết luận
Caddy reverse proxy là lựa chọn rất đáng dùng cho Docker app nhỏ đến vừa: cấu hình ngắn, HTTPS tự động, reload dễ và ít phải chăm certificate thủ công. Chỉ cần đảm bảo DNS đúng, port 80/443 mở và volume caddy_data được giữ persistent, bạn đã có một reverse proxy production-ready cho nhiều use case phổ biến.
Nếu stack của bạn đang dùng Nginx + Certbot chỉ để proxy vài app đơn giản, Caddy là phương án đáng thử để giảm độ phức tạp vận hành.
