# Deploy Node.js lên VPS: hướng dẫn từ A-Z với nginx, SSL và systemd

> Cách deploy Node.js lên VPS Ubuntu/Debian: cài Node LTS, chạy bằng systemd, nginx reverse proxy, SSL miễn phí và cập nhật app không gián đoạn.

Source: https://zopanel.net/vi/blog/deploy-nodejs-vps  
Updated: 2026-10-21

## Ý chính

- Ứng dụng Node.js nên lắng nghe trên 127.0.0.1 và để nginx lo tên miền, SSL và file tĩnh.
- systemd đủ để giữ app luôn chạy và tự khởi động lại, không bắt buộc phải có PM2.
- Gói nodejs có sẵn trong Ubuntu 24.04 và Debian 12 là Node 18, đã hết hỗ trợ; hãy dùng bản LTS 22 hoặc 24.
- Muốn cập nhật không gián đoạn, bản mới phải khởi động và qua health check trước khi nhận traffic.

Để deploy Node.js lên VPS, bạn cần bốn thứ: một bản Node.js LTS còn được hỗ trợ, một trình quản lý tiến trình giữ app luôn chạy (systemd hoặc PM2), nginx làm reverse proxy trước app, và chứng chỉ SSL cho tên miền. App chỉ lắng nghe trên `127.0.0.1`, còn nginx nhận request từ internet ở cổng 80/443 rồi chuyển vào. Làm tay mất khoảng 30-60 phút cho lần đầu; với control panel có hỗ trợ ứng dụng Node.js thì chỉ vài phút.

Bài này đi qua cả hai cách: làm thủ công trên Ubuntu 24.04 hoặc Debian 12 (từng lệnh, kèm lý do), và cách làm với ZoPanel. Cuối bài là các lỗi hay gặp, cách cập nhật không gián đoạn và phần câu hỏi thường gặp.

## Cần chuẩn bị gì trước khi deploy Node.js lên VPS?

- **Một VPS Ubuntu 24.04 hoặc Debian 12/13** có quyền root hoặc sudo. Với một app Express hay NestJS nhỏ, 1 GB RAM là chạy được; nếu app cần build Next.js ngay trên server thì nên có 2 GB trở lên, vì bước build thường tốn RAM hơn lúc chạy.
- **Một tên miền** đã trỏ bản ghi A về IP của VPS. Nếu chưa làm, xem bài [trỏ tên miền về VPS](/vi/blog/point-domain-to-vps).
- **Mã nguồn** trên GitHub/GitLab hoặc trên máy bạn, có lệnh `start` chạy được ở môi trường production.
- **App đọc cổng từ biến môi trường** `PORT` thay vì viết cứng. Đây là thói quen nhỏ nhưng giúp mọi cách deploy đều dễ hơn.

Nếu VPS mới tinh, hãy làm phần bảo mật cơ bản trước (SSH key, tường lửa, cập nhật tự động). Bài [bảo mật VPS Ubuntu](/vi/blog/secure-ubuntu-vps) có danh sách đầy đủ.

## Nên dùng phiên bản Node.js nào?

Hãy dùng bản **LTS** đang được hỗ trợ. Theo [lịch phát hành của Node.js](https://nodejs.org/en/about/previous-releases), tại thời điểm viết (tháng 10/2026) Node.js 24 và 22 là LTS, còn Node.js 20 và 18 đã hết hỗ trợ (EOL) và không còn nhận bản vá bảo mật.

Một cái bẫy hay gặp: gói `nodejs` có sẵn trong kho của Ubuntu 24.04 là **Node 18.19**, của Debian 12 là **Node 18.20**. Chạy `apt install nodejs` cho bạn một phiên bản đã hết hỗ trợ. Vì thế bài này cài Node.js từ bản build chính thức trên nodejs.org.

## Cách deploy Node.js lên VPS thủ công

### Bước 1: Cài Node.js LTS từ bản build chính thức

Lệnh dưới đây tải bản Node.js 24 mới nhất, **kiểm tra checksum SHA-256** trước khi giải nén, rồi đặt vào `/opt/node`. Trên server ARM64, đổi `linux-x64` thành `linux-arm64`.

```bash
cd /tmp
curl -fsSLO https://nodejs.org/dist/latest-v24.x/SHASUMS256.txt
FILE=$(grep -o 'node-v24\.[0-9.]*-linux-x64\.tar\.xz' SHASUMS256.txt | head -n1)
curl -fsSLO "https://nodejs.org/dist/latest-v24.x/$FILE"
grep " $FILE\$" SHASUMS256.txt | sha256sum -c -
sudo mkdir -p /opt/node
sudo tar -xJf "$FILE" -C /opt/node --strip-components=1
sudo ln -sf /opt/node/bin/node /usr/local/bin/node
sudo ln -sf /opt/node/bin/npm /usr/local/bin/npm
sudo ln -sf /opt/node/bin/npx /usr/local/bin/npx
node -v && npm -v
```

Nếu dòng `sha256sum -c` không in ra `OK`, đừng giải nén: file đã tải không đúng. Khi có bản vá mới, chạy lại đoạn trên rồi khởi động lại app. Nếu thích quản lý bằng `apt`, kho của NodeSource là lựa chọn phổ biến; hãy làm theo hướng dẫn thêm kho thủ công của họ thay vì chạy script tải từ internet mà chưa đọc.

### Bước 2: Tạo user riêng và đưa mã nguồn lên

Đừng chạy app bằng root. Tạo một user hệ thống riêng, không có quyền đăng nhập:

```bash
sudo adduser --system --group --home /srv/myapp myapp
sudo apt install -y git
sudo -u myapp git clone https://github.com/ban/myapp.git /srv/myapp/app
cd /srv/myapp/app
sudo -u myapp npm ci --omit=dev
# Nếu app cần build (Next.js, NestJS, TypeScript):
# sudo -u myapp npm ci && sudo -u myapp npm run build
```

`npm ci` cài đúng phiên bản trong `package-lock.json`, nên kết quả giống hệt máy bạn. Repository riêng tư thì dùng deploy key chỉ đọc thay vì mật khẩu GitHub.

### Bước 3: Chạy app bằng systemd

systemd có sẵn trên mọi bản Ubuntu/Debian, khởi động app cùng server, tự khởi động lại khi app chết và gom log vào `journalctl`. Đặt biến môi trường bí mật trong một file riêng chỉ root đọc được:

```bash
sudo tee /etc/myapp.env >/dev/null <<'EOF'
NODE_ENV=production
PORT=3000
DATABASE_URL=mysql://myapp:matkhau@127.0.0.1/myapp
EOF
sudo chmod 600 /etc/myapp.env
```

Tạo file `/etc/systemd/system/myapp.service`:

```ini
[Unit]
Description=My Node.js app
After=network.target

[Service]
User=myapp
Group=myapp
WorkingDirectory=/srv/myapp/app
EnvironmentFile=/etc/myapp.env
ExecStart=/usr/local/bin/node server.js
Restart=on-failure
RestartSec=5
# Hạn chế quyền: app không ghi được ra ngoài thư mục được phép
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/srv/myapp
MemoryMax=768M

[Install]
WantedBy=multi-user.target
```

Đổi `server.js` thành file khởi động của bạn (hoặc `/usr/local/bin/npm run start`). Rồi bật dịch vụ:

```bash
sudo systemctl daemon-reload
sudo systemctl enable --now myapp
systemctl status myapp
journalctl -u myapp -f        # xem log trực tiếp
curl -I http://127.0.0.1:3000 # app đã trả lời chưa?
```

Trong code, nhớ lắng nghe trên `127.0.0.1`, không phải `0.0.0.0`, để app không lộ thẳng ra internet:

```js
app.listen(process.env.PORT, "127.0.0.1");
```

### Bước 4: Cấu hình nginx làm reverse proxy

```bash
sudo apt install -y nginx
```

Tạo `/etc/nginx/conf.d/websocket.conf` để hỗ trợ WebSocket (Socket.IO, Next.js dev, v.v.):

```nginx
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}
```

Tạo `/etc/nginx/sites-available/myapp`:

```nginx
server {
    listen 80;
    server_name example.com www.example.com;

    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;
    }
}
```

Bật site và kiểm tra cấu hình **trước** khi reload, để một lỗi gõ phím không làm sập nginx:

```bash
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
```

Với Express, thêm `app.set("trust proxy", "loopback")` để `req.ip` và `req.protocol` lấy đúng IP khách và `https` từ header của nginx. Chi tiết các chỉ thị proxy có trong [tài liệu nginx](https://nginx.org/en/docs/http/ngx_http_proxy_module.html).

### Bước 5: Cài SSL miễn phí và mở tường lửa

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

Certbot lấy chứng chỉ [Let's Encrypt](https://letsencrypt.org/getting-started/), sửa cấu hình nginx để chuyển HTTP sang HTTPS và cài timer tự gia hạn. Kiểm tra gia hạn bằng `sudo certbot renew --dry-run`.

Nếu dùng UFW, chỉ mở SSH và web; cổng 3000 không cần mở vì app chỉ nghe nội bộ:

```bash
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
```

Cẩn thận: nếu SSH của bạn chạy ở cổng khác 22, hãy `ufw allow <cổng>/tcp` trước khi `enable`, kẻo tự khoá mình ngoài server.

## systemd hay PM2: chọn cái nào?

Cả hai đều ổn. Khác biệt chính:

| Tiêu chí | systemd | PM2 |
| --- | --- | --- |
| Cài thêm | Không, có sẵn | `npm install -g pm2` và cần `pm2 startup` để chạy khi boot |
| Tự khởi động lại | Có (`Restart=on-failure`) | Có |
| Giới hạn RAM/CPU, sandbox | Có (`MemoryMax`, `ProtectSystem`…) | Có giới hạn RAM để restart, không có sandbox |
| Chạy nhiều tiến trình | Tự viết unit template | Cluster mode có sẵn (`-i max`) |
| Log | `journalctl` | `pm2 logs`, tự xoay vòng cần module thêm |
| Không gián đoạn khi deploy | Phải tự làm | `pm2 reload` cho cluster mode |

Nếu server chỉ chạy một hai app và bạn muốn ít phụ thuộc, systemd là đủ. Nếu đã quen PM2 và cần cluster mode, PM2 rất tiện. Đừng dùng cả hai cho cùng một app.

## Cập nhật app thế nào để không bị gián đoạn?

Cách đơn giản nhất là `git pull`, `npm ci`, build rồi `systemctl restart myapp`. Cách này gây gián đoạn vài giây, và tệ hơn: nếu bản mới lỗi khi khởi động, website chết cho tới khi bạn sửa. Có ba điểm cần lưu ý:

1. **Build trước, restart sau.** Đừng build trong thư mục đang chạy; build ở thư mục release mới rồi mới chuyển sang.
2. **Kiểm tra sức khoẻ trước khi chuyển traffic.** Khởi động bản mới ở cổng khác, gọi thử một route (ví dụ `/health`), chỉ khi trả lời đúng mới đổi `proxy_pass` của nginx và reload. Đây là mô hình blue/green.
3. **Giữ được bản cũ để rollback.** Giữ vài thư mục release gần nhất; quay lại chỉ là trỏ về thư mục cũ.

Tự viết script cho cả ba việc là hoàn toàn làm được, nhưng đây chính là chỗ nhiều người bỏ qua cho tới ngày bản deploy lỗi lúc nửa đêm.

## Deploy Node.js lên VPS bằng ZoPanel

ZoPanel làm sẵn các bước trên, gồm cả phần blue/green. Quy trình:

1. **Cài runtime:** quản trị viên vào **Runtime** và cài Node.js. Panel cung cấp Node.js 24, 22, 20 và 18 chạy song song, kèm pnpm và yarn. Nếu dự án yêu cầu một bản chính trong `.nvmrc`, `.node-version` hoặc `engines.node` mà chưa cài, bản mới nhất đã cài sẽ được dùng.
2. **Tạo website:** **Website → Thêm website**, chọn loại **Git deploy**, nhập địa chỉ repository, nhánh và (với monorepo) thư mục gốc. Giữ **SSL miễn phí (Let's Encrypt)** đang bật. Repository riêng tư thì dùng **Deploy key** chỉ đọc mà panel tạo cho bạn.
3. **Kiểm tra cấu hình build:** ZoPanel nhận diện Next.js, Nuxt, NestJS, Express, Fastify, Koa, Hono… và đề xuất lệnh cài (`npm ci`, `pnpm install --frozen-lockfile`…), lệnh build và lệnh chạy. Bạn sửa được ở thẻ **Build & chạy**. Biến bí mật đặt ở **Biến môi trường**.
4. **Deploy:** app được build bằng chính user của tài khoản hosting, trong giới hạn CPU và RAM của gói. Bản mới khởi động ở slot rảnh và phải trả lời **Đường dẫn health check** với mã dưới 500 trong 90 giây; chỉ khi đó nginx mới chuyển sang bản mới và bản cũ mới bị dừng. Nếu build lỗi hay app không khởi động được, bản đang chạy không bị ảnh hưởng.
5. **Tự động deploy khi push:** bật **Deploy khi push lên nhánh main** ở thẻ **Tự động deploy**, rồi dán Payload URL và Secret vào webhook của GitHub, GitLab hoặc Gitea.

Một vài điểm đáng biết: app phải đọc cổng từ `PORT` (mỗi slot có cổng riêng, đừng viết cứng); **Rollback** trong thẻ **Các bản release** giữ 5 bản build gần nhất và cũng không gián đoạn; thẻ **Dùng mọi nhân CPU** chạy một tiến trình Node.js trên mỗi nhân CPU của gói mà không cần sửa code, với điều kiện app không giữ session hay websocket trong RAM. Log ứng dụng xem trực tiếp ở tab **Deploy**, còn lỗi 502 có nút **Chạy chẩn đoán**. Chi tiết trong tài liệu [Ứng dụng Node.js và Python](/vi/docs/apps-node-python) và [Git deploy](/vi/docs/git-deploy).

Vì mỗi app chạy như dịch vụ của một tài khoản hosting, bạn có thể chạy nhiều app của nhiều khách trên cùng VPS mà mỗi app vẫn nằm trong sandbox và giới hạn tài nguyên riêng.

## Những lỗi thường gặp khi deploy Node.js

- **502 Bad Gateway:** app không chạy hoặc không nghe đúng cổng. Kiểm tra `systemctl status`, `journalctl -u myapp` và `ss -ltnp | grep 3000`.
- **App chạy được khi gõ tay nhưng chết dưới systemd:** thường thiếu biến môi trường (systemd không đọc `.bashrc`) hoặc sai `WorkingDirectory`.
- **Build bị "Killed":** hết RAM khi build. Thêm swap, build trên máy CI rồi chỉ đẩy kết quả lên, hoặc nâng RAM.
- **Đăng nhập bị lặp chuyển hướng sau khi bật HTTPS:** app không tin header `X-Forwarded-Proto`. Bật `trust proxy` trong Express hoặc tuỳ chọn tương đương của framework.
- **Mở cổng 3000 ra internet:** người khác truy cập thẳng app, bỏ qua SSL và mọi giới hạn ở nginx. Luôn bind `127.0.0.1`.
- **Lưu file upload trong thư mục code:** mất file ở lần deploy sau. Hãy để upload ở thư mục cố định ngoài release, hoặc ở kho S3.

## Kết luận

Deploy Node.js lên VPS không khó: Node LTS chính thức, một user riêng, systemd, nginx và Let's Encrypt là đủ cho phần lớn ứng dụng. Phần tốn công thật sự là về sau: cập nhật không gián đoạn, rollback, log, và khi có nhiều app trên một server thì cách ly giữa chúng. Nếu bạn muốn những việc đó chạy sẵn, hãy thử [gói Free của ZoPanel](/vi/pricing) (3 tài khoản hosting, 10 website, 10 database, không giới hạn thời gian; Git deploy và ứng dụng Node.js đều có trong gói Free) trên một VPS thử nghiệm và deploy chính repository của bạn.

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

### VPS bao nhiêu RAM thì đủ để chạy Node.js?

Một app Express hay Fastify nhỏ chạy tốt trên VPS 1 GB RAM. Nếu build Next.js hoặc dự án TypeScript lớn ngay trên server, nên có 2 GB trở lên hoặc thêm swap, vì bước build tốn RAM hơn lúc chạy. Khi chạy nhiều app hay thêm database, hãy cộng RAM của từng thành phần.

### Có bắt buộc dùng PM2 để chạy Node.js trên VPS không?

Không. systemd có sẵn trên Ubuntu và Debian, giữ app chạy, tự khởi động lại khi lỗi và có thêm các tuỳ chọn sandbox. PM2 tiện khi bạn cần cluster mode và lệnh `pm2 reload`; chọn một trong hai, đừng dùng cả hai cho cùng một app.

### Vì sao không nên dùng `apt install nodejs` trên Ubuntu 24.04?

Vì gói trong kho mặc định của Ubuntu 24.04 là Node.js 18, và Node.js 18 đã hết hỗ trợ từ tháng 3/2025, không còn bản vá bảo mật. Hãy cài Node.js 22 hoặc 24 LTS từ bản build chính thức trên nodejs.org hoặc từ một kho apt có phiên bản mới.

### Làm sao chạy nhiều ứng dụng Node.js trên một VPS?

Cho mỗi app một cổng nội bộ riêng (3000, 3001…), một dịch vụ systemd riêng và một khối `server` nginx riêng theo tên miền. Tốt nhất mỗi app chạy bằng một user riêng để app này không đọc được file của app kia. Control panel như ZoPanel tự cấp cổng và tách mỗi app vào tài khoản riêng.

### Deploy Node.js có cần Docker không?

Không bắt buộc. Chạy trực tiếp bằng Node.js và systemd đơn giản và nhẹ hơn cho phần lớn app. Docker hữu ích khi app cần thư viện hệ thống đặc biệt hoặc bạn muốn môi trường giống hệt nhau giữa máy dev và server.
