Tới nội dung
Cloudj

Ứng dụng VPS

Deploy Laravel lên VPS: Nginx, PHP-FPM, queue từng bước

Hướng dẫn deploy Laravel lên VPS từng bước: cài PHP-FPM, Nginx, cấu hình .env, chạy queue bằng Supervisor, lập lịch và phát hành không gián đoạn.

Cập nhật · 4 phút đọc

Để deploy Laravel lên VPS, bạn cần bốn thứ phối hợp với nhau: Nginx đứng trước, PHP-FPM chạy code, một trình quản lý tiến trình giữ cho queue worker luôn sống, và một quy trình phát hành không làm sập trang. Các nền tảng managed giấu những phần này đi; trên máy chủ của mình, bạn dựng một lần và hiểu rõ chính xác thứ gì đang chạy trên production. Bài này đi qua từng bước trên một máy chủ Ubuntu mới, kèm lệnh có thể chép dùng ngay.

Nếu chưa chọn vị trí, hướng dẫn VPS châu Á so sánh các lựa chọn. Cloudj không có máy chủ tại Việt Nam; với ứng dụng phục vụ người dùng Việt Nam hoặc người dùng khắp Đông Nam Á, các vị trí gần nhất là Singapore (bài chọn VPS Singapore), Hong Kong (bài chọn VPS Hong Kong) và Tokyo (bài chọn VPS Nhật Bản).

Chuẩn bị trước khi deploy Laravel lên VPS

  • Máy chủ. Với một ứng dụng Laravel thông thường, database chạy chung máy, 2 vCPU và 4 GB RAM là mức khởi đầu thoải mái. Máy nhỏ hơn đủ cho ứng dụng nhỏ hoặc môi trường staging. Tạo máy chủ trong console Cloudj; trang cloud server liệt kê mọi vị trí.
  • Domain có bản ghi A trỏ về IP máy chủ.
  • Repository Git mà máy chủ pull được, dùng deploy key.
  • Máy chủ đã gia cố. Làm các bước cơ bản trong checklist bảo mật VPS mới trước: SSH key, user không phải root, firewall chỉ mở 22, 80 và 443.

Các lệnh bên dưới giả định Ubuntu 24.04, PHP 8.3 và user tên deploy. Hãy chỉnh phiên bản theo yêu cầu của ứng dụng.

Bước 1: cài PHP-FPM, Nginx và database

sudo apt update
sudo apt install -y nginx mariadb-server redis-server supervisor unzip git \
  php8.3-fpm php8.3-cli php8.3-mysql php8.3-mbstring php8.3-xml \
  php8.3-curl php8.3-zip php8.3-bcmath php8.3-intl php8.3-redis
curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer

Tạo database và user:

sudo mysql -e "CREATE DATABASE app; CREATE USER 'app'@'localhost' IDENTIFIED BY 'doi-mat-khau'; GRANT ALL ON app.* TO 'app'@'localhost';"

Dùng PostgreSQL? Bài cài PostgreSQL trên VPS hướng dẫn phần đó.

Bước 2: tổ chức thư mục để phát hành không gián đoạn

Thay vì pull code vào một thư mục và cầu mong không có gì hỏng giữa chừng, hãy để mỗi bản phát hành trong một thư mục riêng và trỏ symlink current vào bản đang chạy. Các file dùng chung (.env và storage) nằm ngoài các bản phát hành.

/var/www/app/
  releases/20261111-1200/
  shared/.env
  shared/storage/
  current -> releases/20261111-1200
sudo mkdir -p /var/www/app/{releases,shared/storage}
sudo chown -R deploy:www-data /var/www/app
mkdir -p /var/www/app/shared/storage/{app/public,framework/{cache,sessions,views},logs}

Tạo /var/www/app/shared/.env với giá trị production: APP_ENV=production, APP_DEBUG=false, APP_URL của bạn, thông tin database, QUEUE_CONNECTION=redis và CACHE_STORE=redis. Tạo APP_KEY một lần và đừng bao giờ đổi, nếu không dữ liệu mã hóa và session sẽ không đọc được nữa.

Bước 3: cấu hình Nginx

Trỏ web root vào current/public, tuyệt đối không trỏ vào thư mục gốc của dự án.

server {
    listen 80;
    server_name app.example.vn;
    root /var/www/app/current/public;
    index index.php;
    charset utf-8;

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

    location ~ ^/index\.php(/|$) {
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        fastcgi_param DOCUMENT_ROOT $realpath_root;
        include fastcgi_params;
        fastcgi_hide_header X-Powered-By;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }
}

$realpath_root rất quan trọng ở đây: nó phân giải symlink current, nên PHP-FPM thấy đường dẫn bản mới sau mỗi lần chuyển. Bật site, reload Nginx, rồi thêm HTTPS bằng Certbot như trong bài Nginx reverse proxy kèm SSL.

Bước 4: script deploy

Lưu đoạn sau thành ~/deploy.sh và chạy cho mỗi lần phát hành.

#!/usr/bin/env bash
set -euo pipefail
APP=/var/www/app
REL=$APP/releases/$(date +%Y%m%d-%H%M%S)

git clone --depth 1 [email protected]:you/app.git "$REL"
cd "$REL"
ln -s $APP/shared/.env .env
rm -rf storage && ln -s $APP/shared/storage storage
composer install --no-dev --optimize-autoloader --no-interaction
# npm ci && npm run build   # nếu build asset trên máy chủ
php artisan migrate --force
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan storage:link

ln -sfn "$REL" $APP/current
sudo systemctl reload php8.3-fpm
php artisan queue:restart

ls -1dt $APP/releases/* | tail -n +6 | xargs -r rm -rf

Từng phần làm gì:

  1. Mỗi lần phát hành một thư mục mới, nên trang đang chạy không bị đụng tới cho tới lúc chuyển.
  2. .env và storage dùng chung, nên file upload, log và session không mất qua các bản phát hành.
  3. Migration chạy trước khi chuyển. Hãy viết migration sao cho code cũ vẫn sống được trong chốc lát (thêm cột trước, xóa cột ở bản phát hành sau).
  4. ln -sfn đổi symlink trong một bước. Reload PHP-FPM để OPcache bỏ đường dẫn cũ.
  5. queue:restart báo worker làm xong job hiện tại rồi khởi động lại với code mới.
  6. Dòng cuối giữ lại năm bản phát hành. Muốn rollback, trỏ current về thư mục trước đó và reload PHP-FPM.

Cho phép user deploy reload PHP-FPM không cần mật khẩu bằng một quy tắc sudoers hẹp, chỉ cho đúng lệnh đó.

Bước 5: giữ queue worker luôn chạy với Supervisor

Các job trong queue (gửi email, thông báo, xử lý ảnh) cần một worker chạy lâu dài. Supervisor tự khởi động lại worker khi nó lỗi và khi máy chủ reboot.

[program:app-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/app/current/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600
user=deploy
numprocs=2
autostart=true
autorestart=true
stopwaitsecs=3600
redirect_stderr=true
stdout_logfile=/var/www/app/shared/storage/logs/worker.log

Lưu thành /etc/supervisor/conf.d/app-worker.conf, rồi chạy sudo supervisorctl reread && sudo supervisorctl update. Bắt đầu với một đến hai worker cho mỗi vCPU và theo dõi CPU, RAM trong biểu đồ của console.

Bước 6: scheduler

Scheduler của Laravel chỉ cần một dòng cron cho user deploy:

* * * * * cd /var/www/app/current && php artisan schedule:run >> /dev/null 2>&1

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

  • Lỗi 500 ngay sau khi deploy: xem storage/logs/laravel.log; thường là thiếu biến trong .env hoặc config đã cache cũ. Chạy php artisan config:clear rồi cache lại.
  • Permission denied trên storage: user của PHP-FPM (www-data) cần quyền ghi vào shared/storage. Cho deploy và www-data chung group và bật quyền ghi cho group.
  • Vẫn chạy code cũ: Nginx đang dùng $document_root thay vì $realpath_root, hoặc chưa reload PHP-FPM.
  • Job chạy code cũ: quên chạy queue:restart.

Tài liệu deployment chính thức của Laravel liệt kê thêm các tối ưu cho production.

Về chi phí, bạn có thể trả theo giờ cho máy staging (nhớ xóa khi không dùng, vì máy tắt vẫn bị tính phí) và trả trước 1, 3, 6 hoặc 12 tháng cho production. Số dư tài khoản tính bằng USD; khách hàng Việt Nam nạp được bằng chuyển khoản ngân hàng bằng VND.

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

Ứng dụng Laravel cần VPS cấu hình bao nhiêu?

Ứng dụng nhỏ, database chạy chung máy, chạy thoải mái trên 2 vCPU và 4 GB RAM. Tăng RAM khi queue và lưu lượng tăng; bạn có thể nâng CPU và RAM trong console.

Có cần Docker để deploy Laravel không?

Không. Nginx, PHP-FPM và Supervisor chạy trực tiếp trên máy chủ vừa đơn giản vừa nhanh. Docker hữu ích khi bạn chạy nhiều ứng dụng cần các phiên bản PHP khác nhau.

Rollback một bản phát hành lỗi thế nào?

Trỏ symlink current về thư mục phát hành trước đó và reload PHP-FPM. Chỉ rollback database khi migration không tương thích ngược.

Nên đặt ứng dụng Laravel ở đâu cho người dùng Việt Nam?

Cloudj không có máy chủ tại Việt Nam, nên máy chủ ở nước ngoài sẽ xa người dùng hơn so với một nhà cung cấp trong nước. Singapore hợp với người dùng khắp Đông Nam Á; Hong Kong và Tokyo cũng đáng thử. Tạo vài máy chủ nhỏ theo giờ ở hai hoặc ba vị trí, đo từ nhà mạng của người dùng bằng ping và mtr, rồi giữ nơi tốt nhất.

Phát hành bản đầu tiên

Tạo máy chủ trong console Cloudj, làm qua các bước này một lần, và mọi lần phát hành sau chỉ còn là chạy một script. Giá cập nhật cho mọi vị trí có trên trang bảng giá.

Triển khai máy chủ hoặc mua proxy

Chọn vị trí trong console, xem giá trực tiếp và thanh toán từ số dư tài khoản.