پیکربندی Nginx برای Laravel — php-fpm، root و بهینهسازی
server block، fastcgi_pass، try_files به index.php و headerهای امنیتی — config آماده production.
جواب کوتاه: در production، Nginx درخواستها را میگیرد و فایلهای استاتیک را خودش سرو میکند. اجرای PHP کار PHP-FPM است: Nginx هر درخواست PHP را از طریق پروتکل FastCGI — با Unix socket یا پورت TCP — به آن میدهد. برای Laravel سه چیز باید درست باشد: root روی پوشه public باشد، try_files همه مسیرها را به index.php بفرستد، و fastcgi_pass دقیقاً به همان آدرسی اشاره کند که pool مربوط در PHP-FPM روی آن گوش میدهد.
Apache با mod_php در XAMPP آسان است؛ در production اوبونتو معمولاً Nginx + php-fpm است — سبکتر و concurrent بهتر. Laravel فقط از public/ سرو شود؛ بقیه پوشهها نباید web-accessible باشند.
ساختار کلی
Client → Nginx :80/443 → fastcgi → php-fpm → Laravel bootstrap
PHP-FPM چیست و چه نقشی کنار Nginx دارد؟
PHP-FPM (FastCGI Process Manager) سرویسی است که چند پردازش PHP را آماده نگه میدارد و درخواستهای FastCGI را به آنها میدهد. Nginx خودش PHP اجرا نمیکند. وقتی درخواستی به index.php میرسد، Nginx آن را به PHP-FPM میفرستد. یکی از پردازشهای آزاد pool، Laravel را اجرا میکند و پاسخ را برمیگرداند. هر پردازش تا پایان یک درخواست مشغول همان درخواست است. پس تعداد پردازشها، تعداد درخواستهای PHP همزمان را محدود میکند.
در اوبونتو و دبیان، سرویس با نسخه PHP نامگذاری میشود (مثلاً php8.3-fpm) و فایلهای اصلی اینجاست:
/etc/php/8.3/fpm/php-fpm.conf— تنظیمات کلی سرویس/etc/php/8.3/fpm/pool.d/www.conf— تنظیمات pool: کاربر، آدرس listen، تعداد پردازشها/etc/php/8.3/fpm/php.ini— php.ini مخصوص FPM؛ جدا از/etc/php/8.3/cli/php.iniکه دستورهای artisan و worker از آن استفاده میکنند
اشتباه رایج: upload_max_filesize را در php.ini مربوط به CLI عوض میکنید و تعجب میکنید چرا سایت تغییری نکرده. بعد از تغییر تنظیمات FPM، سرویس را reload کنید:
sudo php-fpm8.3 -t
sudo systemctl reload php8.3-fpm
اتصال Nginx به PHP-FPM — Unix socket یا TCP؟
آدرسی که در listen داخل pool تعریف میکنید باید با fastcgi_pass در Nginx یکی باشد:
- Unix socket (
listen = /run/php/php8.3-fpm.sock): وقتی Nginx و PHP-FPM روی یک سرورند. کاربر Nginx باید اجازه دسترسی به فایل socket را داشته باشد (listen.ownerوlisten.group، معمولاًwww-data). در اوبونتو/var/runبه/runلینک شده و هر دو مسیر یکی است. - TCP (
listen = 127.0.0.1:9000): وقتی PHP-FPM در کانتینر یا سرور دیگری است — مثل حالت Docker Compose پایینتر. پورت را فقط در شبکه داخلی باز کنید.
server block پایه
این پیکربندی بر اساس نمونه مستندات استقرار Laravel 12 است. نام دامنه، مسیر پروژه و نسخه PHP را با سرور خودتان عوض کنید:
server {
listen 80;
listen [::]:80;
server_name example.com;
root /var/www/portfolio/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:/var/run/php/php8.3-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_hide_header X-Powered-By;
}
location ~ /\.(?!well-known).* {
deny all;
}
}
چند نکته درباره این بلاک:
location ~ ^/index\.php(/|$)فقط front controller را به PHP-FPM میدهد، نه هر فایل.phpرا. اگر فایل PHP دیگری به هر دلیلی درpublicقرار بگیرد، اجرا نمیشود. با این حال درpublicفایل PHP دیگری نگذارید.$realpath_rootمسیر واقعی را بعد از دنبال کردن symlink میدهد. در روشهای deploy که پوشهcurrentیک symlink به نسخه جدید است، این باعث میشود PHP-FPM و OPcache مسیر نسخه جدید را ببینند.- بلاک آخر دسترسی به فایلهای مخفی مثل
.envو.gitرا میبندد، بهجز.well-knownکه برای صدور گواهی SSL لازم است.
چرا public root؟
.env و storage بیرون document root — امنیت.
try_files ضروری
بدون آن routeهای Laravel 404 میدهند — همه به index.php front controller.
ترتیب کار try_files $uri $uri/ /index.php?$query_string این است: اول فایل واقعی (مثل CSS یا عکس داخل public)، بعد پوشه، و اگر هیچکدام نبود، درخواست با query string کامل به index.php میرود تا router لاراول تصمیم بگیرد.
client_max_body_size
client_max_body_size 20M;
آپلود فایل — هماهنگ با PHP upload_max_filesize.
پیشفرض Nginx یک مگابایت است و آپلود بزرگتر با خطای 413 رد میشود. در php.ini مربوط به FPM هم upload_max_filesize و post_max_size باید حداقل به همین اندازه باشند. هر کدام کمتر باشد، همان محدودیت اعمال میشود.
gzip
gzip on;
gzip_types text/plain text/css application/json application/javascript;
php-fpm pool
pm = dynamic
pm.max_children = 20
pm.start_servers = 4
تنظیم بر اساس RAM — monitor با htop.
این عددها نمونهاند، نه توصیه. منطق انتخابشان این است:
pm: حالتdynamicتعداد پردازشها را بین حداقل و حداکثر تنظیم میکند و برای بیشتر سایتها مناسب است. حالتstaticهمیشه همه پردازشها را روشن نگه میدارد و برای سرور اختصاصی با بار ثابت است. حالتondemandپردازشها را فقط هنگام درخواست میسازد و برای سایت کمترافیک یا سروری با چند pool است.pm.max_children: حافظهای که برای PHP در نظر گرفتهاید را بر میانگین حافظه هر پردازش FPM تقسیم کنید. این میانگین را روی سرور خودتان در ساعت پرترافیک اندازه بگیرید (مثلاً باps -o rss= -C php-fpm8.3). عدد بیش از حد باعث استفاده از swap میشود و عدد کم باعث صف شدن درخواستها.pm.max_requests: هر پردازش بعد از این تعداد درخواست بازسازی میشود. این جلوی رشد تدریجی حافظه را میگیرد.- timeoutها:
fastcgi_read_timeoutدر Nginx (پیشفرض ۶۰ ثانیه)،request_terminate_timeoutدر pool، وmax_execution_timeدر php.ini را با هم هماهنگ کنید. کار طولانی — مثل ساخت گزارش بزرگ یا ارسال ایمیل گروهی — جایش در queue است، نه در درخواست وب.
اگر در لاگ PHP-FPM پیام رسیدن به max_children را میبینید، یعنی درخواستها پشت pool صف شدهاند. قبل از بالا بردن عدد، ببینید چرا درخواستها طول میکشند. slowlog در pool درخواستهای کند را ثبت میکند.
با Docker Compose
fastcgi_pass app:9000;
نام service بهجای unix socket — Compose.
static asset
location ~* \.(js|css|png|jpg|webp)$ {
expires 30d;
access_log off;
}
یا CDN — بهینهسازی.
دسترسی فایلها — storage و bootstrap/cache
Laravel در دو پوشه مینویسد: storage (لاگ، کش، session، فایلهای آپلودی) و bootstrap/cache (کش پیکربندی و route). کاربری که PHP-FPM با آن اجرا میشود (مقدار user در pool، معمولاً www-data) باید در این دو پوشه اجازه نوشتن داشته باشد. بقیه پروژه فقط باید خواندنی باشد:
sudo chown -R deploy:www-data /var/www/portfolio
sudo chmod -R 775 /var/www/portfolio/storage /var/www/portfolio/bootstrap/cache
php artisan storage:link
از chmod 777 استفاده نکنید. یک دام رایج: اگر دستور artisan را با کاربر دیگری (مثلاً root) اجرا کنید، ممکن است فایل لاگ یا کش با مالکیت همان کاربر ساخته شود. بعد PHP-FPM نمیتواند در آن بنویسد و سایت خطای 500 میدهد. artisan را با همان کاربر deploy یا کاربر وب اجرا کنید.
خطاهای رایج: 403، 404، 502 و 504
اولین جایی که باید نگاه کنید /var/log/nginx/error.log است، بعد لاگ PHP-FPM و storage/logs/laravel.log.
- 403 Forbidden: معمولاً
rootبه پوشه اشتباهی اشاره میکند (پوشهای بدونindex.php)، یا کاربر Nginx اجازه عبور از یکی از پوشههای والد مسیر پروژه را ندارد. - 404 برای همه مسیرها بهجز صفحه اصلی:
try_filesوجود ندارد یا اشتباه است. - پاسخ «File not found.» و پیام
Primary script unknownدر لاگ: مسیری که باSCRIPT_FILENAMEبه PHP-FPM میرسد وجود ندارد یا PHP-FPM به آن دسترسی ندارد —rootرا بررسی کنید. - 502 Bad Gateway: Nginx به PHP-FPM وصل نمیشود. اگر در لاگ
No such file or directoryمیبینید، سرویس خاموش است یا مسیر socket با نسخه PHP نصبشده نمیخواند (مثلاً بعد از ارتقای PHP). اگرPermission deniedمیبینید، تنظیمlisten.ownerوlisten.groupرا بررسی کنید. - 504 Gateway Timeout: PHP-FPM جواب داده ولی دیرتر از
fastcgi_read_timeout. کار سنگین را به queue ببرید، نه اینکه timeout را بینهایت کنید. - 500 با صفحه خطای Laravel یا صفحه سفید: معمولاً دسترسی نوشتن در
storage، کلیدAPP_KEYیا پیکربندی کششده قدیمی. لاگ لاراول علت را نشان میدهد.
Queue worker و scheduler — بیرون از PHP-FPM
worker صف (php artisan queue:work) و scheduler از PHP-FPM و Nginx رد نمیشوند. اینها پردازشهای CLI هستند و از php.ini مربوط به CLI استفاده میکنند. worker را معمولاً Supervisor یا systemd زنده نگه میدارد و scheduler با یک کرون هر دقیقه اجرا میشود. دو نکته مرتبط با deploy: بعد از هر deploy دستور php artisan queue:restart را بزنید تا workerها کد جدید را بارگذاری کنند، و worker را با همان کاربری اجرا کنید که با دسترسی پوشه storage سازگار است. مفهوم صف و راهاندازی آن را در مقدمه Queue در Laravel توضیح دادهایم.
چکلیست بررسی بعد از deploy
sudo nginx -t && sudo systemctl reload nginx
sudo systemctl status php8.3-fpm
curl -I https://example.com
curl -I https://example.com/some-route
curl -I https://example.com/.env
php artisan optimize
php artisan about
- صفحه اصلی و یک route غیر از صفحه اصلی کد 200 بدهند، و یک آدرس ناموجود صفحه 404 خود Laravel را نشان دهد
- آدرس
/.envباید 403 یا 404 بدهد، هرگز محتوای فایل php artisan optimizeکش config، event، route و view را میسازد. بعد از هر تغییر در.envدوباره اجرایش کنیدphp artisan aboutمحیط، وضعیت debug و وضعیت کشها را نشان میدهد. در production،APP_DEBUGبایدfalseباشد- یک فایل آزمایشی آپلود کنید و مطمئن شوید از طریق
/storageقابل دسترسی است
اشتباهات
- root روی پوشه پروژه نه public
- fastcgi_param اشتباه — open_basedir error
- فراموش کردن deny hidden files
- مسیر socket در
fastcgi_passبعد از ارتقای نسخه PHP عوض نشده - تغییر php.ini مربوط به CLI بهجای FPM
جمعبندی
SSL: Let us Encrypt. Deploy کامل: Ubuntu. Cron: Scheduled tasks.