Bỏ qua đến nội dung
KhaiziNam Blog KhaiziNam Blog
Quay lại
Read in English

Cách Deploy Node.js và React Lên Hosting Linux Bằng Nginx Reverse Proxy — Hướng Dẫn Đầy Đủ PM2 + SSL 2025

Bạn đã build xong app Node.js hoặc React và đang nhìn vào màn hình terminal của VPS Linux mà không biết bắt đầu từ đâu? Upload file lên shared hosting kiểu cũ không chạy được với Node.js — bạn cần một process manager để giữ app sống, một reverse proxy để trỏ domain vào đúng port, và cấu hình SSL để chạy HTTPS. Bài này hướng dẫn toàn bộ stack triển khai thực tế: PM2 quản lý process, Nginx làm reverse proxy với proxy_pass, SSL miễn phí qua Let’s Encrypt, và cách serve React build tĩnh — tất cả từ đầu trên Ubuntu.

Mục Lục

1. Nginx Reverse Proxy Là Gì Và Tại Sao Node.js Cần Nó

2. Chuẩn Bị Trước Khi Bắt Đầu

3. Bước 1 — Cài Node.js Trên Ubuntu Linux

4. Bước 2 — Cài Và Cấu Hình PM2 Process Manager

5. Bước 3 — Cài Và Cấu Hình Nginx

6. Bước 4 — Cấu Hình Nginx proxy_pass Cho Node.js API

7. Bước 5 — Deploy React Build Tĩnh Với Nginx

8. Bước 6 — Bật HTTPS Miễn Phí Với Let’s Encrypt

9. Bước 7 — Chạy Nhiều App Node.js Trên Một Server

10. Lỗi Thường Gặp Và Cách Sửa

11. Câu Hỏi Thường Gặp


Nginx Reverse Proxy Là Gì Và Tại Sao Node.js Cần Nó

Node.js chạy app của bạn trên một port nội bộ — thường là 3000, 4000 hoặc 5000. Port này không được và không nên expose trực tiếp ra internet trên port 80 hay 443. Đây là lúc Nginx đóng vai trò reverse proxy: nó đứng trước process Node.js, nhận toàn bộ traffic từ internet trên port 80/443, rồi chuyển tiếp vào bên trong đến app của bạn qua chỉ thị proxy_pass.

Kiến trúc này cho phép bạn làm được những thứ mà Node.js một mình không xử lý tốt:

PM2 bổ sung cho Nginx bằng cách giữ process Node.js sống sau khi crash, tự khởi động lại khi server reboot, và cung cấp monitoring và quản lý log.

Xem thêm: Deploy PHP, Laravel, CodeIgniter Lên Linux VPS Với Nginx

Chuẩn Bị Trước Khi Bắt Đầu

Bước 1 — Cài Node.js Trên Ubuntu Linux

Không cài Node.js từ repository mặc định của Ubuntu — phiên bản đó quá cũ. Dùng NodeSource repository để lấy bản LTS hiện tại (Node.js 20.x tại thời điểm viết bài):

# Cập nhật package list sudo apt update && sudo apt upgrade -y

Cài curl nếu chưa có

sudo apt install -y curl git build-essential

Thêm NodeSource repository cho Node.js 20 LTS

curl -fsSL https://deb.nodesource.com/setup\_20.x | sudo -E bash -

Cài Node.js và npm

sudo apt install -y nodejs

Kiểm tra phiên bản

node -v # phải hiện v20.x.x npm -v # phải hiện 10.x.x

Nếu bạn quản lý nhiều project Node.js cần các phiên bản khác nhau, cài NVM (Node Version Manager) thay thế để switch version theo từng project:

# Cài NVM curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash source ~/.bashrc

Cài và dùng Node.js 20

nvm install 20 nvm use 20 nvm alias default 20

Bước 2 — Cài Và Cấu Hình PM2 Process Manager

PM2 là process manager tiêu chuẩn cho Node.js trong môi trường production. Không có PM2, app của bạn sẽ chết ngay khi phiên SSH kết thúc hoặc server reboot. Cài toàn cục:

sudo npm install -g pm2

Clone app và khởi động với PM2:

# Clone repository của bạn cd /var/www git clone https://github.com/yourusername/your-node-app.git cd your-node-app

Cài dependencies (chỉ production)

npm install —production

Khởi động app với PM2 (thay app.js bằng file entry của bạn)

pm2 start app.js —name “my-node-app”

Hoặc chỉ định port cụ thể

PORT=3000 pm2 start app.js —name “my-node-app”

Lưu danh sách process PM2

pm2 save

Cấu hình PM2 tự khởi động khi server reboot

pm2 startup

Sao chép và chạy lệnh mà PM2 in ra (dạng như:)

sudo env PATH=$PATH:/usr/bin /usr/lib/node_modules/pm2/bin/pm2 startup systemd -u youruser —hp /home/youruser

Các lệnh PM2 dùng hàng ngày:

pm2 list # Danh sách tất cả process đang chạy pm2 logs my-node-app # Xem log real-time pm2 restart my-node-app # Restart sau khi deploy code mới pm2 stop my-node-app # Dừng process pm2 delete my-node-app # Xóa khỏi danh sách PM2 pm2 monit # Dashboard CPU/memory real-time

Với app production, dùng cluster mode để tận dụng tất cả CPU cores:

# Khởi động với cluster mode — spawn một instance mỗi CPU core pm2 start app.js —name “my-node-app” -i max

Hoặc chỉ định số lượng instance cụ thể

pm2 start app.js —name “my-node-app” -i 4

Bước 3 — Cài Và Cấu Hình Nginx

# Cài Nginx sudo apt install -y nginx

Bật Nginx tự khởi động khi server bật

sudo systemctl enable nginx

Khởi động Nginx

sudo systemctl start nginx

Kiểm tra trạng thái

sudo systemctl status nginx

Cấu hình firewall — cho phép HTTP, HTTPS và SSH

sudo ufw allow ssh sudo ufw allow ‘Nginx Full’ sudo ufw enable sudo ufw status

Cấu hình Nginx nằm trong hai thư mục trên Ubuntu:

Cách tách biệt này cho phép bạn soạn thảo và kiểm tra cấu hình mà không kích hoạt ngay.

Bước 4 — Cấu Hình Nginx proxy_pass Cho Node.js API

Tạo file cấu hình Nginx mới cho app Node.js. Thay yourdomain.com bằng domain thực của bạn và 3000 bằng port app đang lắng nghe:

sudo nano /etc/nginx/sites-available/yourdomain.com

Dán cấu hình sau vào:

server { listen 80; server_name yourdomain.com www.yourdomain.com;

# Log file
access\_log /var/log/nginx/yourdomain.access.log;
error\_log  /var/log/nginx/yourdomain.error.log;

location / {
    proxy\_pass         http://127.0.0.1:3000;
    proxy\_http\_version 1.1;

    # Bắt buộc cho WebSocket
    proxy\_set\_header Upgrade    $http\_upgrade;
    proxy\_set\_header Connection 'upgrade';

    # Truyền thông tin client thực đến Node.js app
    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\_cache\_bypass $http\_upgrade;
    proxy\_redirect     off;

    # Timeout — tăng lên cho request chạy lâu
    proxy\_read\_timeout  240s;
    proxy\_send\_timeout  240s;
    proxy\_connect\_timeout 75s;
}

}

Bật site và reload Nginx:

# Tạo symlink để bật site sudo ln -s /etc/nginx/sites-available/yourdomain.com /etc/nginx/sites-enabled/

Kiểm tra cú pháp cấu hình — luôn làm bước này trước khi reload

sudo nginx -t

Nếu test pass, reload Nginx

sudo systemctl reload nginx

App Node.js của bạn giờ có thể truy cập tại http://yourdomain.com. Dòng proxy_pass http://127.0.0.1:3000 là trái tim của reverse proxy — toàn bộ traffic vào port 80 được chuyển tiếp đến process Node.js trên port 3000 nội bộ.

Tại sao dùng 127.0.0.1 thay vì localhost trong proxy_pass?

Dùng 127.0.0.1 ép IPv4 resolution và tránh một bug tinh vi: trên hệ thống bật IPv6, localhost có thể resolve thành ::1 (IPv6 loopback) trong khi app Node.js chỉ lắng nghe IPv4 — gây lỗi connection refused dù cả Nginx lẫn Node.js đều đang chạy bình thường.

Bước 5 — Deploy React Build Tĩnh Với Nginx

React (và các SPA framework khác như Vue, Next.js static export) nên được serve dưới dạng file tĩnh đã build sẵn — không proxy qua Node.js. Cách này nhanh hơn đáng kể và không tốn tài nguyên app server.

Build React app trên server hoặc trong CI pipeline:

cd /var/www/your-react-app npm install npm run build

Output nằm trong /var/www/your-react-app/build (CRA) hoặc /dist (Vite)

Tạo cấu hình Nginx để serve static build và tùy chọn proxy API calls về Node.js backend:

sudo nano /etc/nginx/sites-available/app.yourdomain.com

server { listen 80; server_name app.yourdomain.com;

# Thư mục gốc — trỏ đến output build của React
root /var/www/your-react-app/build;
index index.html;

access\_log /var/log/nginx/react-app.access.log;
error\_log  /var/log/nginx/react-app.error.log;

# Serve React app — xử lý client-side routing
# Không có dòng này, refresh trên /dashboard sẽ trả 404
location / {
    try\_files $uri $uri/ /index.html;
}

# Proxy request /api về Node.js backend
# React app gọi /api/users -> chuyển tiếp đến Node.js port 3000
location /api {
    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\_cache\_bypass $http\_upgrade;
}

# Cache mạnh cho static assets
location ~\* \\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
    expires    1y;
    add\_header Cache-Control "public, immutable";
}

}

Directive try_files $uri $uri/ /index.html cực kỳ quan trọng cho React Router. Không có nó, khi user truy cập trực tiếp vào /dashboard sẽ nhận 404 vì Nginx tìm file tên dashboard trên disk không thấy. Dòng này nói với Nginx: nếu file không tồn tại, serve index.html và để React Router tự xử lý routing phía client.

Bước 6 — Bật HTTPS Miễn Phí Với Let’s Encrypt

Không bao giờ chạy app production trên HTTP thuần. Let’s Encrypt cấp SSL certificate miễn phí qua Certbot, công cụ này cũng tự cấu hình Nginx luôn:

# Cài Certbot và Nginx plugin sudo apt install -y certbot python3-certbot-nginx

Cho phép HTTPS qua firewall

sudo ufw allow ‘Nginx Full’

Cấp certificate và tự cấu hình Nginx

Thay bằng domain thực của bạn

sudo certbot —nginx -d yourdomain.com -d www.yourdomain.com

Làm theo hướng dẫn:

- Nhập email để nhận thông báo gia hạn

- Đồng ý điều khoản dịch vụ

- Chọn redirect (option 2) để ép toàn bộ HTTP sang HTTPS

Certbot tự sửa file cấu hình Nginx để thêm HTTPS và redirect HTTP sang HTTPS. Certificate có hiệu lực 90 ngày, Certbot cài systemd timer để tự gia hạn trước khi hết hạn. Kiểm tra tự gia hạn với:

sudo certbot renew —dry-run

Bước 7 — Chạy Nhiều App Node.js Trên Một Server

Một trong những lợi thế lớn nhất của Nginx: chạy nhiều app trên cùng một server, mỗi app một domain hoặc subdomain riêng, mỗi app một port nội bộ riêng. Tạo file config riêng cho từng app:

# App 1 — API trên port 3000 sudo nano /etc/nginx/sites-available/api.yourdomain.com

App 2 — Admin panel trên port 4000

sudo nano /etc/nginx/sites-available/admin.yourdomain.com

App 3 — React frontend (file tĩnh)

sudo nano /etc/nginx/sites-available/app.yourdomain.com

Ví dụ chạy hai Node.js API riêng biệt cùng lúc:

# /etc/nginx/sites-available/api.yourdomain.com server { listen 80; server_name api.yourdomain.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_cache_bypass $http_upgrade; } }

/etc/nginx/sites-available/admin.yourdomain.com

server { listen 80; server_name admin.yourdomain.com; location / { proxy_pass http://127.0.0.1:4000; 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_cache_bypass $http_upgrade; } }

Bật cả hai và reload:

sudo ln -s /etc/nginx/sites-available/api.yourdomain.com /etc/nginx/sites-enabled/ sudo ln -s /etc/nginx/sites-available/admin.yourdomain.com /etc/nginx/sites-enabled/ sudo nginx -t && sudo systemctl reload nginx

Lỗi Thường Gặp Và Cách Sửa

Câu Hỏi Thường Gặp

Hỏi: Có cần PM2 không nếu đang dùng Docker?

Đáp: Không. Docker tự quản lý vòng đời process — nếu container crash, Docker restart lại (với flag --restart unless-stopped). Bên trong Docker container, chạy Node.js trực tiếp làm main process (CMD ["node", "app.js"]). PM2 dành cho deployment trên VPS hoặc bare-metal không có container hóa.

Hỏi: Next.js deploy giống Node.js không hay khác?

Đáp: Giống, nhưng cần phân biệt một điểm. Next.js production chạy như Node.js server (npm run start trên port 3000 mặc định) — proxy_pass về nó y hệt Node.js app thông thường. Nếu dùng next export để xuất ra file tĩnh hoàn toàn, serve như ví dụ React static ở trên. Với App Router dùng server components, luôn dùng chế độ Node.js server.

Hỏi: Deploy code mới mà không muốn app bị downtime thì làm thế nào?

Đáp: Pull code mới, cài dependencies, sau đó dùng pm2 reload thay vì pm2 restart để deploy zero-downtime: git pull && npm install --production && pm2 reload my-node-app. PM2 reload xoay vòng các worker lần lượt, giữ app vẫn nhận traffic trong suốt quá trình.

Hỏi: App Node.js nên lắng nghe trên port nào?

Đáp: Bất kỳ port nào trên 1024 mà chưa bị dùng — 3000, 4000, 5000, 8000 đều là lựa chọn phổ biến. Kiểm tra port đang dùng bằng sudo ss -tlnp. Port bên ngoài (80/443) do Nginx xử lý hoàn toàn — app Node.js không cần biết đến chúng.

Hỏi: Đặt biến môi trường cho production thế nào?

Đáp: Tạo file .env trong thư mục app và dùng thư viện dotenv, hoặc truyền biến trực tiếp qua PM2 ecosystem file. Tạo ecosystem.config.js ở root project và chạy pm2 start ecosystem.config.js. Cách này tách biệt cấu hình môi trường khỏi code ứng dụng và dễ quản lý riêng cho từng môi trường dev, staging, production. Tham khảo thêm về cách cấu hình CI/CD tự động deploy Node.js với GitHub Actions để hoàn thiện quy trình.

Tổng Kết — Stack Triển Khai Đã Sẵn Sàng Cho Production

Bạn vừa có đầy đủ stack triển khai hoàn chỉnh: Node.js chạy dưới PM2, Nginx làm reverse proxy xử lý toàn bộ traffic công khai qua proxy_pass, HTTPS được đảm bảo bởi Let’s Encrypt, React static assets được Nginx serve trực tiếp, và khả năng host nhiều app trên một server duy nhất. Đây là stack mà đa số deployment Node.js production trên thế giới đang dùng.

Bước tiếp theo: Thiết lập script deploy tự động — một shell script hoặc GitHub Action SSH vào server, pull code mới nhất, chạy npm install và gọi pm2 reload. Tự động hóa deployment từ ngày đầu loại bỏ lỗi thủ công và làm cho mỗi lần cập nhật chỉ mất vài giây thay vì vài phút.

Xem thêm: Deploy PHP, Laravel, CodeIgniter Lên Linux VPS Với Nginx


Chia sẻ bài viết:

Bài viết liên quan

Bí quyết Deploy React/Node.js trên VPS 1GB RAM với PM2: Không bao giờ bị Out of Memory 2026

Đối với các nhà phát triển Web, việc sở hữu một VPS 1GB RAM với chi phí rẻ là lựa chọn khởi đầu lý tưởng. Thế nhưng, thực tế triển khai lại thường biến thành một "cơn ác mộng" khi ứng dụng liên tục bị sập mà không rõ nguyên nhân. Vấn đề cốt lõi nằm ở hai yếu tố chính sau đây:

Deploy PHP, Laravel, CodeIgniter Lên Linux VPS Với Nginx + MySQL Docker + SSL 2026

Hướng dẫn đầy đủ cài PHP 8.2, Nginx, MySQL Docker, cấu hình php-fpm, UFW firewall và SSL Let's Encrypt để deploy PHP thuần, Laravel, CodeIgniter lên Ubuntu VPS từ đầu.

Tối ưu thời gian setup môi trường dự án với Docker cho VPS cấu hình thấp

Thực trạng VPS 2GB RAM và nỗi lo "hết bộ nhớ" Việc triển khai dự án web trên những gói VPS cấu hình thấp (2GB RAM) từ lâu đã trở thành bài toán "cân não" với nhiều lập trình viên. Bạn có bao giờ gặp tình trạng vừa cài xong hệ thống, chưa kịp chạy thử thì server đã văng kết nối SSH, hoặc tệ hơn là website cứ hoạt động chập chờn rồi "đột tử" vì lỗi Out of Memory (OOM)?

Mức lương fresher junior IT 2026 - PHP, NodeJS, React, Flutter thực tế

Bảng lương thực tế của fresher và junior IT Việt Nam năm 2026 theo từng stack - PHP/Laravel, Node.js, ReactJS, Flutter - kèm cách đọc con số để biết bạn đang ở đâu trên thị trường, dù bạn chưa đi làm hay đã đi làm 1-2 năm.

Tổng hợp mức lương IT theo level tại TP.HCM và Hà Nội năm 2025 - 2026

Ngành IT tại Việt Nam tiếp tục giữ mặt bằng lương cao so với nhiều ngành khác, đặc biệt tập trung tại TP.HCM và Hà Nội. Hai thành phố này hiện là trung tâm tuyển dụng công nghệ lớn nhất cả nước, nơi tập trung nhiều công ty outsourcing, product, fintech, AI và startup quốc tế.

Top 5 web đọc truyện tranh miễn phí vẫn còn hoạt động hiện nay

Top 5 web đọc truyện tranh miễn phí vẫn còn hoạt động hiện nay 1. NetTruyen — Cái tên lâu đời không thể bỏ qua NetTruyen là một trong những nền tảng đọc truyện tranh online lâu đời và quen thuộc nhất với cộng đồng Việt Nam.

Cách đàm phán lương khi mới ra trường — fresher IT không nên bỏ qua

Đàm phán lương fresher IT là quá trình thương lượng mức lương với nhà tuyển dụng sau khi nhận được offer — một bước mà hầu hết sinh viên mới ra trường bỏ qua hoàn toàn vì nghĩ "fresher thì không có quyền mặc cả." Thực tế ngược lại: hầu hết công ty đều chừa ra biên độ thương lượng ngay cả với fresher, và không đàm phán nghĩa là bạn đang tự để lại tiền trên bàn mà không biết.

Portfolio cho dev junior - cần có gì để được chú ý

Portfolio developer junior là tập hợp các project thực tế, kỹ năng và thông tin nghề nghiệp mà bạn trình bày để nhà tuyển dụng đánh giá năng lực - thay thế cho phần kinh nghiệm làm việc còn trống trên CV. Với fresher và junior developer, portfolio không phải thứ "có thì tốt" - đó là bằng chứng duy nhất bạn có thể đưa ra để chứng minh bạn thực sự biết làm việc, không chỉ biết lý thuyết.

Cover Letter Fresher IT: Cách Viết Để Được Gọi Phỏng Vấn

Cách viết cover letter cho fresher IT khi chưa có kinh nghiệm, kèm mẫu thực tế, cấu trúc chuẩn và mẹo tăng tỷ lệ vượt qua vòng lọc CV.

Bị từ chối phỏng vấn IT — tôi đã làm gì để pass lần sau

Bị từ chối phỏng vấn IT là trải nghiệm mà hầu như mọi fresher và junior developer đều trải qua ít nhất một lần — thường là nhiều hơn thế. Cú từ chối đầu tiên có thể khiến bạn nghi ngờ năng lực bản thân, nhưng thực tế phần lớn các trường hợp thất bại đều có nguyên nhân cụ thể, có thể xác định và có thể khắc phục trong vòng vài tuần nếu bạn biết nhìn đúng chỗ.


Bài trước
Cách tự giới thiệu bản thân khi phỏng vấn IT (script mẫu cho fresher & junior)
Bài tiếp theo
Session vs JWT: Toàn Bộ Lý Thuyết Và Câu Hỏi Phỏng Vấn Hay Gặp Nhất Mà Junior Cần Nắm Chắc