Operations

Reverse proxy với HTTPS tự động bằng Caddy

Đặt một service HTTP ra Internet an toàn luôn kéo theo cùng một danh sách việc lặt vặt: xin cert TLS, cấu hình renew, viết block reverse proxy, bật nén, gắn security header. Với Nginx bạn ghép Certbot vào rồi tự lo cron gia hạn. Caddy gộp toàn bộ chuỗi đó vào một binary Go duy nhất: HTTPS bật mặc định, cert Let's Encrypt xin và renew tự động, cú pháp cấu hình gọn tới mức một reverse proxy đầy đủ chỉ ba dòng. Bài này đi từ cài đặt tới một Caddyfile production phục vụ nhiều site, kèm so sánh thẳng với Nginx để bạn biết khi nào nên đổi.

Caddy giải quyết vấn đề gì

Caddy là web server và reverse proxy viết bằng Go, phân phối dưới dạng một file thực thi tĩnh không phụ thuộc runtime ngoài. Điểm khác biệt lớn nhất so với Nginx hay Apache là Automatic HTTPS: khi bạn khai một domain trong cấu hình, Caddy tự chạy giao thức ACME với Let's Encrypt (hoặc ZeroSSL làm dự phòng), lấy certificate, cài vào bộ nhớ và tự gia hạn trước khi hết hạn. Bạn không viết một dòng nào về đường dẫn cert, không đụng tới Certbot, không cần cron job renew.

Kèm theo đó là vài mặc định hợp lý cho 2026: HTTP/2 bật sẵn, HTTP/3 (QUIC) khả dụng, tự redirect HTTP sang HTTPS, và OCSP stapling. Đổi lại bạn chấp nhận một web server ít mảnh module rời rạc hơn Nginx và hiệu năng tĩnh thô nhỉnh hơn nghiêng về Nginx ở tải rất cao. Với vai trò reverse proxy đứng trước app backend, khác biệt hiệu năng đó gần như không đáng kể.

Yêu cầu trước khi bắt đầu

Caddy cần hai thứ để xin được cert qua ACME, không có ngoại lệ:

  • Port 80 và 443 mở ra Internet. Let's Encrypt xác thực quyền sở hữu domain qua các port này (HTTP-01 dùng port 80, TLS-ALPN-01 dùng port 443). Nếu firewall chặn, quá trình xin cert sẽ thất bại.
  • Một domain public trỏ A/AAAA record về IPv4 của VPS. ACME chỉ cấp cert cho domain thật phân giải được, không cấp cho địa chỉ IP trần hay tên nội bộ. Cần một VPS có IPv4 tĩnh và bản ghi DNS trỏ đúng về nó là đủ.

Kiểm tra DNS đã trỏ đúng trước khi chạy Caddy, tránh việc Let's Encrypt đếm vào giới hạn số lần fail của rate limit:

dig +short app.example.com A
# phải trả về đúng IPv4 của VPS

Cài Caddy trên Debian và Ubuntu

Dùng repository APT chính thức của dự án, đừng cài gói caddy mặc định trong kho Ubuntu vì bản đó thường trễ nhiều phiên bản và có thể không hiểu cú pháp Caddyfile hiện tại.

sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl

curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \
  | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg

curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' \
  | sudo tee /etc/apt/sources.list.d/caddy-stable.list

sudo apt update
sudo apt install caddy

Gói này cài Caddy như một systemd service chạy sẵn, kèm file cấu hình mẫu tại /etc/caddy/Caddyfile. Kiểm tra service đang chạy và xem phiên bản:

systemctl status caddy
caddy version

Caddyfile tối giản: reverse proxy tới một backend

Giả sử bạn có một app Node.js hay bất kỳ service HTTP nào nghe ở localhost:8080. Mở /etc/caddy/Caddyfile, xóa nội dung mẫu và thay bằng:

app.example.com {
    reverse_proxy localhost:8080
}

Ba dòng này là toàn bộ những gì cần. Khi reload, Caddy sẽ tự xin cert Let's Encrypt cho app.example.com, phục vụ HTTPS, redirect mọi request HTTP sang HTTPS, và proxy request tới backend. Không có block server, không có đường dẫn cert, không có listen 443 ssl như Nginx.

Về header, Caddy tự set các X-Forwarded-For, X-Forwarded-ProtoX-Forwarded-Host khi proxy, nên backend biết được request gốc là HTTPS và IP thật của client. Nếu backend cần thêm header tùy biến, dùng header_up bên trong block reverse_proxy:

app.example.com {
    reverse_proxy localhost:8080 {
        header_up X-Real-IP {remote_host}
        header_up Host {upstream_hostport}
    }
}

Nén và security header

Directive encode bật nén on-the-fly. Nếu bạn ghi encode gzip zstd, Caddy ưu tiên zstd cho client hỗ trợ và fallback về gzip. Thực tế nếu bỏ trống encode, Caddy đã bật sẵn zstd và gzip theo mặc định, nhưng khai rõ vẫn tốt cho tính minh bạch của cấu hình.

Directive header gắn security header vào response trả cho client. Một cấu hình hợp lý cho app đứng sau proxy:

app.example.com {
    encode gzip zstd

    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains"
        X-Content-Type-Options "nosniff"
        X-Frame-Options "SAMEORIGIN"
        Referrer-Policy "strict-origin-when-cross-origin"
        -Server
    }

    reverse_proxy localhost:8080
}

Dòng -Server gỡ header Server để lộ ít thông tin hạ tầng hơn. HSTS chỉ nên bật khi bạn chắc site sẽ luôn chạy HTTPS, vì trình duyệt sẽ ghi nhớ và từ chối HTTP trong suốt max-age.

Nhiều site trong một Caddyfile

Mỗi site là một block riêng, đặt liền nhau trong cùng file. Caddy quản lý cert độc lập cho từng domain. Ví dụ một VPS phục vụ một app, một API và một static site:

app.example.com {
    encode gzip zstd
    reverse_proxy localhost:8080
}

api.example.com {
    encode gzip zstd
    reverse_proxy localhost:3000
}

static.example.com {
    encode gzip zstd
    root * /var/www/static
    file_server
}

Để tránh lặp lại cấu hình chung, dùng snippet khai một lần rồi import vào từng site:

(common) {
    encode gzip zstd
    header -Server
}

app.example.com {
    import common
    reverse_proxy localhost:8080
}

api.example.com {
    import common
    reverse_proxy localhost:3000
}

Bạn cũng có thể gộp nhiều domain vào chung một block bằng cách liệt kê cách nhau bởi dấu phẩy, ví dụ example.com, www.example.com dùng chung một cấu hình.

Validate và reload không downtime

Luôn kiểm tra cú pháp trước khi áp dụng. Caddy dùng một adapter để dịch Caddyfile sang cấu hình JSON nội bộ, và validate sẽ báo lỗi trước khi chúng gây sập service:

sudo caddy validate --config /etc/caddy/Caddyfile

Reload nạp cấu hình mới mà không rớt kết nối đang chạy, nhờ Caddy áp dụng config theo kiểu graceful:

sudo systemctl reload caddy

Xem log để chắc cert đã được cấp thành công và không có block nào lỗi:

journalctl -u caddy --no-pager -n 50

Lần đầu chạy với domain mới, để ý dòng log báo Caddy đã lấy được certificate. Nếu thấy lỗi ACME, gần như luôn là do DNS chưa trỏ đúng hoặc port 80/443 bị chặn, hãy soát lại hai điều kiện ở phần đầu.

Caddy so với Nginx: chọn cái nào

Cả hai đều là reverse proxy tốt. Khác biệt nằm ở chi phí vận hành:

  • TLS. Caddy tự xin và renew cert, không cần Certbot hay cron. Với Nginx bạn phải cài Certbot, cấu hình renew và tự lo khi renew fail. Đây là lý do lớn nhất người ta chuyển sang Caddy.
  • Độ dài cấu hình. Một reverse proxy có HTTPS trong Caddy là ba dòng. Tương đương trong Nginx là vài chục dòng gồm hai server block (80 và 443), đường dẫn cert, các proxy_set_header và block redirect.
  • Hệ sinh thái và tinh chỉnh. Nginx có kho tài liệu, module và ví dụ khổng lồ tích lũy hàng chục năm, cùng khả năng tinh chỉnh sâu cho các tình huống hiệu năng cực đoan. Caddy trẻ hơn nhưng tài liệu chính thức rất mạch lạc.
  • Hiệu năng. Ở tải rất cao phục vụ file tĩnh, Nginx thường nhỉnh hơn đôi chút. Ở vai trò proxy trước app backend, khác biệt này không phải yếu tố quyết định.

Nếu bạn đang quản lý nhiều domain và mệt vì việc gia hạn cert, Caddy tiết kiệm thời gian rõ rệt. Nếu bạn đã có hạ tầng Nginx ổn định và cần tinh chỉnh sâu, không có lý do gấp để đổi.

Tóm lược

Caddy biến việc đưa một service ra Internet với HTTPS thành ba dòng cấu hình: khai domain, viết reverse_proxy, reload. Cert Let's Encrypt được xin và gia hạn tự động miễn là port 80/443 mở và domain trỏ đúng về IPv4 của VPS. Thêm encode gzip zstd cho nén, block header cho security header, và snippet import khi phục vụ nhiều site. So với Nginx cộng Certbot, Caddy cắt bỏ gần hết phần vận hành TLS thủ công, đổi lại một hệ sinh thái trẻ hơn. Tham khảo cú pháp đầy đủ và các directive nâng cao tại caddyserver.com/docs.