Cách Cấu Hình SSL Miễn Phí Cho NodeJS Docker Trên VPS Chuẩn Nhất

07/05/2026 · P T P · Chung

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 docker

Kiểm tra DNS:

ping example.com

example.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.yml

Docker 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-app

Bướ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-email

Nế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 nginx

Test:

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/certbot

Nếu ok, thêm cron trên VPS:

crontab -e

Thêm dòng:

0 3   * cd /path/to/project && docker compose run --rm certbot renew --webroot --webroot-path=/var/www/certbot && docker compose restart nginx

Giả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.com

Port 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 443

Nginx 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.

#docker #hinh #mien #nodejs #tren
Chia sẻ:
← Trước
Deploy NodeJS bằng Docker trên VPS: Lỗi Hay Gặp, Cách Gỡ Nhanh

Bài viết tương tự

Bình luận

Chưa có bình luận. Hãy là người đầu tiên!