# Cấu hình Nginx reverse proxy SSL trên VPS với Let's Encrypt

URL: https://cloudj.net/vi/blog/nginx-reverse-proxy-ssl-vps
Published: 2026-11-11 · Updated: 2026-11-11

> Hướng dẫn cấu hình Nginx reverse proxy SSL trên VPS Ubuntu hoặc Debian: trỏ DNS, viết server block, lấy chứng chỉ Let's Encrypt miễn phí và tự gia hạn.

Cấu hình Nginx reverse proxy SSL là cách phổ biến nhất để đưa một ứng dụng web trên VPS ra Internet. Ứng dụng của bạn chỉ lắng nghe ở một cổng nội bộ như 3000 hoặc 8000, còn Nginx nhận traffic công khai ở cổng 80 và 443, xử lý HTTPS bằng chứng chỉ Let's Encrypt miễn phí rồi chuyển request vào ứng dụng. Bài này đi trọn quy trình trên Ubuntu hoặc Debian: bản ghi DNS, server block, chứng chỉ, và bước kiểm tra gia hạn mà nhiều người chỉ nhớ ra khi chứng chỉ đã hết hạn.

## Cần chuẩn bị gì

- Một máy chủ Linux có địa chỉ IPv4 công khai. Bất kỳ image Ubuntu hoặc Debian nào trên [cloud server của Cloudj](https://cloudj.net/vi/cloud-servers) đều dùng được; cấu hình nhỏ nhất là đủ để làm proxy cho một ứng dụng nhỏ.
- Một tên miền hoặc subdomain bạn quản lý, ví dụ `app.example.com`.
- Ứng dụng đã chạy trên máy chủ và lắng nghe ở `127.0.0.1` (bài này dùng cổng 3000).
- Quyền SSH với một user có sudo. Nếu chưa bảo mật máy chủ, hãy làm theo [checklist bảo mật VPS mới](https://cloudj.net/vi/blog/secure-new-vps-checklist) trước.

Nếu chưa chọn vị trí đặt máy chủ, [hướng dẫn chọn vị trí VPS](https://cloudj.net/vi/blog/vps-locations-guide) giúp bạn chọn thành phố gần người dùng, và bài [chọn vị trí VPS theo độ trễ](https://cloudj.net/vi/blog/choose-vps-location-latency) chỉ cách đo thực tế. Với người dùng chủ yếu ở Việt Nam, hãy đo từ chính các nhà mạng mà khách của bạn dùng.

## Bước 1: trỏ DNS về máy chủ

Let's Encrypt kiểm tra xem tên miền có trỏ về đúng máy chủ đang xin chứng chỉ hay không, vì vậy DNS phải làm trước tiên. Tại nhà cung cấp DNS, tạo bản ghi `A` cho `app.example.com` với IPv4 công khai của máy chủ (xem trên trang máy chủ trong [console Cloudj](https://app.cloudj.net)). Nếu máy chủ có IPv6 và bạn muốn phục vụ qua IPv6 thì thêm bản ghi `AAAA`; nếu không thì đừng tạo, vì một bản ghi `AAAA` sai là nguyên nhân rất hay gặp khiến việc xác thực thất bại.

Kiểm tra bản ghi ngay trên máy chủ trước khi đi tiếp:

```bash
dig +short app.example.com
```

Trên Debian, lệnh `dig` nằm trong gói `bind9-dnsutils`. Kết quả phải là IP của máy chủ. Nếu trả về rỗng hoặc IP cũ, hãy chờ bản ghi cập nhật xong.

## Bước 2: cài Nginx và mở firewall

```bash
sudo apt update
sudo apt install -y nginx
sudo systemctl enable --now nginx
```

Nếu dùng UFW, hãy mở HTTP và HTTPS. Profile `Nginx Full` mở cả cổng 80 và 443:

```bash
sudo ufw allow 'Nginx Full'
sudo ufw status
```

Cổng 80 vẫn phải mở kể cả khi bạn đã chuyển hết sang HTTPS, vì certbot dùng HTTP challenge qua cổng này mỗi lần gia hạn. Bài [cấu hình firewall UFW](https://cloudj.net/vi/blog/ufw-firewall-vps) hướng dẫn phần còn lại của bộ rule.

## Bước 3: cấu hình Nginx reverse proxy (SSL ở bước sau)

Trước hết, thêm một đoạn cấu hình nhỏ để WebSocket hoạt động mà không ảnh hưởng tới request thông thường. Khối `map` này phải nằm trong context `http`, và các file trong `conf.d` được include đúng chỗ đó:

```bash
sudo tee /etc/nginx/conf.d/connection-upgrade.conf > /dev/null <<'EOF'
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}
EOF
```

Sau đó tạo file site `/etc/nginx/sites-available/app.example.com`:

```nginx
server {
    listen 80;
    listen [::]:80;
    server_name app.example.com;

    client_max_body_size 20m;

    location / {
        proxy_pass http://127.0.0.1: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;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_read_timeout 60s;
    }
}
```

Vài dòng cần giải thích thêm. `Host` giữ nguyên tên miền gốc để ứng dụng tạo link đúng. `X-Forwarded-Proto` cho ứng dụng biết khách truy cập bằng HTTPS hay HTTP; nhiều framework cần header này để không bị vòng lặp chuyển hướng. `client_max_body_size` nâng giới hạn upload mặc định 1 MB; hãy đặt theo đúng mức ứng dụng của bạn cho phép.

Bật site, kiểm tra cú pháp rồi reload:

```bash
sudo ln -s /etc/nginx/sites-available/app.example.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
```

Mở `http://app.example.com`, bạn sẽ thấy ứng dụng chạy qua HTTP thường. Nếu gặp lỗi 502 Bad Gateway nghĩa là Nginx không kết nối được tới ứng dụng: kiểm tra bằng `curl -I http://127.0.0.1:3000`.

## Bước 4: lấy chứng chỉ SSL miễn phí bằng certbot

Cài certbot và plugin Nginx từ kho gói của hệ điều hành:

```bash
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d app.example.com --redirect
```

Lần chạy đầu, certbot hỏi email và yêu cầu đồng ý điều khoản. Sau đó nó xác minh quyền sở hữu tên miền qua cổng 80, lưu chứng chỉ vào `/etc/letsencrypt/live/app.example.com/`, thêm khối `listen 443 ssl` vào file site, và với cờ `--redirect` sẽ chuyển toàn bộ HTTP sang HTTPS. Nginx được reload tự động.

Muốn dùng cả tên miền gốc lẫn `www`, truyền hai cờ `-d`: `-d example.com -d www.example.com`. Cả hai tên đều phải có bản ghi DNS trỏ về máy chủ.

## Bước 5: kiểm tra tự gia hạn

Chứng chỉ Let's Encrypt có thời hạn ngắn, nên việc gia hạn bắt buộc phải tự động. Gói certbot trên Debian và Ubuntu cài sẵn một systemd timer chạy kiểm tra gia hạn hai lần mỗi ngày. Hãy xác nhận timer và chạy thử:

```bash
systemctl list-timers | grep certbot
sudo certbot renew --dry-run
```

Nếu lệnh chạy thử thành công thì lần gia hạn thật cũng sẽ thành công, miễn là DNS và cổng 80 không thay đổi. Với chứng chỉ cấp qua plugin Nginx, certbot tự reload Nginx sau khi gia hạn.

## Gia cố thêm sau khi chạy ổn

- **Giữ ứng dụng ở chế độ nội bộ.** Cho ứng dụng lắng nghe ở `127.0.0.1` thay vì `0.0.0.0`, để đường vào công khai duy nhất là qua Nginx. Nếu dùng Docker, publish cổng dạng `127.0.0.1:3000:3000`; xem thêm bài [cài Docker trên VPS](https://cloudj.net/vi/blog/install-docker-vps).
- **HSTS.** Khi HTTPS đã chạy ổn ở mọi nơi, thêm `add_header Strict-Transport-Security "max-age=31536000" always;` vào server block cổng 443. Đừng thêm sớm, vì trình duyệt sẽ ghi nhớ thiết lập này.
- **Ẩn phiên bản Nginx.** Đặt `server_tokens off;` trong `/etc/nginx/nginx.conf`.
- **Nhiều ứng dụng trên một máy chủ.** Lặp lại bước 3 và 4 với file site và tên miền mới cho mỗi ứng dụng. Nginx chọn block dựa trên `server_name`.

[Tài liệu chính thức về module proxy của Nginx](https://nginx.org/en/docs/http/ngx_http_proxy_module.html) liệt kê đầy đủ các directive ở trên nếu bạn cần tinh chỉnh buffer hay timeout.

## Xử lý lỗi thường gặp

| Hiện tượng | Nguyên nhân thường gặp | Cách kiểm tra |
|---|---|---|
| certbot báo challenge thất bại | DNS trỏ sai hoặc cổng 80 bị chặn | `dig +short`, `sudo ufw status` |
| 502 Bad Gateway | Ứng dụng chưa chạy hoặc chạy ở cổng khác | `curl -I http://127.0.0.1:3000` |
| Vòng lặp chuyển hướng sau khi bật HTTPS | Ứng dụng bỏ qua `X-Forwarded-Proto` | Bật thiết lập trusted proxy của framework |
| `nginx -t` báo lỗi | Gõ sai hoặc thiếu dấu chấm phẩy | Đọc số dòng trong thông báo lỗi |
| Chạy thử gia hạn thất bại | DNS hoặc firewall đã thay đổi | Kiểm tra lại DNS và cổng như ở bước 1 và 2 |

## Câu hỏi thường gặp

### Mỗi tên miền có cần một IP riêng không?

Không. Nginx và trình duyệt hiện đại đều hỗ trợ SNI, nên nhiều website HTTPS có thể dùng chung một IPv4. Mỗi tên miền chỉ cần server block và chứng chỉ riêng.

### Có lấy được chứng chỉ wildcard theo cách này không?

Không với HTTP challenge. Chứng chỉ wildcard như `*.example.com` cần DNS challenge, tức là phải dùng plugin DNS của certbot tương ứng với nhà cung cấp DNS của bạn.

### Có nên cài certbot bằng snap thay vì apt?

Cách nào cũng được. Gói apt đơn giản hơn trên Ubuntu và Debian và có sẵn timer gia hạn. Dự án certbot khuyến nghị snap để có bản mới nhất; chỉ cần chọn một cách và đừng cài cả hai.

### Vị trí VPS có ảnh hưởng tới SSL không?

Không ảnh hưởng tới chứng chỉ. Vị trí chỉ quyết định độ trễ tới người truy cập, bạn có thể đo theo bài [kiểm tra độ trễ VPS bằng ping và mtr](https://cloudj.net/vi/blog/test-vps-latency-ping-mtr).

## Triển khai trên máy chủ của bạn

Khi DNS đã cập nhật, dựng reverse proxy kèm SSL chỉ mất khoảng mười lăm phút, và các bước giống hệt nhau ở mọi vị trí của Cloudj. Bạn có thể tạo máy chủ Ubuntu hoặc Debian trong [console Cloudj](https://app.cloudj.net), nạp số dư bằng chuyển khoản VND, và xem giá trực tiếp cho từng cấu hình trên [trang bảng giá](https://cloudj.net/vi/pricing).
