Cách cấu hình SSL miễn phí khi deploy NodeJS bằng Docker trên VPS
Deploy app NodeJS lên VPS xong, chạy được qua HTTP chưa đủ. Trình duyệt báo “Not Secure” → mất uy tín. API không có HTTPS → cookie, token, session dễ bị nghe lén. SEO, webhook, OAuth callback, thanh toán, đăng nhập Google/GitHub… đều thích hoặc bắt buộc SSL.
Tin tốt: SSL miễn phí, setup không quá khó. Combo phổ biến, ổn định nhất: Docker + Nginx reverse proxy + Let’s Encrypt. Mô hình này tách biệt rõ: app NodeJS lo business logic, Nginx lo SSL và route, Certbot lo cấp/gia hạn chứng chỉ.
Bài này đi từ tư duy kiến trúc → cấu hình thực tế → auto renew → lỗi hay gặp. Mục tiêu: sau khi đọc, bạn có thể tự dựng HTTPS chuẩn trên VPS cho app NodeJS chạy trong Docker.
Kiến trúc nên dùng
Mô hình khuyến nghị:
– NodeJS container → chạy app nội bộ, ví dụ port 3000
– Nginx container hoặc Nginx trên host → public 80/443, reverse proxy vào NodeJS
– Certbot → xin SSL miễn phí từ Let’s Encrypt
– VPS → có domain trỏ đúng IP
Luồng:
– user -> https://yourdomain.com
– Nginx :443 nhận req
– Nginx dùng cert SSL
– Nginx proxy req vào node-app:3000
Lý do không nhúng SSL trực tiếp vào NodeJS:
– Khó quản lý cert → renew, mount, reload phức tạp hơn – Nginx tối ưu hơn → TLS, redirect, cache, gzip, rate limit – Dễ scale → nhiều app chung 1 reverse proxy
Điều kiện trước khi bắt đầu
Bạn cần:
– 1 VPS Ubuntu/Debian phổ biến
– Domain, ví dụ example.com
– DNS A record trỏ về IP VPS
– Docker + Docker Compose
– Port 80, 443 mở trên firewall/security group
Cài Docker nếu chưa có:
sudo apt update
sudo apt install -y docker.io docker-compose-plugin
sudo systemctl enable docker
sudo systemctl start dockerKiểm tra DNS:
ping example.comexample.com -> IP VPS đúng mới xin cert được.
Cấu trúc project đề xuất
Ví dụ:
project/
├─ app/
│ ├─ Dockerfile
│ ├─ package.json
│ └─ server.js
├─ nginx/
│ └─ default.conf
└─ docker-compose.ymlDocker hóa app NodeJS
File app/Dockerfile:
FROM node:20-alpine
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install --production
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
File app/server.js demo:
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Hello HTTPS from NodeJS + Docker + VPS');
});
app.listen(3000, () => {
console.log('App running on port 3000');
});
Điểm cần nhớ:
– App chỉ cần listen 3000
– Không cần mở public port app ra Internet
– Chỉ Nginx public 80/443
Cấu hình Nginx reverse proxy
File nginx/default.conf:
server {
listen 80;
server_name example.com www.example.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
location / {
proxy_pass http://node-app:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Ý nghĩa:
– Port 80 → phục vụ challenge cho Let’s Encrypt, sau đó redirect HTTPS
– Port 443 → dùng chứng chỉ SSL
– proxy_pass http://node-app:3000 → trỏ đến service Docker tên node-app
Cấu hình Docker Compose
File docker-compose.yml:
version: '3.8'
services:
node-app:
build: ./app
container_name: node-app
restart: always
expose:
- "3000"
nginx:
image: nginx:latest
container_name: nginx
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf
- ./certbot/www:/var/www/certbot
- ./certbot/conf:/etc/letsencrypt
depends_on:
- node-app
certbot:
image: certbot/certbot
container_name: certbot
volumes:
- ./certbot/www:/var/www/certbot
- ./certbot/conf:/etc/letsencrypt
Lưu ý quan trọng:
– ./certbot/conf → nơi lưu cert bền vững
– ./certbot/www → nơi ACME challenge dùng chung giữa Nginx và Certbot
Xin chứng chỉ SSL miễn phí từ Let’s Encrypt
Lần đầu, chưa có cert, nếu Nginx cố load file SSL chưa tồn tại → fail. Cách xử lý gọn:
Bước 1: Tạm chạy Nginx chỉ với HTTP
Tạm comment block server port 443 trong nginx/default.conf, giữ block 80.
Khởi động:
docker compose up -d nginx node-appBước 2: Chạy Certbot để xin cert
docker compose run --rm certbot certonly --webroot --webroot-path=/var/www/certbot -d example.com -d www.example.com --email [email protected] --agree-tos --no-eff-emailNếu thành công, cert nằm ở:
./certbot/conf/live/example.com/Bước 3: Bật lại cấu hình HTTPS
Khôi phục block 443 trong nginx/default.conf, rồi reload:
docker compose restart nginxTest:
– http://example.com → redirect https://example.com
– Trình duyệt hiện khóa bảo mật → ok
Tự động gia hạn chứng chỉ
Cert Let’s Encrypt hết hạn sau 90 ngày. Không auto renew → vài tháng sau site chết HTTPS.
Test renew:
docker compose run --rm certbot renew --webroot --webroot-path=/var/www/certbotNếu ok, thêm cron trên VPS:
crontab -eThêm dòng:
0 3 * cd /path/to/project && docker compose run --rm certbot renew --webroot --webroot-path=/var/www/certbot && docker compose restart nginxGiải thích:
– 3h sáng mỗi ngày chạy renew – Certbot chỉ gia hạn khi gần hết hạn – Sau renew → restart Nginx để load cert mới
Nếu muốn sạch hơn → dùng nginx -s reload thay restart, nhưng restart đủ dùng đa số case.
Tối ưu thêm cho production
Sau khi chạy ổn, nên bổ sung:
Bật bảo mật header cơ bản
Trong block 443:
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header Referrer-Policy strict-origin-when-cross-origin;Xử lý app Express phía sau proxy
Nếu dùng Express, thêm:
app.set('trust proxy', 1);Cần khi:
– dùng secure cookie
– đọc protocol thật qua X-Forwarded-Proto
– auth/session chạy sau reverse proxy
Không publish port app
Tránh kiểu:
ports:
- "3000:3000"Không cần. expose đủ. Public thẳng app → tăng bề mặt tấn công.
Lỗi hay gặp
Domain chưa trỏ đúng IP
certbot fail → thường do DNS sai. Kiểm tra:
nslookup example.comPort 80 bị chặn
Let’s Encrypt dùng HTTP challenge → port 80 phải truy cập được. Nếu firewall/UFW/cloud security group chặn → xin cert fail.
Mở port:
sudo ufw allow 80
sudo ufw allow 443Nginx báo không tìm thấy file cert
Nguyên nhân:
– chưa chạy certbot – sai domain trong path – mount volume sai
Kiểm tra:
ls ./certbot/conf/live/example.com/Redirect loop HTTP/HTTPS
Thường do app cũng tự ép HTTPS, trong khi Nginx đã làm việc đó. Fix:
– để Nginx quản redirect chính
– app tin proxy qua app.set('trust proxy', 1)
Kết luận
Cấu hình SSL miễn phí cho NodeJS bằng Docker trên VPS không khó nếu đi đúng kiến trúc:
– NodeJS → chạy app – Nginx → reverse proxy, terminate SSL – Certbot + Let’s Encrypt → cấp/gia hạn cert miễn phí
Lợi ích rất rõ:
– site chuyên nghiệp hơn – dữ liệu mã hóa an toàn hơn – dễ tích hợp dịch vụ ngoài – sẵn sàng production đúng chuẩn hơn
Nếu mới bắt đầu, hãy giữ setup đơn giản: 1 app NodeJS, 1 Nginx, 1 Certbot, 1 domain. Khi đã quen, bạn có thể mở rộng sang nhiều domain, nhiều app, wildcard cert, hoặc dùng giải pháp tự động như Traefik/Caddy. Nhưng nền tảng vẫn vậy: tách app khỏi SSL layer để dễ vận hành, dễ debug, dễ scale.
Nếu triển khai thực tế, lời khuyên cuối: test kỹ 3 thứ sau mỗi lần deploy:
– http -> https redirect đúng
– cert còn hạn
– app nhận đúng IP/protocol gốc qua proxy
Làm tốt 3 điểm đó → hệ thống HTTPS của bạn sẽ ổn định, an toàn, ít phải chữa cháy.
Bình luận (0)
Chưa có bình luận. Hãy là người đầu tiên!