Vũ Văn HảiFull-stack · AI-native
Hướng dẫnBlog
Trao đổi dự án

© 2026 Vũ Văn Hải · Viết từ kinh nghiệm triển khai thật.

Trang chủHướng dẫnBlogRSS
  1. Hướng dẫn
  2. /VPS
  3. /Cài Caddy làm reverse proxy trên VPS bằng Docker

Cài Caddy làm reverse proxy trên VPS bằng Docker

Dựng Caddy làm reverse proxy bằng Docker trên VPS mới: tạo network dùng chung, viết Caddyfile và docker-compose, tự động có HTTPS, rồi thêm domain mới chỉ bằng một lệnh reload không downtime.

Cập nhật: 21 thg 9, 20266 phút đọc
CaddyDockerVPSMạng
Mục lục
  • Vì sao chọn Caddy
  • 1. Cài đặt Caddy lần đầu
  • Tạo thư mục chứa cấu hình Caddy
  • Tạo Docker network dùng chung
  • Tạo file cấu hình Caddyfile
  • Tạo docker-compose.yml cho Caddy
  • Khởi động Caddy
  • 2. Thêm một ứng dụng / domain mới
  • Bước 1: Nối container mới vào network của Caddy
  • Bước 2: Khai báo domain trong Caddyfile
  • Bước 3: Reload Caddy (zero downtime)
  • Quy tắc quan trọng: không dùng localhost trong reverse_proxy
  • Lưu ý

Caddy là reverse proxy nổi bật ở chỗ tự động cấp phát và gia hạn chứng chỉ SSL (HTTPS) mà gần như không cần cấu hình gì (zero-config SSL). Bài này dựng Caddy bằng Docker trên một VPS mới, sau đó đưa ra quy trình 3 bước để thêm một ứng dụng/domain mới mà không gây downtime.

Trong toàn bài, thay example.com, app.example.com, api.example.com bằng domain của bạn, và các tên container (my-new-app, my-nextjs-container, my-backend-api) bằng tên container thực tế.

Vì sao chọn Caddy

  • Tự động lấy chứng chỉ SSL từ Let's Encrypt / ZeroSSL.
  • File cấu hình cực kỳ ngắn gọn và dễ hiểu.
  • Hỗ trợ reload cấu hình không làm gián đoạn dịch vụ (zero-downtime).

1. Cài đặt Caddy lần đầu

Tạo thư mục chứa cấu hình Caddy

Gom các file cấu hình vào cùng một thư mục để sau này dễ quản lý, chỉnh sửa và sao lưu.

mkdir -p ~/services/caddy
cd ~/services/caddy

Tạo Docker network dùng chung

Để Caddy "nhìn thấy" và chuyển tiếp (proxy) traffic đến các container khác, tất cả phải nằm trên cùng một network. Tạo một network tên caddy-net (hoặc giữ tên nginx-proxy nếu bạn đã dùng từ trước và muốn tương thích ngược):

docker network create caddy-net

Tạo file cấu hình Caddyfile

Caddyfile là nơi bạn khai báo các domain và dịch vụ tương ứng.

cat > ~/services/caddy/Caddyfile << 'EOF'
# =============================================
# Caddy Reverse Proxy Configuration
# =============================================
# Chỉ cần khai báo domain, Caddy sẽ tự lo phần SSL!

# Ví dụ 1: Proxy đến một Next.js app
# app.example.com {
#     reverse_proxy my-nextjs-container:3000
# }

# Ví dụ 2: Proxy đến một API backend
# api.example.com {
#     reverse_proxy my-backend-api:8080
# }
EOF

Khi dùng thật, thay example.com và tên container bằng domain và ứng dụng của bạn.

Tạo docker-compose.yml cho Caddy

File này định nghĩa container Caddy, các cổng (ports) cần mở và các thư mục cần lưu trữ (volumes).

Volume caddy_data rất quan trọng: nó giữ chứng chỉ SSL qua mỗi lần khởi động lại. Mất volume này, Caddy phải xin lại chứng chỉ và dễ dính rate limit của Let's Encrypt.

cat > ~/services/caddy/docker-compose.yml << 'EOF'
services:
  caddy:
    image: caddy:2-alpine
    container_name: caddy-proxy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp" # Bắt buộc mở UDP port 443 nếu muốn hỗ trợ HTTP/3
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
      - caddy_config:/config
    networks:
      - caddy-net

volumes:
  caddy_data:
  caddy_config:

networks:
  caddy-net:
    external: true
EOF

Khởi động Caddy

Trước khi bật Caddy, hãy chắc chắn VPS không có dịch vụ nào khác đang chiếm cổng 80 và 443 (ví dụ Nginx, Apache hay một proxy cũ).

Nếu đang chạy Nginx Proxy Manager, tắt nó để giải phóng port:

docker stop nginx-proxy-manager-app-1

Khởi động Caddy:

cd ~/services/caddy
docker compose up -d

Để kiểm tra Caddy đã chạy ổn và đã xin được SSL thành công chưa, xem logs:

docker compose logs -f

Khi thấy log báo certificate obtained successfully thì bấm Ctrl+C để thoát.

2. Thêm một ứng dụng / domain mới

Mỗi khi có một ứng dụng mới (ví dụ một dịch vụ Docker chạy trên port 3000), bạn chỉ cần làm đúng 3 bước sau.

Bước 1: Nối container mới vào network của Caddy

Vì cả Caddy và ứng dụng đều chạy trong Docker, cách thiết lập đúng nhất (best practice) là cho ứng dụng tự động join vào network của Caddy (caddy-net) ngay trong file docker-compose.yml.

Cách 1 (khuyến nghị): khai báo trong docker-compose

Thêm đoạn cấu hình networks này vào file docker-compose.yml của ứng dụng mới:

services:
  my-new-app:
    # ... (các cấu hình image, ports...)
    networks:
      - caddy-net

networks:
  caddy-net:
    external: true

Cờ external: true báo cho Docker biết network caddy-net đã tồn tại sẵn bên ngoài (đã tạo ở phần 1). Khi chạy docker compose up -d, container sẽ tự động gia nhập network này. Nhờ vậy ứng dụng mang sang VPS nào cũng tự chạy đúng mà không cần gõ lệnh thủ công.

Cách 2 (tạm thời): gắn network thủ công bằng lệnh

Nếu ứng dụng đã chạy và bạn chưa thể sửa docker-compose ngay, chạy:

docker network connect caddy-net my-new-app

Cách này chỉ là tạm thời: container sẽ mất kết nối tới network nếu bị tạo lại hoặc rebuild.

Bước 2: Khai báo domain trong Caddyfile

Mở Caddyfile:

nano ~/services/caddy/Caddyfile

Thêm khối cấu hình mới xuống cuối file:

app.example.com {
    reverse_proxy my-new-app:3000
}

Bước 3: Reload Caddy (zero downtime)

Caddy hỗ trợ reload file cấu hình mà không cần khởi động lại (restart) container. Người dùng đang truy cập các site khác đi qua cùng Caddy sẽ không hề hay biết và không bị rớt kết nối.

docker exec caddy-proxy caddy reload --config /etc/caddy/Caddyfile

Xong. Khi chạy lệnh này, Caddy tự nhận diện domain mới app.example.com, tự đi xin chứng chỉ SSL từ Let's Encrypt và bắt đầu chuyển tiếp request ngay lập tức.

Quy tắc quan trọng: không dùng localhost trong reverse_proxy

Caddy đang chạy trong một container Docker riêng, nên localhost trong Caddyfile là chính container Caddy, KHÔNG phải VPS của bạn.

  • KHÔNG dùng reverse_proxy localhost:3000. Lúc này localhost không phải là máy VPS mà là bản thân container chạy Caddy. Caddy sẽ tìm dịch vụ ở port 3000 bên trong chính container Caddy, dẫn đến lỗi 502 Bad Gateway.
  • Cách dùng đúng: reverse_proxy <tên-container>:3000. Khi Caddy và app đã chung network caddy-net, Docker có sẵn DNS nội bộ. Bạn chỉ cần dùng đúng container_name khai báo trong docker-compose.yml của app làm hostname trỏ tới, ví dụ reverse_proxy my-new-app:3000.

Lưu ý

Triệu chứngCách xử lý
502 Bad Gateway dù app đang chạyCaddyfile đang trỏ localhost, hoặc app chưa join caddy-net - dùng reverse_proxy <tên-container>:<port> và kiểm tra lại network
Caddy không khởi động được vì port 80/443 đã bị chiếmTắt Nginx, Apache hoặc proxy cũ (ví dụ docker stop nginx-proxy-manager-app-1) rồi chạy lại docker compose up -d
App mất kết nối với Caddy sau khi rebuildNetwork được gắn bằng docker network connect - chuyển sang khai báo networks trong docker-compose.yml của app
Bị rate limit khi xin chứng chỉ SSLĐừng xóa volume caddy_data; nó giữ chứng chỉ qua các lần khởi động lại

Muốn người dùng thấy một trang bảo trì tử tế thay vì lỗi 502 mỗi lần deploy? Xem tiếp Trang bảo trì tự động với Caddy.

Bài trướcCấu hình VPS mới: các bước cơ bản an toànBài tiếp theoTrang bảo trì tự động với Caddy khi deploy Docker

Bài liên quan

  • Sửa lỗi Docker container không kết nối được PostgreSQL trên VPS

    Docker đổi dải IP mạng mỗi lần docker-compose down rồi up, nên PostgreSQL trên host từ chối container. Cách chẩn đoán subnet, mở UFW và pg_hba.conf, rồi cho phép cả dải 172.16.0.0/12 để sửa một lần là xong.

    Database

    Database
  • Đặt app ở đâu trên VPS? Cấu trúc thư mục chuẩn /opt/apps

    Quy ước đặt mỗi app trong một thư mục con của /opt/apps trên VPS: chown thư mục cha một lần duy nhất, từ đó git clone, git pull và sửa .env đều không cần sudo.

    VPS

    VPS
  • Kiểm tra nhanh thông số VPS bằng một dòng lệnh

    Script bash chạy bằng một dòng lệnh để kiểm tra OS, CPU, RAM, disk và mạng của VPS trong khoảng 15 giây, kèm bảng tham chiếu giúp bạn biết thông số nào là yếu, ổn hay mạnh.

    VPS

    VPS

Viết bởi Vũ Văn Hải

Tôi là Hải, full-stack developer ở TP. Hồ Chí Minh. Các bài ở đây đúc kết từ những hệ thống tôi tự dựng và vận hành. Cần dựng hoặc gỡ rối một hệ thống tương tự? Cứ nhắn tôi.

Trao đổi dự ánXem thêm hướng dẫn

Thấy sai sót hoặc lệnh không còn chạy? Báo cho tôi

Mục lục

  • Vì sao chọn Caddy
  • 1. Cài đặt Caddy lần đầu
  • Tạo thư mục chứa cấu hình Caddy
  • Tạo Docker network dùng chung
  • Tạo file cấu hình Caddyfile
  • Tạo docker-compose.yml cho Caddy
  • Khởi động Caddy
  • 2. Thêm một ứng dụng / domain mới
  • Bước 1: Nối container mới vào network của Caddy
  • Bước 2: Khai báo domain trong Caddyfile
  • Bước 3: Reload Caddy (zero downtime)
  • Quy tắc quan trọng: không dùng localhost trong reverse_proxy
  • Lưu ý