Setup website với Nginx trên VPS Ubuntu

Triển khai Nginx production trên Ubuntu 22.04/24.04: thiết lập DNS, SSL, server block cho PHP-FPM/Node/static, cấu hình phân quyền, logs, bước verify và quy trình rollback an toàn.

NHT
· 8 phút đọc
Setup website với Nginx trên VPS Ubuntu

TL;DR / Checklist nhanh

  • Cập nhật hệ thống, cài Nginx, mở UFW cho OpenSSH và HTTP/HTTPS.
  • Tạo webroot, cấu hình server block (PHP-FPM hoặc reverse proxy tới Node, hoặc static).
  • Trỏ DNS về IP VPS, xin SSL Let’s Encrypt, bật redirect HTTPS.
  • Thiết lập phân quyền tối thiểu cần thiết, kiểm tra logs, verify bằng curl.
  • Triển khai theo quy trình reload an toàn và có kế hoạch rollback.

Điều kiện, phiên bản tham chiếu và cảnh báo rủi ro

  • Hệ điều hành: Ubuntu 22.04 LTS hoặc 24.04 LTS (amd64).
  • Nginx: gói từ Ubuntu repo mặc định. PHP-FPM ví dụ: 8.3 (có thể dùng PPA nếu cần phiên bản mới).
  • Quyền: user có sudo; truy cập SSH hoạt động bình thường.
  • Tên miền: có thể cấu hình DNS A/AAAA trỏ về IP VPS.

Cảnh báo:

  • UFW: chỉ bật sau khi đã mở rule cho SSH cổng đang dùng, tránh khóa phiên SSH. Nếu dùng console nhà cung cấp VPS, bạn có đường lui khi bị khóa.
  • PPA PHP: chỉ thêm khi thật sự cần phiên bản mới; luôn kiểm tra tính tương thích trước ở môi trường staging.
  • SSL/HSTS: chỉ bật HSTS sau khi xác nhận HTTPS ổn định; bật sai có thể gây lỗi truy cập lâu dài cho người dùng.

1) Cập nhật hệ thống, cài Nginx và cấu hình UFW an toàn

sudo apt update && sudo apt upgrade -y
sudo apt install -y nginx
# Mở firewall cho SSH (mặc định cổng 22). Nếu bạn dùng cổng khác, thay thế tương ứng
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'   # mở 80 và 443
sudo ufw enable               # xác nhận Y khi được hỏi
sudo ufw status

# Kiểm tra Nginx hoạt động
systemctl status nginx

Kết quả mong đợi: Nginx Active (running), truy cập http://IP_VPS thấy trang mặc định.

Verify nhanh:

curl -I http://<IP_VPS>
ss -ltnp | grep -E ':80|:443'   # Nginx lắng nghe cổng 80/443

Rollback nếu bị khóa SSH do firewall: đăng nhập qua console của nhà cung cấp VPS, chạy sudo ufw disable hoặc xóa rule sai và bật lại.

2) (Tuỳ chọn) Cài MariaDB

sudo apt install -y mariadb-server mariadb-client
sudo mysql_secure_installation
-- Tạo DB và user mẫu
CREATE DATABASE myapp_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'myapp_user'@'localhost' IDENTIFIED BY 'm@t_kh@u_m@nh';
GRANT ALL PRIVILEGES ON myapp_db.* TO 'myapp_user'@'localhost';
FLUSH PRIVILEGES;

Verify:

mysql -u myapp_user -p -e "SHOW DATABASES LIKE 'myapp_db';"

3) (Tuỳ chọn) Cài PHP-FPM 8.3

Chỉ thêm PPA khi cần phiên bản mới hơn repo Ubuntu:

sudo apt install -y software-properties-common ca-certificates lsb-release apt-transport-https curl
sudo add-apt-repository ppa:ondrej/php -y
sudo apt update
sudo apt install -y \
  php8.3 php8.3-fpm php8.3-cli php8.3-mysql php8.3-curl php8.3-mbstring \
  php8.3-xml php8.3-bcmath php8.3-intl php8.3-sqlite3 php8.3-zip \
  php8.3-gd php8.3-redis php-imagick php8.3-opcache php8.3-apcu

Tối ưu cơ bản cho production (/etc/php/8.3/fpm/php.ini):

memory_limit = 256M
post_max_size = 64M
upload_max_filesize = 64M
max_execution_time = 120
cgi.fix_pathinfo = 0

; Opcache
opcache.enable=1
opcache.validate_timestamps=0
opcache.max_accelerated_files=20000
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
sudo systemctl reload php8.3-fpm
systemctl status php8.3-fpm

Lưu ý: update-alternatives --config php chỉ ảnh hưởng phiên bản PHP CLI, không thay đổi socket FPM.

4) (Tuỳ chọn) Cài Node.js LTS

curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
source ~/.bashrc
nvm install --lts
node -v && npm -v

5) Tạo webroot và file kiểm thử

sudo mkdir -p /var/www/example.com/public
sudo chown -R $USER:$USER /var/www/example.com
cat << 'PHP' | sudo tee /var/www/example.com/public/info.php
<?php phpinfo();
PHP

6) Cấu hình server block cho PHP-FPM

Tạo file cấu hình:

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

Nội dung mẫu an toàn cơ bản:

server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

    root /var/www/example.com/public;
    index index.php index.html;

    # Security headers cơ bản (áp dụng cả HTTP, khuyến nghị tinh chỉnh ở khối HTTPS)
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;

    # Gzip cơ bản cho nội dung văn bản
    gzip on;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
        fastcgi_read_timeout 120s;
    }

    # Chặn truy cập file ẩn
    location ~ /\.[^/]+ { deny all; }
}

Kích hoạt và reload an toàn:

sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
# Tùy chọn: giữ site mặc định cho tới khi verify OK, sau đó mới gỡ
sudo nginx -t && sudo systemctl reload nginx

Verify:

curl -I http://example.com
curl -I http://example.com/info.php

Rollback nếu lỗi cấu hình: sudo rm -f /etc/nginx/sites-enabled/example.comsudo nginx -t && sudo systemctl reload nginx.

7) Reverse proxy cho Node/SSR

Ví dụ Next.js (có WebSocket):

upstream next_app {
    server 127.0.0.1:3000;
    keepalive 64;
}

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

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

    location / {
        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_http_version 1.1;
        proxy_pass http://next_app;
    }
}

Gợi ý systemd cho app Node (khởi động cùng hệ thống):

sudo tee /etc/systemd/system/nextapp.service >/dev/null <<'UNIT'
[Unit]
Description=Next.js App
After=network.target

[Service]
Type=simple
User=www-data
WorkingDirectory=/var/www/example.com
Environment=NODE_ENV=production
ExecStart=/usr/bin/node ./.next/standalone/server.js
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target
UNIT
sudo systemctl daemon-reload
sudo systemctl enable --now nextapp
systemctl status nextapp

8) Static site (React SPA/Next static export)

server {
    listen 80;
    server_name example.com www.example.com;
    root /var/www/example.com/out;  # thư mục build tĩnh

    location / {
        try_files $uri $uri/ /index.html;
    }
}

9) Trỏ DNS về VPS

Thêm bản ghi tại nhà cung cấp DNS:

  • A: @ → IP VPS
  • A: www → IP VPS

Verify propagation:

dig +short A example.com

10) Cài chứng chỉ SSL (Let’s Encrypt) và redirect HTTPS

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

Kiểm tra tự động gia hạn:

systemctl status certbot.timer
sudo certbot renew --dry-run

Verify:

curl -I https://example.com

Rollback nếu xin SSL lỗi: đảm bảo DNS trỏ đúng, port 80 mở; tạm thời phục vụ HTTP (gỡ cấu hình HTTPS sinh ra nếu chưa chạy) và thử lại sau khi DNS đã cập nhật.

11) Phân quyền an toàn cho web

Nguyên tắc: chỉ cấp ghi cho thành phần thực sự cần ghi (VD: thư mục runtime/cache/upload). Không chmod -R 777.

  • Chủ sở hữu mã nguồn: user deploy; group: www-data để FPM/Node có thể đọc.
  • Thư mục runtime (VD: Laravel storage, bootstrap/cache) cấp ghi cho group www-data.
# Ví dụ Laravel
sudo chown -R deploy:www-data /var/www/example.com
sudo find /var/www/example.com -type f -exec chmod 0644 {} +
sudo find /var/www/example.com -type d -exec chmod 0755 {} +

sudo chgrp -R www-data /var/www/example.com/storage /var/www/example.com/bootstrap/cache
sudo chmod -R 0775 /var/www/example.com/storage /var/www/example.com/bootstrap/cache

# Nếu cần ACL tinh chỉnh hơn
sudo apt install -y acl
sudo setfacl -R -m g:www-data:rwx /var/www/example.com/storage /var/www/example.com/bootstrap/cache
sudo setfacl -d -m g:www-data:rwx /var/www/example.com/storage /var/www/example.com/bootstrap/cache

12) Logs, giám sát và reload không gián đoạn

  • Logs mặc định: /var/log/nginx/access.log, /var/log/nginx/error.log. Ubuntu đã có logrotate tại /etc/logrotate.d/nginx.
  • Giám sát dịch vụ: systemctl status nginx, journalctl -u nginx -f.
  • Reload an toàn sau khi chỉnh config: sudo nginx -t && sudo systemctl reload nginx.
tail -f /var/log/nginx/access.log /var/log/nginx/error.log

13) Mẫu cấu hình thêm

A) Laravel / Livewire / Filament (PHP-FPM)

server {
    listen 80;
    server_name example.com www.example.com;
    root /var/www/example.com/public;
    index index.php;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \\.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
    }
}

B) Next.js SSR (Node server)

upstream next_app { server 127.0.0.1:3000; keepalive 64; }
map $http_upgrade $connection_upgrade { default upgrade; '' close; }
server {
    listen 80;
    server_name example.com www.example.com;
    location / {
        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_http_version 1.1;
        proxy_pass http://next_app;
    }
}

C) React SPA hoặc Next static export

server {
    listen 80;
    server_name example.com www.example.com;
    root /var/www/example.com/out; # thư mục build tĩnh
    location / { try_files $uri $uri/ /index.html; }
}

D) NestJS API

upstream nest_app { server 127.0.0.1:4000; keepalive 64; }
server {
    listen 80;
    server_name api.example.com;
    location / {
        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_pass http://nest_app;
    }
}

14) Lỗi thường gặp và cách xử lý nhanh

  • 502 Bad Gateway (PHP): socket FPM sai hoặc FPM chưa chạy → kiểm tra systemctl status php8.3-fpm, chỉnh đúng fastcgi_pass.
  • 404 trên Laravel: thiếu try_files → dùng đúng snippet ở trên.
  • SSL cấp thất bại: domain chưa trỏ xong hoặc port 80 bị chặn → mở UFW Nginx Full, chờ DNS propagate rồi chạy lại certbot.
  • Tải file lớn lỗi: thêm client_max_body_size 64m; trong server block và reload.
  • Permission denied khi ghi file runtime: chỉ cấp ghi cho thư mục cần thiết (VD: storage, bootstrap/cache), xem mục phân quyền an toàn.

15) Quy trình rollback nhanh (tránh downtime)

  1. Sao lưu file cấu hình trước khi sửa: sudo cp /etc/nginx/sites-available/example.com /etc/nginx/sites-available/example.com.bak.$(date +%F-%H%M).
  2. Sau khi sửa, luôn chạy sudo nginx -t. Nếu fail, khôi phục file backup ngay.
  3. Nếu sau reload phát sinh lỗi 5xx: sudo ln -sf /etc/nginx/sites-available/example.com.bak... sang sites-enabled hoặc gỡ link site mới: sudo rm /etc/nginx/sites-enabled/example.comsudo systemctl reload nginx.
  4. Vẫn lỗi? Tạm bật lại site mặc định: sudo ln -s /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default → reload.
Xem thêm: Setup SSH GitHub cho Shared Hosting để clone dự án an toàn, nhanh gọn (cách làm tương tự trên VPS) và Tăng dung lượng disk VPS Ubuntu trong Proxmox khi cần mở rộng lưu trữ.

FAQ

  • Reload khác Restart thế nào? Reload (systemctl reload nginx) nạp lại cấu hình không cắt kết nối; Restart dừng dịch vụ rồi khởi động lại (có thể gián đoạn ngắn).
  • Phục vụ nhiều domain? Tạo một server block cho mỗi domain, trỏ server_name tương ứng, kích hoạt qua symlink sites-enabled.
  • Đổi phiên bản PHP-FPM? Cài FPM phiên bản khác, sửa fastcgi_pass sang socket tương ứng, reload Nginx.
  • Force HTTPS? Khi đã có SSL ổn định, thêm server block 80 chỉ để redirect: return 301 https://$host$request_uri;.

Tài liệu tham khảo