DevOps

نصب و دیپلوی لاراول روی سرور مجازی — اوبونتو ۲۲.۰۴، PHP 8.3، صف با systemd، کرون و SSL

تصویر شاخص آموزش نصب و دیپلوی لاراول روی سرور مجازی: ترمینال اوبونتو ۲۲.۰۴ با PHP 8.3 و Nginx، دستورهای composer و artisan، سرویس systemd برای صف و کرون Scheduler روی سرور ابری ساعتی مهران هاست

دیپلوی لاراول روی سرور مجازی وقتی درست انجام شده که سه چیز بدون دخالت شما زنده بماند: کارگر صف، Scheduler و خودِ سایت بعد از هر git pull. این راهنما همان مسیر را روی اوبونتو ۲۲.۰۴ — جدیدترین ایمیج اوبونتو روی سرور ابری ایران — می‌سازد: PHP 8.3 از مخزن ondrej، Nginx، MariaDB، Composer، unit systemd برای صف، یک خط کرون، SSL رایگان و اسکریپت دیپلوی زیر ۲۰ خط. دسترسی Composer به Packagist از دیتاسنتر ایران هم با تاریخ اندازه‌گیری آمده، نه با حدس.

هاست لاراول بگیرم یا سرور مجازی؟ چهار چیزی که هاست اشتراکی به لاراول نمی‌دهد

لاراول روی هاست اشتراکی «بالا می‌آید»، اما چهار چیزِ لازمِ پروژه‌ی واقعی را ندارد. اول، کارگر صف: php artisan queue:work پروسه‌ای همیشه‌روشن است و هاست اشتراکی پروسه‌ی پس‌زمینه‌ی دائمی را می‌کشد. دوم، Scheduler: کرون دقیقه‌ای می‌خواهد که schedule:run را صدا بزند. سوم، Composer و SSH روی خود سرور. چهارم، نسخه‌ی PHP: لاراول ۱۳ زیر PHP 8.3 نصب نمی‌شود و نسخه‌اش روی هاست اشتراکی تصمیم ارائه‌دهنده است، نه شما.

بحث عمومی در مقایسه‌ی هاست اشتراکی و سرور مجازی است؛ برای لاراول: سایت ساده‌ی بدون Job شاید دوام بیاورد، ولی به‌محض اولین dispatch() به سرور با دسترسی root نیاز دارید. مهران هاست «هاست لاراول» به‌عنوان پلن اشتراکی نمی‌فروشد.

صورتحساب ساعتی ترس از خرید یک ماه سرور برای یک آزمایش را برمی‌دارد. یک سرور ۲ گیگابایتی بسازید، دو ساعت رویش کار کنید و اگر جواب نداد حذفش کنید — بابت همان دو ساعت حساب می‌شوید. یک نکته پیش از دکمه‌ی ساخت: کیف پول باید حداقلِ شارژ اولیه به‌علاوه‌ی ۲۴ ساعت نرخ همان سرور را داشته باشد، وگرنه پنل به صفحه‌ی کیف پول برتان می‌گرداند؛ باقی‌مانده بعد از حذف سرور اعتبار می‌ماند.

سرور مجازی لاراول: چه رم، هسته و دیسکی برای دیپلوی لازم است؟

سازنده‌ی سرور مهران هاست بازه‌ی مشخصی دارد: ۱ تا ۱۰ گیگابایت رم، ۱ تا ۸ هسته و ۱۰ تا ۱۰۰ گیگابایت دیسک NVMe با پله‌های ۵ گیگابایتی، و پیش‌فرض تا ۵ سرور برای هر حساب. ستون دیسک عمداً آمده: پنل برنامه‌های آماده را بر اساس حداقل منابع فیلتر می‌کند و Coolify روی دیسک ۱۰ گیگابایتی اصلاً در فهرست نمی‌آید:

سناریورمهستهدیسکتوضیح
تست دوساعته‌ی این راهنما۱ گیگابایت۱۱۰ گیگابایتکافی است، به شرط swap؛ composer update روی ۱ گیگ حافظه کم می‌آورد
سایت معمولی: Nginx + PHP-FPM + MariaDB روی یک سرور۲ گیگابایت۲۲۰ گیگابایتانتخاب پیش‌فرض این مقاله
با Redis، چند کارگر صف یا ترافیک بالا۴ گیگابایت۲ تا ۴۳۰ گیگابایتHorizon و چند queue:work هم‌زمان اینجا راحت‌اند
میان‌بر Coolify (بخش بعد)۴ گیگابایت (حداقل ۲)۲۳۰ گیگابایتکف مهران هاست برای Coolify ۳۰ گیگابایت دیسک است؛ حداقل رسمی خودش ۱۰ گیگابایت

ایمیج را «Ubuntu 22.04» انتخاب کنید؛ جدیدترین اوبونتوی نود ایران است و اگر «Ubuntu 22.04.1» هم در فهرست دیدید، همان jammy است. مسیرها و نام بسته‌های این مقاله مخصوص ۲۲.۰۴‌اند، که پشتیبانی استانداردش تا آوریل ۲۰۲۷ است. تحویل کمتر از ۶۰ ثانیه است؛ رمز root و کنسول تحت وب در صفحه‌ی سرور است.

کوچک شروع کنید: ارتقا از صفحه‌ی سرور و بدون نصب مجدد انجام می‌شود — فقط رو به بالا، با ۲۴ ساعت از نرخ جدید در کیف پول، و افزایش دیسک یک ری‌استارت می‌خواهد. طبق راهنمای سرور ابری ساعتی، سرور خاموش ۵۰ درصد نرخ ساعتی را می‌پردازد و فقط حذف سرور هزینه را متوقف می‌کند. قیمت ترکیب‌ها در ماشین‌حساب سرور ابری است و صفحه‌ی سرور مجازی برای توسعه‌دهنده‌ها دسترسی root، کنسول و مدل ساعتی را خلاصه کرده؛ برای کاربران بیرون ایران همان پنل سرور خارج از ایران می‌سازد.

روی سرور ۱ گیگابایتی اول swap بسازید؛ بدون آن composer update با خطای حافظه می‌میرد:

fallocate -l 2G /swapfile && chmod 600 /swapfile && mkswap /swapfile && swapon /swapfile && echo '/swapfile none swap sw 0 0' >> /etc/fstab

روی سرور تولید فقط composer install با composer.lock کامیت‌شده بزنید، نه composer update.

راه میان‌بر: Coolify یا CloudPanel یک‌کلیکی برای لاراول کی منطقی است؟

برای «git push و تمام» لازم نیست از این پنل بیرون بروید: موقع ساخت سرور یک برنامه‌ی آماده تیک می‌خورد — رایگان و دقیقاً یکی برای هر ساخت. Coolify پلتفرم متن‌باز استقرار است که Docker را خودش نصب می‌کند، با هر push دیپلوی می‌کند و SSL می‌گیرد؛ حداقل رسمی‌اش ۲ هسته، ۲ گیگابایت رم و ۱۰ گیگابایت دیسک است، ولی پنل مهران هاست آن را از ۲ گیگابایت رم و ۳۰ گیگابایت دیسک به بالا نشان می‌دهد. CloudPanel سبک‌تر است و سایت PHP را با Nginx از پنل وب می‌گرداند؛ حداقل رسمی‌اش ۲ گیگابایت رم است و پنل ما از ۱ گیگابایت رم و ۱۵ گیگابایت دیسک بازش می‌گذارد، که زیر حداقل رسمی است. مقایسه‌اش با بقیه در کنترل‌پنل‌های رایگان است.

اگر نصب خودکار انجام نشد، صفحه‌ی سرور دستوری یک‌خطی برای چسباندن در کنسول تحت وب می‌دهد. مسیر دستی وقتی بهتر است که رم ۱ یا ۲ گیگابایت باشد و نخواهید سهمش به Docker برود.

نقشه‌ی دیپلوی لاراول روی سرور مجازی اوبونتو ۲۲.۰۴: Nginx به سوکت php8.3-fpm، سرویس systemd برای queue:work، کرون schedule:run و مراحل اسکریپت دیپلوی از git pull تا artisan up
سه پروسه‌ای که بعد از دیپلوی باید زنده بمانند و جایی که هرکدام از سیستم‌عامل شروع می‌شود.

نصب لاراول روی سرور اوبونتو ۲۲.۰۴: PHP 8.3 و Nginx را چطور آماده کنم؟

اوبونتو ۲۲.۰۴ در مخزن خودش PHP 8.1 دارد و لاراول ۱۳ زیر ۸.۳ نصب نمی‌شود — composer install با «your php version (8.1.x) does not satisfy that requirement» می‌ایستد. پس PHP از مخزن ondrej می‌آید؛ گزینه‌ی الف، به‌عنوان root:

apt update && apt install -y software-properties-common
add-apt-repository -y ppa:ondrej/php
apt update
apt install -y php8.3-fpm php8.3-cli php8.3-mysql php8.3-curl php8.3-gd php8.3-mbstring \
  php8.3-xml php8.3-zip php8.3-intl php8.3-bcmath nginx mariadb-server git unzip
php -v

PHP 8.3.33 (cli) (built: Jul 30 2026 10:41:07) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.3.33, Copyright (c) Zend Technologies
    with Zend OPcache v8.3.33, Copyright (c), by Zend Technologies

عدد بعد از 8.3 با هر انتشار عوض می‌شود. این بسته‌ها اکستنشن‌های اجباری لاراول ۱۳ (Ctype، cURL، DOM، Fileinfo، Filter، Hash، Mbstring، OpenSSL، PCRE، PDO، Session، Tokenizer، XML) را پوشش می‌دهند، به‌علاوه‌ی intl و bcmath برای پکیج‌های رایج و zip و unzip برای Composer.

گزینه‌ی ب، بدون دستور: موقع ساخت سرور برنامه‌ی «پی‌اچ‌پی ۸.۳» را تیک بزنید. این یک‌کلیکی روی اوبونتو همان ppa:ondrej/php را اضافه می‌کند، همان ده بسته را نصب و php8.3-fpm را فعال می‌کند — اما وب‌سرور و دیتابیس نصب نمی‌کند و php8.3-pgsql هم در فهرستش نیست. بعد از تحویل فقط apt install -y nginx mariadb-server git unzip می‌ماند.

⚠️ «استک LEMP» یک‌کلیکی را برای لاراول ۱۳ روی ۲۲.۰۴ انتخاب نکنید: PHP خودِ توزیع یعنی 8.1 را نصب می‌کند، bcmath و intl ندارد، و یک صفحه‌ی عمومی phpinfo() در /var/www/html/info.php می‌گذارد. اگر با آن ساخته‌اید، مخزن ondrej را اضافه و بسته‌های php8.3-* را نصب کنید، fastcgi_pass را روی /run/php/php8.3-fpm.sock بگذارید و صفحه‌ی اطلاعات را پاک کنید: rm -f /var/www/html/info.php /etc/nginx/conf.d/default-php.conf && nginx -t && systemctl reload nginx. رمز root دیتابیس در گزارش نصب و /root/.my.cnf است.

درباره‌ی نسخه: PHP 8.3 حداقل لاراول ۱۳ است، اما از ابتدای ۲۰۲۶ فقط وصله‌ی امنیتی می‌گیرد، آن هم تا دی ۱۴۰۶ (۳۱ دسامبر ۲۰۲۷)؛ PHP 8.4 و 8.5 در پشتیبانی فعال‌اند و اگر پکیج‌هایتان اجازه می‌دهند، کافی است در دستورها 8.3 را عوض کنید. خود مخزن ondrej به گفته‌ی صفحه‌اش در حال ادغام در packages.sury.org است؛ اگر روزی apt update درباره‌اش هشدار داد، راهنمای مهاجرت همان صفحه را دنبال کنید. تنظیمات عمومی Nginx و فایروال در راه‌اندازی Nginx روی اوبونتو است.

آپلود پروژه لاراول روی سرور: Composer و گیت را چطور راه بیندازم؟

چهار خط نصب Composer در صفحه‌ی دانلود Composer است و خط دومش هش SHA-384 نصب‌کننده را چک می‌کند؛ این هش با هر نسخه عوض می‌شود، پس از خود صفحه بردارید، نه از مقاله. بعد از آن:

mv composer.phar /usr/local/bin/composer
composer --version

خروجی باید با «Composer version 2.» شروع شود. کد را مثل مستندات لاراول در /srv بگذارید، نه /var/www/html. برای مخزن خصوصی یک deploy key بسازید و کلید عمومی‌اش را در مخزن ثبت کنید:

git clone git@github.com:you/example.git /srv/example.ir
git config --global --add safe.directory /srv/example.ir
cd /srv/example.ir
export COMPOSER_ALLOW_SUPERUSER=1
composer install --no-dev --optimize-autoloader --no-interaction

...
Generating optimized autoload files
> Illuminate\Foundation\ComposerScripts::postAutoloadDump
> @php artisan package:discover --ansi

   INFO  Discovering packages.

Composer از دیتاسنتر ایران به Packagist و GitHub می‌رسد؟ اعداد زیر در ۲۵ شهریور ۱۴۰۵ (۱۶ سپتامبر ۲۰۲۶) از یکی از سرورهای سرویس خودمان در همان دیتاسنتر و روی همان شبکه‌ی سرورهای ابری گرفته شده‌اند. این ماشین سرور مشتری نیست؛ اعداد را «نمونه‌ی همان شبکه» بخوانید نه تضمین سرور خودتان؛ زمان‌ها گرد شده‌اند:

curl -sS -o /dev/null -m 12 -w 'HTTP %{http_code}  %{http_version}  %{time_total}s\n' https://repo.packagist.org/packages.json
HTTP 200  2  0.833s
curl -sS -o /dev/null -m 12 -w 'HTTP %{http_code}  %{http_version}  %{time_total}s\n' https://github.com/
HTTP 200  2  0.906s
curl -sS -o /dev/null -m 12 -w 'HTTP %{http_code}  %{http_version}  %{time_total}s\n' https://archive.ubuntu.com/ubuntu/
HTTP 200  1.1  0.361s

یعنی در روز عادی Packagist، GitHub و مخازن اوبونتو کد ۲۰۰ می‌دهند و میانگین RTT تا github.com حدود ۸۵ میلی‌ثانیه بود. همین سه دستور را روی سرور خودتان هم بزنید، چون مسیر رنج‌ها یکی نیست. اگر میرور دارید، آدرسش را جای MIRROR_URL بگذارید: composer config repos.packagist composer MIRROR_URL، و برای برگشت composer config --unset repos.packagist.

⚠️ در روزهای قطع سراسری اینترنت بین‌الملل، composer install از سرور ایران کار نمی‌کند، چون Packagist و GitHub مقصد خارجی‌اند. راه درست، نگه‌داشتن composer.lock در مخزن و ساختن vendor/ روی ماشین خودتان یا سرور خارج از ایران و انتقالش با rsync است. چون سرور و بازدیدکننده هر دو داخل ایران‌اند، ترافیک خود سایت از گذرگاه بین‌الملل عبور نمی‌کند.

فایل .env و مجوزهای storage را چطور درست تنظیم کنم؟

فایل .env هرگز در مخزن نیست؛ روی سرور از نمونه بسازید و چهار مقدار را عوض کنید: APP_ENV=production، APP_DEBUG=false، APP_URL=https://example.ir و مشخصات دیتابیس بخش بعد. مستندات پیکربندی لاراول درباره‌ی APP_DEBUG صریح است: روی تولید همیشه false، وگرنه صفحه‌ی خطا مقادیر حساس را به کاربر نشان می‌دهد.

cp .env.example .env
nano .env        # APP_ENV, APP_DEBUG, APP_URL, DB_*
php artisan key:generate --force

   INFO  Application key set successfully.

chown -R www-data:www-data /srv/example.ir
chmod -R 775 /srv/example.ir/storage /srv/example.ir/bootstrap/cache
php artisan storage:link

   INFO  The [public/storage] link has been connected to [storage/app/public].

PHP-FPM روی اوبونتو با کاربر www-data اجرا می‌شود (/etc/php/8.3/fpm/pool.d/www.conf) و لاراول فقط باید در storage و bootstrap/cache بنویسد؛ chmod -R 777 انجمن‌ها را نزنید. پیش از عمومی‌شدن سایت، SSH را روی کلید ببرید و ufw را فقط روی ۲۲، ۸۰ و ۴۴۳ باز کنید.

کانفیگ Nginx برای لاراول چه شکلی است؟

بلوک زیر نمونه‌ی مستندات استقرار لاراول ۱۳ است به‌علاوه‌ی default_server، نام دامنه، سوکت /run/php/… (روی ۲۲.۰۴ همان /var/run مستندات) و چهار خط بافر و حذف X-Powered-By. سه خط اصلی: root روی public، try_files به index.php و fastcgi_pass به سوکت PHP-FPM 8.3:

# /etc/nginx/sites-available/example.ir
server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name example.ir www.example.ir;
    root /srv/example.ir/public;

    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-Content-Type-Options "nosniff";

    index index.php;
    charset utf-8;

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

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    error_page 404 /index.php;

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

    location ~ /\.(?!well-known).* {
        deny all;
    }
}
ln -s /etc/nginx/sites-available/example.ir /etc/nginx/sites-enabled/
rm -f /etc/nginx/sites-enabled/default
nginx -t

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

systemctl reload nginx

حذف sites-enabled/default اجباری است، چون آن فایل هم default_server روی پورت ۸۰ دارد و nginx -t با «duplicate default server» رد می‌شود. بلوک آخر هر مسیر نقطه‌دار — .env، .git — را با 403 می‌بندد و فقط .well-known را برای SSL باز می‌گذارد.

دیتابیس MariaDB یا PostgreSQL را کجا بسازم و migrate را چطور اجرا کنم؟

MariaDB در مرحله‌ی قبل نصب شد و روی اوبونتو ۲۲.۰۴ نسخه‌ی ۱۰.۶ است؛ root آن از ترمینال root بدون رمز وارد می‌شود:

mariadb -e "CREATE DATABASE example_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
mariadb -e "CREATE USER 'example_user'@'localhost' IDENTIFIED BY 'a-strong-password';"
mariadb -e "GRANT ALL PRIVILEGES ON example_db.* TO 'example_user'@'localhost'; FLUSH PRIVILEGES;"

در .env مقدار DB_CONNECTION=mariadb (یا mysql)، DB_HOST=127.0.0.1، نام دیتابیس، کاربر و رمز را بگذارید. اگر پروژه PostgreSQL می‌خواهد، آن را همراه درایور php8.3-pgsql با apt نصب کنید — روی ۲۲.۰۴ نسخه‌ی ۱۴:

apt install -y postgresql php8.3-pgsql
sudo -u postgres psql -c "CREATE USER example_user WITH PASSWORD 'a-strong-password';"
sudo -u postgres psql -c "CREATE DATABASE example_db OWNER example_user;"
systemctl restart php8.3-fpm

با DB_CONNECTION=pgsql و DB_PORT=5432. در هر دو حالت، migrate روی سرور تولید بدون --force فقط سؤال می‌پرسد:

php artisan migrate --force

   INFO  Preparing database.

  Creating migration table ........................................ 14.21ms DONE

   INFO  Running migrations.

  0001_01_01_000000_create_users_table ............................ 31.08ms DONE
  0001_01_01_000001_create_cache_table ............................. 9.65ms DONE
  0001_01_01_000002_create_jobs_table ............................. 16.40ms DONE

در لاراول ۱۳ درایور پیش‌فرض صف و کش database است، یعنی صف بخش بعد بدون سرویس اضافه روی همین دیتابیس کار می‌کند.

صف (Queue) لاراول را چطور با systemd همیشه روشن نگه دارم؟

مستندات لاراول برای queue:work از Supervisor می‌گوید؛ systemd همان کار را بدون بسته‌ی اضافه می‌کند: با بوت بالا می‌آید و پروسه‌ی مرده را برمی‌گرداند:

# /etc/systemd/system/laravel-queue.service
[Unit]
Description=Laravel queue worker (example.ir)
After=network.target mariadb.service

[Service]
User=www-data
Group=www-data
WorkingDirectory=/srv/example.ir
ExecStart=/usr/bin/php /srv/example.ir/artisan queue:work --sleep=3 --tries=3 --max-time=3600
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable --now laravel-queue
systemctl status laravel-queue --no-pager

● laravel-queue.service - Laravel queue worker (example.ir)
     Loaded: loaded (/etc/systemd/system/laravel-queue.service; enabled; vendor preset: enabled)
     Active: active (running) since Wed 2026-09-16 10:14:03 +0330; 6s ago
   Main PID: 21437 (php)

گزینه‌ها از مستندات صف لاراول‌اند: --sleep=3 مکث روی صف خالی، --tries=3 سقف تلاش هر Job و --max-time=3600 خروج ساعتی کارگر تا نشت حافظه انباشته نشود؛ Restart=always کارگر تازه می‌سازد. برای درایور Redis، apt install -y redis-server php8.3-redis و QUEUE_CONNECTION=redis کافی است.

💡 قاعده‌ی کارگر صف: کارگر کد را یک بار در حافظه بارگذاری می‌کند و تغییرات بعدی را نمی‌بیند. بعد از هر دیپلوی php artisan queue:restart بزنید تا کارگر فعلی بعد از Job جاری خارج شود و systemd نسخه‌ی جدید را بالا بیاورد؛ مستندات لاراول ۱۳ دستور php artisan reload را هم دارد که همین سیگنال را به همه‌ی سرویس‌های بلندمدت می‌فرستد.

Scheduler لاراول را چطور با کرون فعال کنم؟

کل زمان‌بندی لاراول در routes/console.php تعریف می‌شود و روی سرور فقط یک خط کرون لازم است که هر دقیقه schedule:run را صدا بزند. آن را برای www-data بنویسید، نه root، تا خروجی کارها مالک درست داشته باشد:

crontab -u www-data -e
* * * * * cd /srv/example.ir && php artisan schedule:run >> /dev/null 2>&1

cd /srv/example.ir && sudo -u www-data php artisan schedule:run

   INFO  No scheduled commands are ready to run.

«No scheduled commands are ready to run» یعنی Scheduler سالم است و این دقیقه کاری نداشته؛ php artisan schedule:list کارها را با زمان اجرای بعدی نشان می‌دهد. نحو ستون‌های کرون در راهنمای کرون جاب لینوکس است. در حالت تعمیر (artisan down) هیچ کار زمان‌بندی‌شده‌ای اجرا نمی‌شود، مگر با evenInMaintenanceMode().

دامنه و SSL رایگان را برای پروژه لاراول چطور وصل کنم؟

یک رکورد A برای example.ir و یکی برای www کافی است. در DNS رایگان مهران هاست، TTL را موقع ساخت رکورد از ۳۶۰۰ پیش‌فرض به ۶۰ کم کنید تا انتقال سریع اثر کند و بعد از پایدارشدن سایت به ۳۶۰۰ برگردانید؛ کاربرد هر نوع رکورد در آموزش رکوردهای DNS است. Certbot روی ایمیج تازه نصب نیست و نسخه‌ی مخزن ۲۲.۰۴ قدیمی است، پس از snap نصبش کنید:

apt install -y snapd
snap install core && snap refresh core
snap install --classic certbot
ln -s /snap/bin/certbot /usr/bin/certbot

وقتی dig +short A example.ir آی‌پی سرور را برگرداند، گواهی را بگیرید:

certbot --nginx -d example.ir -d www.example.ir

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/example.ir/fullchain.pem
...
Congratulations! You have successfully enabled HTTPS on https://example.ir and https://www.example.ir

نسخه‌ی snap بلوک ۴۴۳، ریدایرکت ۸۰ و تمدید خودکار را خودش تنظیم می‌کند؛ خطاهای رایج صدور در نصب SSL رایگان با Certbot است. بعد از SSL، APP_URL باید با https شروع شود وگرنه لینک‌های asset() روی http می‌مانند. کلید شبکه‌ی توزیع محتوای مهران هاست کنار همان رکورد A اختیاری و بدون هزینه‌ی جداگانه است، ولی با روشن‌شدنش رکورد عمومی به نودهای لبه می‌رود و گواهی را خودِ CDN صادر و تمدید می‌کند؛ تمدید Certbot روی مبدأ دیگر مسیر HTTP-01 ندارد و بی‌صدا شکست می‌خورد. یکی را انتخاب کنید.

اسکریپت دیپلوی با گیت و کوتاه‌ترین زمان قطعی چطور بنویسم؟

استقرار لاراول روی VPS بعد از بار اول یعنی همین توالی، و باید در یک فایل باشد تا queue:restart از قلم نیفتد. اسکریپت زیر را در /usr/local/bin/deploy-example بگذارید، اجرایی‌اش کنید و به‌عنوان root بزنید:

#!/usr/bin/env bash
set -euo pipefail
APP=/srv/example.ir
SECRET=$(openssl rand -hex 12)
export COMPOSER_ALLOW_SUPERUSER=1
cd "$APP"

trap 'php artisan up' EXIT
php artisan down --render="errors::503" --secret="$SECRET" --retry=60
echo "bypass while down: https://example.ir/$SECRET"

git pull --ff-only
composer install --no-dev --optimize-autoloader --no-interaction
php artisan migrate --force
php artisan optimize
php artisan queue:restart
systemctl reload php8.3-fpm
chown -R www-data:www-data "$APP/storage" "$APP/bootstrap/cache"

down --secret سایت را با کد 503 می‌بندد و نشانی مخفی‌ای می‌دهد تا خودتان پیش از همه نسخه‌ی جدید را ببینید؛ --render صفحه‌ی ۵۰۳ را پیش از بارگذاری وابستگی‌ها آماده می‌کند تا موقع composer install کسی خطای خام نبیند. trap عمداً پیش از down آمده تا قطع‌شدن اسکریپت سایت را پایین نگه ندارد — ولی چاقوی دولبه است: اگر composer install یا migrate بعد از git pull شکست بخورد، artisan up سایت را با کد جدید و vendor قدیمی بالا می‌آورد و نتیجه ۵۰۰ است؛ روی پروژه‌ی حساس trap را روی ERR بگذارید. این مسیر چند ثانیه قطعی دارد؛ بی‌قطعیِ واقعی یعنی پوشه‌ی جدید و جابه‌جایی symlink. optimize کش config، event، route و view را می‌سازد:

php artisan optimize

   INFO  Caching framework bootstrap, configuration, and metadata.

  config ......................................................... 22.15ms DONE
  events .......................................................... 3.62ms DONE
  routes ......................................................... 17.80ms DONE
  views ......................................................... 131.09ms DONE

ریلود php8.3-fpm کش opcode را می‌اندازد و chown پایانی فایل‌هایی را که به‌عنوان root ساخته شد به www-data برمی‌گرداند؛ بدون آن، اولین درخواست بعد از دیپلوی موقعِ نوشتن در storage/logs/laravel.log یا storage/framework با «Permission denied» می‌شکند.

بعد از دیپلوی لاراول خطای 500 یا صفحه‌ی سفید می‌گیرم — از کجا شروع کنم؟

خطای 500 لاراول بعد از دیپلوی تقریباً همیشه یکی از پنج علت زیر است و خطای واقعی در یکی از دو فایل نوشته شده: storage/logs/laravel.log یا /var/log/nginx/error.log. اول این دو را بخوانید:

tail -n 30 /srv/example.ir/storage/logs/laravel.log
tail -n 30 /var/log/nginx/error.log
sudo -u www-data php artisan about --only=environment
  • «No application encryption key has been specified».env بدون APP_KEY است؛ php artisan key:generate --force و اگر config کش شده، php artisan config:cache.
  • صفحه‌ی سفید و «Permission denied» روی storage/logs یا bootstrap/cache — مالک فایل‌ها root است؛ chown و chmod بخش .env را تکرار کنید.
  • 403 Forbidden از Nginxroot کانفیگ روی ریشه‌ی پروژه است نه public؛ یا فایل default هنوز فعال است.
  • تغییر .env اثر نمی‌کند — کش کانفیگ قدیمی است؛ php artisan config:clear و سپس دوباره cache.
  • 502 Bad Gateway — Nginx به سوکت php8.3-fpm نمی‌رسد؛ systemctl status php8.3-fpm و تطابق مسیر سوکت با fastcgi_pass؛ عیب‌یابی کاملش در مقاله‌ی جداگانه‌ای است که در منابع پایین همین صفحه لینک شده.

APP_DEBUG را روی سرور زنده true نکنید؛ همان خطا با جزئیات در laravel.log هست. چک‌لیست عمومی 500 در رفع خطای 500 Internal Server Error است.

کل این مسیر از سرور خام تا سایت زنده با صف و کرون، روی سرور ۲ گیگابایتی کمتر از دو ساعت وقت می‌گیرد. اگر پروژه مالِ مشتری است سرور می‌ماند و ساعتی حساب می‌شود؛ اگر فقط آزمایش بود، حذفش کنید — نه خاموش.

سؤالات پرتکرار

لاراول روی هاست اشتراکی اجرا می‌شود یا حتماً سرور مجازی لازم است؟

لاراول روی هاست اشتراکی بالا می‌آید اما چهار چیز کم دارد: کارگر صف (queue:work) که باید دائم روشن بماند، Scheduler که کرون دقیقه‌ای با دسترسی SSH می‌خواهد، Composer برای نصب پکیج‌ها و کنترل نسخه‌ی PHP. برای یک وب‌سایت ساده بدون صف شاید کافی باشد، ولی به‌محض استفاده از Job، ایمیل صف‌شده یا Horizon به سرور مجازی با دسترسی root نیاز دارید. سرور ابری ساعتی برای آزمودن همین مسیر مناسب است، چون فقط ساعت‌های مصرف‌شده حساب می‌شود و بعد از تست می‌توان حذفش کرد.

لاراول ۱۳ روی اوبونتو ۲۲.۰۴ اجرا می‌شود؟ PHP پیش‌فرضش که 8.1 است

بله، اما نه با PHP پیش‌فرض. لاراول ۱۳ به PHP 8.3 یا بالاتر نیاز دارد و اوبونتو ۲۲.۰۴ به‌صورت پیش‌فرض 8.1 دارد؛ کافی است مخزن ondrej را اضافه کنید و بسته‌های php8.3-fpm و php8.3-cli را نصب کنید، همان کاری که برنامه‌ی یک‌کلیکی «پی‌اچ‌پی ۸.۳» موقع ساخت سرور در مهران هاست انجام می‌دهد. مراقب باشید «استک LEMP» یک‌کلیکی PHP توزیع یعنی 8.1 را نصب می‌کند و برای لاراول ۱۳ کافی نیست. بعد از نصب با php -v نسخه را چک کنید.

برای دیپلوی لاراول چقدر رم، CPU و دیسک لازم است؟

یک پروژه‌ی معمولی لاراول با Nginx، PHP-FPM و MariaDB روی همان سرور با ۲ گیگابایت رم و ۲۰ گیگابایت دیسک راحت اجرا می‌شود. ۱ گیگابایت برای تست کافی است، ولی composer update روی آن ممکن است حافظه کم بیاورد؛ روی سرور composer install با فایل lock بزنید یا swap بسازید. با Redis، کارگرهای صف یا ترافیک بالا ۴ گیگابایت مطمئن‌تر است. سازنده‌ی سرور مهران هاست ۱ تا ۱۰ گیگابایت رم، ۱ تا ۸ هسته و ۱۰ تا ۱۰۰ گیگابایت دیسک می‌پذیرد و بعداً بدون نصب مجدد ارتقا می‌دهید.

کارگر صف لاراول را چطور همیشه روشن نگه دارم؟

کارگر صف را به‌صورت یک سرویس systemd تعریف کنید تا با بوت سرور اجرا شود و اگر از کار افتاد خودکار برگردد: یک فایل unit با ExecStart برابر php artisan queue:work و Restart=always بسازید، سپس با systemctl enable --now فعالش کنید. بعد از هر دیپلوی هم php artisan queue:restart بزنید تا کارگر کد جدید را بارگذاری کند. Supervisor که مستندات لاراول معرفی می‌کند همین کار را می‌کند؛ systemd روی اوبونتو از قبل نصب است و بسته‌ی اضافه نمی‌خواهد.

بعد از دیپلوی لاراول خطای 500 یا صفحه‌ی سفید می‌گیرم، مشکل از کجاست؟

در بیش‌تر موارد مشکل یکی از این سه است: مجوز پوشه‌های storage و bootstrap/cache که باید متعلق به کاربر www-data باشند، نبودن APP_KEY در فایل .env که با php artisan key:generate ساخته می‌شود، یا کش کانفیگ قدیمی که با php artisan config:clear پاک می‌شود. اول فایل storage/logs/laravel.log و بعد لاگ خطای Nginx را بخوانید؛ خطای واقعی تقریباً همیشه در یکی از این دو نوشته شده است و اگر هیچ‌کدام چیزی نداشت، سراغ لاگ PHP-FPM بروید. APP_DEBUG را روی سرور زنده false نگه دارید.

آموزش‌های مرتبط

آماده‌ی تمرین عملی هستید؟

سرور ابری ساعتی مهران هاست در ۶۰ ثانیه تحویل می‌شود — تمرین کنید و فقط بابت همان ساعت‌ها پرداخت کنید. هزینه را پیش از ثبت‌نام با محاسبه‌گر برآورد کنید.