رفع مشکل

خطای 504 Gateway Timeout در Nginx؛ از تشخیص درخواست کند تا رفع ریشه‌ای

تهیه: کلودیفای (نگارش با کمک هوش مصنوعی) · انتشار: · زمان مطالعه: ۷ دقیقه

504504GatewayTimeoutرفع مشکلخطای 504 Gateway Timeoutدر Nginx؛ از تشخیص درخواستکند تا رفع ریشه‌ایمجلهٔ کلودیفایclodify.net/blog
پاسخ کوتاه

خطای 504 یعنی Nginx درخواست را به بک‌اند فرستاده اما در مهلت تعیین‌شده پاسخی نگرفته است. ابتدا در error.log پیام upstream timed out و آدرس درخواست کند را پیدا کنید، سپس بررسی کنید بک‌اند به دلیل کوئری سنگین دیتابیس، کمبود منابع، تمام شدن workerها یا فراخوانی یک سرویس خارجی کند شده است. افزایش proxy_read_timeout فقط برای کارهای واقعاً طولانی مناسب است؛ راه‌حل اصلی سریع‌تر کردن یا پس‌زمینه‌ای کردن آن کار است.

خطای 504 دقیقاً چه می‌گوید؟

وقتی Nginx به‌عنوان Reverse Proxy جلوی یک برنامه قرار دارد، هر درخواست کاربر را به بک‌اند می‌فرستد و منتظر پاسخ می‌ماند. اگر بک‌اند در مهلت مشخص هیچ پاسخی برنگرداند، Nginx منتظر نمی‌ماند و به کاربر صفحهٔ 504 Gateway Timeout نشان می‌دهد. به بیان ساده: «برنامهٔ پشت من زنده است، اما خیلی دیر جواب می‌دهد».

این خطا با 502 تفاوت مهمی دارد. در 502 معمولاً بک‌اند اصلاً در دسترس نیست یا اتصال را رد می‌کند؛ اما در 504 اتصال برقرار شده و درخواست هم ارسال شده، فقط پاسخ به‌موقع نرسیده است. اگر صفحهٔ شما 502 نشان می‌دهد، راهنمای رفع خطای 502 مناسب‌تر است.

504 در کجای مسیر رخ می‌دهد
درخواستproxy_passکوئریکاربردرخواست صفحهNginxمنتظر تا proxy_read_timeoutبک‌انددر حال پردازش طولانیدیتابیسکوئری کند یا قفلدرخواستproxy_passکوئریکاربردرخواست صفحهNginxمنتظر تا proxy_read_timeoutبک‌انددر حال پردازش طولانیدیتابیسکوئری کند یا قفل
شکل ۱ Nginx اتصال را برقرار کرده اما بک‌اند به‌موقع جواب نمی‌دهد؛ پس از پایان مهلت، کاربر 504 می‌بیند.

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

اولین و مهم‌ترین منبع، لاگ خطای Nginx است. هر 504 در آن یک خط ثبت می‌کند:

sudo tail -n 50 /var/log/nginx/error.log | grep -i "timed out"

پیام معمول چیزی شبیه این است:

upstream timed out (110: Connection timed out) while reading response header from upstream,
client: 203.0.113.7, server: example.com, request: "GET /reports/export HTTP/1.1",
upstream: "http://127.0.0.1:3000/reports/export"

سه نکته در این پیام هست:

  • while reading response header: بک‌اند اتصال را پذیرفته اما پاسخ نداده است؛ مشکل در خود برنامه است.
  • while connecting to upstream: حتی برقراری اتصال طول کشیده است؛ معمولاً بک‌اند آن‌قدر شلوغ است که اتصال جدید را نمی‌پذیرد، یا upstream روی سرور دیگری است و مسیر شبکه مشکل دارد.
  • request: آدرس دقیق درخواست کند. اگر همهٔ 504ها روی یک یا دو آدرس است، مشکل در همان بخش برنامه است؛ اگر روی همهٔ آدرس‌هاست، کل بک‌اند گرفتار است.

قدم دوم: درخواست را مستقیم به بک‌اند بفرستید

برای جدا کردن نقش Nginx، همان درخواست را از روی سرور مستقیم به بک‌اند بزنید و زمانش را اندازه بگیرید:

curl -o /dev/null -s -w "%{http_code} %{time_total}\n" http://127.0.0.1:3000/reports/export

اگر این درخواست هم طولانی است، Nginx فقط پیام‌رسان است و باید بک‌اند را درست کنید. اگر مستقیم سریع جواب می‌دهد اما از طریق Nginx نه، به تنظیمات پروکسی، DNS بک‌اند یا شبکهٔ بین Nginx و بک‌اند مشکوک شوید.

502 یا 504؟
502 Bad Gateway
  • بک‌اند خاموش یا پورت اشتباه
  • اتصال رد می‌شود یا پاسخ نامعتبر است
  • خطا فوری نمایش داده می‌شود
  • در لاگ: connect() failed یا refused
در برابر
504 Gateway Timeout
  • بک‌اند روشن ولی کند
  • اتصال برقرار و درخواست ارسال شده
  • خطا پس از پایان مهلت نمایش داده می‌شود
  • در لاگ: upstream timed out
شکل ۲ زمان نمایش خطا و پیام error.log سریع‌ترین راه تشخیص این دو از هم است.

علت‌های رایج کندی بک‌اند

کوئری سنگین دیتابیس. شایع‌ترین علت. یک گزارش، جست‌وجو یا خروجی گرفتن که روی جدول بزرگ بدون ایندکس اجرا می‌شود، با بزرگ شدن داده‌ها هر روز کندتر می‌شود تا جایی که از مهلت Nginx عبور کند. لاگ کوئری‌های کند دیتابیس را فعال کنید و برای ستون‌هایی که در شرط‌ها و مرتب‌سازی استفاده می‌شوند ایندکس بسازید.

تمام شدن workerهای بک‌اند. برنامه‌هایی مثل PHP-FPM، Gunicorn یا Puma تعداد محدودی worker دارند. اگر چند درخواست کند همهٔ workerها را اشغال کنند، درخواست‌های سادهٔ بعدی هم در صف می‌مانند و 504 می‌گیرند. در این حالت 504 روی همهٔ آدرس‌ها دیده می‌شود، حتی صفحه‌های سبک. در لاگ PHP-FPM پیامی دربارهٔ رسیدن به max_children نشانهٔ همین مشکل است.

کمبود منابع سرور. وقتی CPU کامل اشغال است یا رم تمام شده و سیستم به swap افتاده، همه‌چیز کند می‌شود. با top یا htop وضعیت را ببینید؛ روند کامل در رفع مصرف بالای CPU و بررسی مصرف CPU و رم آمده است.

فراخوانی سرویس خارجی. اگر برنامه در حین پاسخ به کاربر، یک API خارجی، درگاه پرداخت یا سرویس ارسال پیامک را صدا می‌زند و آن سرویس کند یا در دسترس نیست، درخواست کاربر هم معطل می‌ماند. برای همهٔ فراخوانی‌های خارجی در کد، مهلت کوتاه و مشخص تعیین کنید تا یک سرویس بیرونی کل سایت را از کار نیندازد.

قفل شدن دیتابیس یا فایل. گاهی یک تراکنش طولانی جدولی را قفل می‌کند و بقیهٔ درخواست‌ها پشت آن منتظر می‌مانند. فهرست کوئری‌های در حال اجرا در دیتابیس معمولاً مقصر را نشان می‌دهد.

تنظیم مهلت‌ها در Nginx

مهلت‌های پیش‌فرض Nginx برای پروکسی معمولاً شصت ثانیه است. برای مسیرهایی که واقعاً کار طولانی انجام می‌دهند، مثلاً خروجی گرفتن از گزارش بزرگ، می‌توانید مهلت را فقط برای همان مسیر افزایش دهید:

location /reports/export {
    proxy_pass http://127.0.0.1:3000;
    proxy_connect_timeout 10s;
    proxy_send_timeout 120s;
    proxy_read_timeout 300s;
}

برای PHP-FPM معادل آن fastcgi_read_timeout است. پس از تغییر، با sudo nginx -t تست و با sudo systemctl reload nginx اعمال کنید. اگر با ساختار فایل‌های پیکربندی آشنا نیستید، آموزش پیکربندی Nginx را ببینید.

دو نکتهٔ مهم:

  • مهلت را برای کل سایت بالا نبرید. این کار مشکل را پنهان می‌کند؛ کاربر به‌جای خطا، مدت طولانی منتظر صفحهٔ سفید می‌ماند و workerهای بک‌اند بیشتر اشغال می‌شوند.
  • مهلت‌ها در همهٔ لایه‌ها باید هماهنگ باشند. اگر بک‌اند خودش مهلت کوتاه‌تری دارد، مثلاً max_execution_time در PHP یا timeout در Gunicorn، برنامه زودتر قطع می‌شود و افزایش مهلت Nginx اثری ندارد.
روند رفع 504
  1. خواندن error.log

    پیام timed out و آدرس دقیق درخواست کند را پیدا کنید.

  2. تست مستقیم بک‌اند

    با curl روی 127.0.0.1 زمان پاسخ را بدون Nginx بسنجید.

  3. یافتن گلوگاه

    کوئری کند، workerهای پر، کمبود CPU و رم یا سرویس خارجی کند.

  4. اصلاح یا صف پس‌زمینه

    ایندکس، بهینه‌سازی کد یا انتقال کار طولانی به worker پس‌زمینه.

  5. تنظیم مهلت فقط در صورت نیاز

    proxy_read_timeout را فقط برای همان مسیر و هماهنگ با بقیهٔ لایه‌ها افزایش دهید.

شکل ۳ افزایش مهلت آخرین قدم است، نه اولین.

اگر سایت پشت CDN است

وقتی یک CDN جلوی Nginx قرار دارد، یک لایهٔ دیگر هم با مهلت خودش وجود دارد. CDNها معمولاً سقف زمانی مشخصی برای انتظار از سرور اصلی دارند و اگر Nginx دیرتر پاسخ دهد، CDN خودش خطای 504 یا معادل آن را نمایش می‌دهد؛ حتی اگر مهلت Nginx را بالا برده باشید. برای تشخیص اینکه خطا از کدام لایه است، به ظاهر صفحهٔ خطا و هدرهای پاسخ دقت کنید یا درخواست را مستقیم به آی‌پی سرور بفرستید. خطاهای این لایه در مقالهٔ خطاهای 5xx در CDN بررسی شده است.

راه‌حل ریشه‌ای: کار طولانی را از درخواست جدا کنید

اگر یک عملیات ذاتاً طولانی است، مثل ساختن گزارش بزرگ، پردازش فایل یا ارسال انبوه ایمیل، بهترین راه این است که اصلاً داخل درخواست HTTP انجام نشود. الگوی رایج این است:

  1. کاربر درخواست را ثبت می‌کند و برنامه بلافاصله پاسخ «در حال پردازش» می‌دهد.
  2. کار در یک صف قرار می‌گیرد و یک worker پس‌زمینه آن را انجام می‌دهد.
  3. وقتی کار تمام شد، نتیجه ذخیره می‌شود و کاربر با بررسی وضعیت یا اعلان آن را دریافت می‌کند.

ابزارهایی مثل Celery برای پایتون، Sidekiq برای روبی یا BullMQ برای Node.js همین کار را انجام می‌دهند و معمولاً از Redis به‌عنوان صف استفاده می‌کنند. با این الگو، مهلت‌های Nginx دیگر هیچ‌وقت به کار طولانی نمی‌رسند.

چک‌لیست جمع‌بندی

  • در error.log پیام timed out و آدرس درخواست را پیدا کنید.
  • همان درخواست را مستقیم به بک‌اند بزنید و زمانش را ببینید.
  • کوئری‌های کند، workerهای اشغال‌شده، منابع سرور و فراخوانی‌های خارجی را بررسی کنید.
  • فقط برای مسیرهای واقعاً طولانی مهلت را افزایش دهید و لایه‌های دیگر را هم هماهنگ کنید.
  • کارهای طولانی را به صف پس‌زمینه منتقل کنید.

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

سؤالات متداول

تفاوت خطای 502 و 504 چیست؟

در 502 بک‌اند در دسترس نیست یا پاسخ نامعتبر می‌دهد؛ مثلاً سرویس متوقف شده است. در 504 بک‌اند درخواست را دریافت کرده اما در مهلت تعیین‌شده پاسخ نداده است؛ یعنی زنده ولی کند است.

آیا افزایش proxy_read_timeout مشکل را حل می‌کند؟

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

چرا 504 فقط گاهی رخ می‌دهد؟

معمولاً به بار وابسته است: در ساعت‌های شلوغ workerهای بک‌اند پر می‌شوند یا دیتابیس زیر فشار کند می‌شود. لاگ‌ها را با زمان رخداد مقایسه کنید و مصرف منابع را در همان ساعت‌ها بررسی کنید.

مهلت Nginx را بالا بردم اما باز 504 می‌گیرم؛ چرا؟

احتمالاً لایهٔ دیگری مهلت کوتاه‌تری دارد: CDN جلوی سرور، تنظیم timeout در خود برنامه یا PHP-FPM. همهٔ لایه‌ها را بررسی کنید؛ کوتاه‌ترین مهلت تعیین‌کننده است.

آماده‌اید؟ سرورتان را بسازید.

از ۱٬۲۰۰ تومان در ساعت · پرداخت ریالی

سرورتان را همین حالا بسازید.

ساعتی بپردازید. هر وقت خواستید حذف کنید.