Vấn đề thực tế: Quản lý nhiều dịch vụ trên một VPS
Nếu bạn đang chạy nhiều ứng dụng trên cùng một VPS — ví dụ Nextcloud, Gitea, một API backend và một trang WordPress — bạn sẽ nhanh chóng gặp bài toán cổ điển: tất cả đều muốn chạy trên cổng 80 và 443, nhưng chỉ một tiến trình được phép lắng nghe trên mỗi cổng.
Giải pháp truyền thống là cấu hình Nginx thủ công làm reverse proxy: mỗi khi thêm dịch vụ mới, bạn phải tạo một file config mới, xin SSL certificate từ Let’s Encrypt, reload Nginx. Không phức tạp về kỹ thuật, nhưng lặp đi lặp lại và dễ mắc lỗi — nhất là khi quản lý 5–10 subdomain cùng lúc.
Ba cách tiếp cận phổ biến — So sánh thẳng thắn
1. Nginx thuần + Certbot thủ công
Ưu điểm: Nhẹ, toàn quyền kiểm soát, không phụ thuộc thêm công cụ nào.
Nhược điểm: Mỗi lần thêm domain phải chạy certbot, sửa file /etc/nginx/sites-available/, rồi nginx -t && systemctl reload nginx. Với 10 subdomain, điều này trở thành gánh nặng bảo trì.
2. Traefik
Ưu điểm: Tích hợp tốt với Docker, tự động phát hiện container qua label, hỗ trợ Let’s Encrypt.
Nhược điểm: Cấu hình qua file YAML và label phức tạp, đường cong học tập dốc hơn. Không có Web UI trực quan để xem trạng thái routing.
3. Nginx Proxy Manager (NPM)
Ưu điểm: Giao diện Web đẹp, thêm proxy host chỉ cần điền form, SSL Let’s Encrypt tự động một click, hỗ trợ access list, redirect, stream proxy.
Nhược điểm: Nặng hơn một chút vì có database đi kèm (SQLite hoặc MariaDB), cần hiểu cơ bản về reverse proxy để dùng đúng.
Khi nào chọn Nginx Proxy Manager?
NPM phù hợp nhất khi:
- Bạn quản lý VPS cá nhân hoặc team nhỏ, ưu tiên tốc độ triển khai hơn tối ưu tài nguyên
- Đội nhóm có người không quen sửa file config Nginx tay
- Cần thêm/xóa subdomain thường xuyên mà không muốn SSH vào server mỗi lần
- Muốn có dashboard xem nhanh trạng thái tất cả proxy host
Lần đầu dùng Docker Compose cho dự án thực tế, mình mắc khá nhiều lỗi cơ bản mà bây giờ nghĩ lại thấy buồn cười — trong đó có việc cố cấu hình Nginx thủ công bên ngoài Docker trong khi các container chạy bên trong network riêng. Phát hiện ra NPM chạy ngay trong Docker network giải quyết sạch vấn đề đó.
Triển khai Nginx Proxy Manager với Docker Compose
Yêu cầu trước khi bắt đầu
- VPS chạy Linux (Ubuntu 22.04 hoặc Debian 12 khuyến nghị)
- Docker và Docker Compose đã cài đặt
- Domain đã trỏ A record về IP của VPS
- Cổng 80, 443, 81 đã được mở trên firewall
Bước 1: Tạo thư mục và file docker-compose.yml
mkdir -p ~/npm && cd ~/npm
Tạo file docker-compose.yml:
version: '3.8'
services:
npm:
image: 'jc21/nginx-proxy-manager:latest'
container_name: nginx-proxy-manager
restart: unless-stopped
ports:
- '80:80' # HTTP
- '443:443' # HTTPS
- '81:81' # Web UI
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
networks:
default:
name: npm_network
driver: bridge
Cấu trúc trên dùng SQLite mặc định — đủ dùng cho VPS cá nhân. Nếu cần scale hoặc backup dễ hơn, bạn có thể thêm MariaDB vào stack.
Bước 2: Khởi động NPM
docker compose up -d
# Kiểm tra log
docker compose logs -f npm
Sau khoảng 30 giây, truy cập http://<IP-VPS>:81 để vào giao diện quản lý.
Thông tin đăng nhập mặc định:
- Email:
[email protected] - Password:
changeme
Đổi ngay sau lần đăng nhập đầu tiên.
Bước 3: Thêm Proxy Host đầu tiên
Giả sử bạn có một ứng dụng đang chạy trên container tên myapp, lắng nghe cổng 3000 trong Docker network npm_network.
- Vào Proxy Hosts → Add Proxy Host
- Domain Names: nhập
app.yourdomain.com - Scheme:
http - Forward Hostname/IP: tên container (
myapp) hoặc IP nội bộ - Forward Port:
3000 - Bật Block Common Exploits và Websockets Support nếu cần
- Tab SSL → chọn Request a new SSL Certificate → bật Force SSL và HTTP/2 Support
- Nhập email Let’s Encrypt → tick đồng ý → Save
NPM sẽ tự động lấy certificate từ Let’s Encrypt và cấu hình HTTPS. Toàn bộ quá trình mất dưới 30 giây.
Bước 4: Kết nối ứng dụng Docker vào cùng network
Để NPM có thể forward request đến các container khác, chúng phải ở cùng Docker network. Cách đơn giản nhất là khai báo external network trong docker-compose.yml của ứng dụng:
services:
myapp:
image: myapp:latest
container_name: myapp
# Không cần expose port ra host
networks:
- npm_network
networks:
npm_network:
external: true
Với cách này, myapp không cần publish port ra ngoài host — chỉ NPM mới nhận traffic từ internet, các container còn lại hoàn toàn isolated.
Một số tình huống thực tế hay gặp
Redirect HTTP sang HTTPS cho toàn bộ subdomain
Trong phần SSL của mỗi Proxy Host, bật Force SSL là đủ. NPM tự xử lý redirect 301.
Thêm Basic Authentication cho một subdomain nội bộ
Vào tab Access Lists → tạo list mới với username/password → gán vào Proxy Host muốn bảo vệ. Hữu ích cho Grafana, Kibana hoặc các tool internal không có auth riêng.
Gia hạn SSL tự động
NPM tự động gia hạn certificate qua cronjob nội bộ. Bạn không cần làm gì thêm — khác với setup Certbot thủ công phải kiểm tra systemctl status certbot.timer.
Backup và restore
Toàn bộ dữ liệu (config, certificate, database) nằm trong thư mục ./data và ./letsencrypt mà bạn đã mount. Backup đơn giản:
tar -czf npm-backup-$(date +%Y%m%d).tar.gz ~/npm/data ~/npm/letsencrypt
Những điều cần lưu ý
- Cổng 81 chỉ nên mở tạm thời hoặc giới hạn theo IP. Sau khi cấu hình xong, đóng cổng 81 trên firewall và chỉ mở khi cần quản trị.
- Let’s Encrypt rate limit: Tối đa 5 certificate mới cho cùng một domain trong 7 ngày. Đừng thử đi thử lại nhiều lần khi test — dùng staging environment trước.
- Wildcard SSL: NPM hỗ trợ wildcard certificate (
*.yourdomain.com) nhưng cần xác minh qua DNS challenge, yêu cầu cấu hình thêm DNS provider (Cloudflare, AWS Route53…). - Version pinning: Thay
jc21/nginx-proxy-manager:latestbằng tag cụ thể trong môi trường production để tránh breaking change khi update.

