خطای 502 Bad Gateway در Nginx یعنی Nginx درخواست را به PHP-FPM فرستاده، ولی جواب معتبری پس نگرفته است: یا اصلاً نتوانسته به PHP-FPM وصل شود (سوکت نیست، دسترسی ندارد، سرویس خاموش است)، یا وصل شده و پروسس PHP وسط کار مرده است. خود Nginx معمولاً سالم است؛ مشکل در اتصال یا در PHP-FPM است.

برای رفعش حدس نزنید. اول آخرین خطوط /var/log/nginx/error.log را بخوانید؛ تقریباً همیشه یک جمله‌ی مشخص آنجا هست که علت را می‌گوید. در ادامه همین مسیر را قدم‌به‌قدم می‌رویم: لاگ Nginx، وضعیت و لاگ PHP-FPM، مسیر و دسترسی سوکت، تعداد workerها، تایم‌اوت‌ها و بافرها. مثال‌ها برای Ubuntu 24.04 و PHP 8.3 است.

پاسخ کوتاه: خطای 502 در Nginx با PHP-FPM یعنی Nginx از PHP-FPM پاسخ معتبری نگرفته است. با sudo tail -n 50 /var/log/nginx/error.log علت را پیدا کنید: معمولاً PHP-FPM خاموش است، مسیر fastcgi_pass با listen در فایل pool یکی نیست، کاربر Nginx اجازه‌ی نوشتن روی سوکت را ندارد، یا worker در حین اجرا مرده است. بعد از اصلاح تنظیمات، با sudo nginx -t تست و با systemctl reload اعمال کنید.

از لاگ خطا تا پاسخ ۲۰۰: خواندن علت، راه‌اندازی دوباره‌ی PHP-FPM، بررسی سوکت و reload کردن Nginx.

502 دقیقاً یعنی چه؟

در این چیدمان، Nginx خودش PHP اجرا نمی‌کند. هر درخواستی که به فایل .php برسد، از طریق پروتکل FastCGI و دستور fastcgi_pass به PHP-FPM داده می‌شود و Nginx منتظر جواب می‌ماند. کد 502 یعنی این مرحله شکست خورده است.

دو خطای نزدیک را با 502 قاطی نکنید:

  • 504 Gateway Timeout: اتصال برقرار شده، ولی PHP-FPM در مهلت fastcgi_read_timeout جواب نداده است. یعنی PHP زنده است اما کند.
  • 500 Internal Server Error: PHP-FPM جواب داده، ولی خود جواب یک خطاست؛ مثلاً خطای fatal در کد که PHP آن را به شکل پاسخ 500 برمی‌گرداند.

پس 502 یعنی «جوابی نیامد یا جواب ناقص و خراب بود»، نه «جواب دیر آمد» و نه «جواب خطا بود».

دیاگرام ایزومتریک: مرورگر از Nginx می‌خواهد، Nginx از راه سوکت به PHP-FPM نمی‌رسد و مرورگر ۵۰۲ می‌گیرد
مشکل در خود Nginx نیست؛ اتصال بین Nginx و PHP-FPM قطع است.

قدم اول: error.log را بخوانید

bash
sudo tail -n 50 /var/log/nginx/error.log
# یا زنده، هم‌زمان با رفرش صفحه در مرورگر:
sudo tail -f /var/log/nginx/error.log

اگر برای سایت‌تان در بلاک server یک error_log جدا تعریف کرده‌اید، همان فایل را بخوانید. پیام‌هایی که برای 502 اهمیت دارند تقریباً همیشه یکی از این‌ها هستند:

پیام در error.log علت راه‌حل
connect() to unix:/run/php/php8.3-fpm.sock failed (2: No such file or directory) فایل سوکت وجود ندارد: PHP-FPM خاموش است یا مسیر در fastcgi_pass با listen در pool فرق دارد سرویس را روشن کنید و دو مسیر را یکی کنید
connect() to unix:/run/php/php8.3-fpm.sock failed (13: Permission denied) کاربر Nginx اجازه‌ی نوشتن روی فایل سوکت را ندارد listen.owner، listen.group و listen.mode را با کاربر Nginx هماهنگ کنید
connect() failed (111: Connection refused) چیزی روی آن آدرس گوش نمی‌دهد: PHP-FPM روی پورت دیگری است یا خاموش است، یا فایل سوکت باقی‌مانده و پروسسی پشتش نیست آدرس listen را چک و PHP-FPM را ری‌استارت کنید
connect() to unix:... failed (11: Resource temporarily unavailable) همه‌ی workerها مشغول‌اند و صف اتصال سوکت هم پر شده است pm.max_children را با توجه به RAM تنظیم کنید و درخواست‌های کند را پیدا کنید
upstream prematurely closed connection while reading response header from upstream worker وسط اجرای درخواست مرده است: request_terminate_timeout، کشته شدن توسط OOM killer یا segfault لاگ PHP-FPM را بخوانید و علت مرگ worker را رفع کنید
upstream sent too big header while reading response header from upstream هدرهای پاسخ (معمولاً کوکی‌های بزرگ) از بافر Nginx بزرگ‌ترند fastcgi_buffer_size و fastcgi_buffers را بزرگ‌تر کنید
upstream timed out (110: Connection timed out) while reading response header from upstream این پیام مربوط به 504 است، نه 502 بخش تایم‌اوت‌ها را ببینید

قدم دوم: آیا PHP-FPM اصلاً روشن است؟

bash
systemctl status php8.3-fpm
sudo journalctl -u php8.3-fpm -n 50 --no-pager
sudo tail -n 50 /var/log/php8.3-fpm.log

اگر سرویس failed است، معمولاً یک خطای تنظیمات در فایل pool یا php.ini جلوی بالا آمدنش را گرفته است. فایل‌های تنظیمات PHP-FPM را جدا تست کنید و بعد روشنش کنید:

bash
sudo php-fpm8.3 -t
sudo systemctl restart php8.3-fpm

یک دام رایج: بعد از ارتقای PHP (مثلاً از 8.2 به 8.3)، سرویس قدیمی حذف یا خاموش شده ولی کانفیگ Nginx هنوز به php8.2-fpm.sock اشاره می‌کند. با ls /run/php/ ببینید واقعاً چه سوکت‌هایی وجود دارد. اگر با مفهوم سرویس و journalctl راحت نیستید، مقاله‌ی ساخت سرویس systemd پایه‌اش را توضیح می‌دهد.

قدم سوم: مسیر سوکت در دو طرف یکی باشد

Nginx و PHP-FPM باید روی یک آدرس توافق داشته باشند. در pool پیش‌فرض اوبونتو:

/etc/php/8.3/fpm/pool.d/www.conf
[www]
user = www-data
group = www-data

listen = /run/php/php8.3-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

pm = dynamic
pm.max_children = 10
pm.start_servers = 3
pm.min_spare_servers = 2
pm.max_spare_servers = 4
pm.max_requests = 500

request_terminate_timeout = 120s

و طرف Nginx، یک بلاک حداقلی و درست برای یک سایت PHP (مثلاً لاراول):

/etc/nginx/sites-available/example.com
server {
    listen 80;
    server_name 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;
        fastcgi_read_timeout 120s;
    }

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

فایل snippets/fastcgi-php.conf که با Nginx اوبونتو نصب می‌شود، SCRIPT_FILENAME و پارامترهای لازم FastCGI را تنظیم می‌کند و با try_files جلوی اجرای فایل‌های ناموجود را می‌گیرد. مقدار fastcgi_pass باید دقیقاً همان listen در pool باشد. اگر PHP-FPM روی TCP گوش می‌دهد (listen = 127.0.0.1:9000)، در Nginx هم باید fastcgi_pass 127.0.0.1:9000; بنویسید.

قدم چهارم: دسترسی سوکت

سوکت یونیکس یک فایل است و Nginx برای وصل شدن به آن باید اجازه‌ی نوشتن داشته باشد. اول ببینید Nginx با چه کاربری اجرا می‌شود و سوکت مال کیست:

bash
grep -E '^\s*user' /etc/nginx/nginx.conf      # روی اوبونتو: user www-data;
ls -l /run/php/php8.3-fpm.sock
# srw-rw---- 1 www-data www-data 0 ... /run/php/php8.3-fpm.sock

خطای 13: Permission denied معمولاً وقتی پیش می‌آید که user در pool را مثلاً به deploy تغییر داده‌اند و listen.owner و listen.group را هم همراهش عوض کرده‌اند. توجه کنید: user تعیین می‌کند کد PHP با چه کاربری اجرا شود، ولی listen.owner/listen.group تعیین می‌کند چه کسی می‌تواند به سوکت وصل شود. این دو می‌توانند متفاوت باشند؛ کافی است listen.owner یا listen.group همان کاربر Nginx باشد و listen.mode = 0660. اگر listen.acl_users یا listen.acl_groups تنظیم شده باشد، این دو جای owner و group را می‌گیرند و باید آن‌ها را هم بررسی کنید.

سوکت با هر ری‌استارت PHP-FPM دوباره ساخته می‌شود، پس chmod دستی روی فایل سوکت راه‌حل نیست؛ تغییر را در فایل pool بدهید.

قدم پنجم: worker تمام شده یا مرده است

رسیدن به pm.max_children

اگر 502 فقط زیر بار یا در ساعت‌های شلوغ می‌آید، لاگ PHP-FPM را برای این پیام بگردید:

bash
sudo grep -i 'max_children' /var/log/php8.3-fpm.log | tail
# WARNING: [pool www] server reached pm.max_children setting (10), consider raising it

یعنی همه‌ی workerها مشغول‌اند و درخواست‌های تازه در صف می‌مانند. وقتی صف هم پر شود، Nginx خطای 502 می‌دهد و وقتی درخواست‌ها بیش از حد در صف بمانند، 504.

بالا بردن کورکورانه‌ی pm.max_children خطرناک است، چون هر worker حافظه می‌گیرد و اگر RAM تمام شود، سرور به swap می‌افتد یا OOM killer پروسس‌ها را می‌کشد. عدد را از روی حافظه حساب کنید: میانگین حافظه‌ی هر worker را بگیرید و RAMی را که برای PHP کنار گذاشته‌اید بر آن تقسیم کنید.

bash
ps --no-headers -o rss -C php-fpm8.3 | awk '{s+=$1; n++} END {printf "%d workers, avg %.0f MB\n", n, s/n/1024}'

مثلاً اگر میانگین 60 مگابایت است و 1.2 گیگابایت برای PHP دارید، حدود 20 worker سقف معقولی است. اگر workerها همیشه مشغول‌اند، مشکل اصلی معمولاً درخواست‌های کند است (کوئری سنگین، فراخوانی API بیرونی بدون تایم‌اوت)؛ کارهای طولانی را به صف ببرید (صف‌ها در لاراول) و مصرف منابع را پایش کنید.

worker وسط کار مرده است

پیام upstream prematurely closed connection یعنی اتصال برقرار بوده ولی پروسس PHP قبل از فرستادن جواب کامل بسته شده است. لاگ PHP-FPM معمولاً علت را می‌گوید:

text
WARNING: [pool www] child 2481, script '/var/www/example.com/public/index.php' (request: "GET /index.php") execution timed out (120.08 sec), terminating
WARNING: [pool www] child 2481 exited on signal 9 (SIGKILL) after 130.52 sec from start
WARNING: [pool www] child 2533 exited on signal 11 (SIGSEGV) after 3.10 sec from start
  • execution timed out ... terminating: درخواست از request_terminate_timeout طولانی‌تر شده و PHP-FPM worker را کشته است.
  • signal 9 بدون پیام timeout: معمولاً OOM killer کرنل پروسس را کشته است؛ با sudo dmesg -T | grep -i -E 'killed process|out of memory' مطمئن شوید.
  • signal 11 (SIGSEGV): کرش خود PHP یا یک اکستنشن؛ اکستنشن‌های اخیراً نصب‌شده یا OPcache را بررسی کنید.

خطاهای fatal و memory_limit

خطای fatal معمولی در کد، مثل Allowed memory size of ... bytes exhausted یا Maximum execution time exceeded، معمولاً به 500 یا صفحه‌ی سفید ختم می‌شود، نه 502؛ چون PHP خودش خطا را مدیریت می‌کند و پاسخی برمی‌گرداند. این خطاها را در لاگ PHP یا لاگ برنامه (مثلاً storage/logs در لاراول) ببینید. حافظه وقتی به 502 می‌رسد که memory_limit بسیار بزرگ یا -1 باشد: آن‌وقت PHP به سقف خودش نمی‌رسد، کل RAM سرور تمام می‌شود و OOM killer worker را می‌کشد. یک memory_limit معقول (مثلاً 256M) در عمل یعنی خطای قابل‌خواندن به جای مرگ بی‌صدای پروسس.

قدم ششم: تایم‌اوت‌ها و مرز 502 با 504

سه تنظیم مستقل روی طول عمر یک درخواست اثر دارند:

تنظیم کجا پیش‌فرض اگر تمام شود
fastcgi_read_timeout Nginx 60s Nginx منتظر نمی‌ماند و 504 می‌دهد
request_terminate_timeout فایل pool در PHP-FPM 0 (غیرفعال) worker کشته می‌شود و Nginx 502 می‌دهد
max_execution_time php.ini 30 (در FPM) خطای fatal در PHP و معمولاً 500

قاعده‌ی ساده: max_execution_time کوتاه‌تر از request_terminate_timeout و آن هم کوتاه‌تر یا مساوی fastcgi_read_timeout باشد. این‌طوری اسکریپت کند اول با یک خطای PHP قابل‌ردیابی متوقف می‌شود، request_terminate_timeout فقط تور ایمنی برای کدی است که در فراخوانی‌های سیستمی گیر کرده (روی لینوکس، زمان انتظار برای پایگاه‌داده یا شبکه جزو max_execution_time حساب نمی‌شود)، و Nginx هم زودتر از PHP قطع نمی‌کند.

اگر خطای واقعی 504 است، بالا بردن تایم‌اوت فقط مسکن است. درخواستی که بیش از یک دقیقه طول می‌کشد، جایش در یک job پس‌زمینه است، نه در چرخه‌ی درخواست وب.

قدم هفتم: هدر بزرگ و بافرها

پیام upstream sent too big header یعنی هدرهای پاسخ PHP در بافر اول Nginx جا نمی‌شوند. مقصر معمول کوکی‌های بزرگ سشن، تعداد زیاد Set-Cookie یا هدرهای دیباگ است. بافر را در همان location ~ \.php$ بزرگ کنید:

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

    fastcgi_buffer_size 32k;
    fastcgi_buffers 16 16k;
}

fastcgi_buffer_size بافری است که خط وضعیت و هدرها در آن خوانده می‌شوند (پیش‌فرض 4k یا 8k، بسته به پلتفرم)، و fastcgi_buffers بافرهای بدنه‌ی پاسخ است. این اعداد را بی‌حساب خیلی بزرگ نکنید؛ برای هر اتصال هم‌زمان حافظه رزرو می‌شود. اگر هدرها مدام بزرگ‌تر می‌شوند، بهتر است ببینید چرا برنامه این‌قدر کوکی می‌فرستد.

قدم آخر: تست و اعمال تغییرات

بعد از هر تغییر، قبل از اعمال تست کنید. reload اتصال‌های فعلی را نمی‌بُرد و اگر کانفیگ خراب باشد، Nginx با کانفیگ قبلی ادامه می‌دهد:

bash
sudo nginx -t && sudo systemctl reload nginx
sudo php-fpm8.3 -t && sudo systemctl reload php8.3-fpm

بعد صفحه را دوباره باز کنید و هم‌زمان tail -f روی error.log داشته باشید. اگر پیام عوض شد، یک قدم جلو رفته‌اید؛ دوباره از جدول بالا شروع کنید.

پرسش‌های رایج

چرا خطای 502 گاهی می‌آید و گاهی نه؟

خطای 502 متناوب معمولاً یعنی PHP-FPM زیر بار کم می‌آورد: به سقف pm.max_children می‌رسد، worker به خاطر کمبود RAM کشته می‌شود یا یک درخواست خاص از request_terminate_timeout رد می‌شود. لاگ PHP-FPM را در همان ساعت‌هایی که خطا آمده بخوانید.

فرق خطای 502 و 504 در Nginx چیست؟

502 یعنی Nginx اصلاً جواب معتبری از PHP-FPM نگرفته، مثلاً وصل نشده یا worker وسط کار مرده است. 504 یعنی وصل شده ولی جواب در مهلت fastcgi_read_timeout نرسیده است.

آیا ری‌استارت PHP-FPM خطای 502 را درست می‌کند؟

اگر سرویس کرش کرده یا سوکت قدیمی مانده باشد، بله و موقتاً. ولی اگر علت کمبود worker، حافظه یا کد کند باشد، خطا دوباره برمی‌گردد؛ ری‌استارت فقط زمان می‌خرد تا علت را از لاگ پیدا کنید.

سوکت یونیکس بهتر است یا پورت TCP برای PHP-FPM؟

وقتی Nginx و PHP-FPM روی یک سرورند، سوکت یونیکس ساده‌تر و کمی سبک‌تر است و از بیرون هم در دسترس نیست. TCP وقتی لازم است که PHP-FPM روی ماشین یا کانتینر دیگری اجرا شود.

چطور بفهمم Nginx با چه کاربری اجرا می‌شود؟

دستور user در /etc/nginx/nginx.conf را ببینید یا با ps -o user,cmd -C nginx کاربر پروسس‌های worker را چک کنید. روی اوبونتو این کاربر www-data است.

جمع‌بندی

502 در Nginx با PHP-FPM همیشه یعنی شکست در گرفتن جواب از PHP-FPM، و error.log تقریباً همیشه می‌گوید کدام شکست. ترتیب بررسی را نگه دارید: پیام لاگ، وضعیت سرویس و لاگ PHP-FPM، یکی بودن مسیر سوکت، دسترسی سوکت، تعداد و سلامت workerها، تایم‌اوت‌ها و در آخر بافرها. هر تغییری را با nginx -t و php-fpm8.3 -t تست کنید و با reload اعمال کنید، و اگر خطا فقط زیر بار پیدا می‌شود، علت را در حافظه و درخواست‌های کند بگردید، نه در بالا بردن کورکورانه‌ی اعداد.