قائمة تجهيز سيرفر Laravel للإنتاج: من الكود إلى الخادم الحيّ

ضبط PHP-FPM وOPcache وqueue workers وتخزين الإعدادات المؤقت والأذونات والأمان يرفع أداء تطبيق Laravel أضعافاً ويقفل أبواب الاختراق قبل أول زائر.
القائمة المختصرة: ما الذي يفصل سيرفر Laravel الجاهز عن غيره
سيرفر Laravel جاهز للإنتاج يعني ثمانية أشياء مضبوطة قبل أول زائر: PHP-FPM بعدد عمليات يناسب ذاكرة الخادم، OPcache مفعّل بذاكرة كافية وبدون فحص توقيت الملفات، تخزين مؤقت للإعدادات والمسارات والواجهات عبر php artisan config:cache وroute:cache وview:cache، وqueue workers تُدار بـ Supervisor لا يدوياً، وcron واحد يشغّل مجدول Laravel كل دقيقة، وأذونات ملفات دقيقة تمنح الكتابة لمجلدين فقط، وطبقة أمان تقفل APP_DEBUG وتفرض HTTPS، وأخيراً مراقبة تخبرك قبل أن يخبرك العميل.
الفرق ليس تجميلياً. تطبيق Laravel بدون OPcache وبدون config cache قد يستهلك من 40 إلى 60 مللي ثانية إضافية على كل طلب في مجرد قراءة ملفات الإعداد وإعادة تصريف الكود، وهذا وحده يضاعف زمن الاستجابة على صفحة متوسطة. مع الضبط الصحيح، ومع سيرفر داخل الرياض بزمن استجابة شبكي قرابة 8ms بدل 100 إلى 120ms لسيرفر خارجي، يصبح التطبيق سريعاً على مستوى البنية التحتية والتطبيق معاً. هذه القائمة تمشي معك بالترتيب من أعمق طبقة في PHP حتى قفل الأمان النهائي، وكل بند فيها قابل للتطبيق في دقائق على خادم سحابي VPS.
PHP-FPM: عصب الأداء الذي يتجاهله الجميع
PHP-FPM هو من ينفّذ كود PHP فعلياً خلف Nginx، وضبط مجمّع العمليات فيه يقرر كم طلباً متزامناً يتحمّله خادمك قبل أن يتوقف. الإعداد الأهم هو pm وقيمته المثلى في الإنتاج dynamic أو static حسب حجم الخادم. القاعدة العملية: احسب متوسط استهلاك عملية PHP للذاكرة لديك، وهو غالباً بين 40 و80 ميجابايت لتطبيق Laravel نموذجي، ثم اقسم الذاكرة المتاحة للـ PHP على هذا الرقم لتحصل على pm.max_children.
على خادم بذاكرة 4GB مع ترك 1GB لقاعدة البيانات ونظام التشغيل، يتبقى نحو 3GB لـ PHP. إذا كانت العملية تستهلك 60MB فالحد الأعلى المنطقي قرابة 50 عملية. تجاوز هذا الرقم يعني بدء استخدام swap وانهيار الأداء تحت الضغط بدل أن يرفض الخادم الطلب بشكل نظيف.
- pm.max_children: الذاكرة المتاحة ÷ حجم العملية الواحدة.
- pm.start_servers: نحو ربع الحد الأعلى ليكون الخادم جاهزاً عند الإقلاع.
- pm.min_spare_servers وpm.max_spare_servers: نافذة العمليات الاحتياطية الجاهزة لموجات الطلب المفاجئة.
- pm.max_requests: ضعه عند 500 حتى تُعاد العملية دورياً وتتحرر أي تسريبات ذاكرة تراكمية.
راقب سجل PHP-FPM بحثاً عن رسالة تحذير بلوغ max_children، فظهورها المتكرر يعني أن الخادم يرفض طلبات فعلية، وهنا القرار إما رفع الذاكرة عبر إضافة مورد على الخادم السحابي أو تقليل استهلاك العملية.
OPcache: أرخص تسريع ستحصل عليه
OPcache يخزّن كود PHP المُصرَّف في الذاكرة فلا يُعاد تحليله وتصريفه على كل طلب، والأثر يصل إلى تقليل زمن معالجة PHP بنسبة تتجاوز 50% في تطبيقات كثيرة الملفات مثل Laravel التي تحمّل مئات الكلاسات لكل طلب. تفعيله سطر واحد، لكن الإعداد الخاطئ يقتل نصف الفائدة.
أهم ثلاث قيم في الإنتاج: opcache.memory_consumption ابدأ من 256 ميجابايت لتطبيق كبير، وopcache.max_accelerated_files ارفعه إلى 20000 لأن Laravel وحزمه تتجاوز آلاف الملفات بسهولة، والأهم opcache.validate_timestamps=0 في الإنتاج فقط. هذه القيمة الأخيرة توقف فحص تاريخ تعديل كل ملف على كل طلب، وتربح بها أداءً ملموساً، لكنها تعني أن OPcache لن يرى تعديلاتك حتى تعيد تشغيل PHP-FPM أو تمسح الكاش يدوياً بعد كل نشر.
القاعدة الذهبية: في بيئة التطوير اترك validate_timestamps مفعّلاً، وفي الإنتاج أطفئه واجعل خطوة النشر تنفّذ إعادة تحميل PHP-FPM. من دون هذه الخطوة سترى كوداً قديماً يعمل رغم أنك نشرت الجديد، وهي من أكثر مصائد النشر إرباكاً.
تخزين الإعدادات والمسارات والواجهات مؤقتاً
Laravel يقرأ عشرات ملفات الإعداد وملفات المسارات على كل طلب ما لم تخبره أن يجمّعها في ملف واحد مُخزَّن. هذه الأوامر الأربعة جزء إلزامي من كل عملية نشر:
- php artisan config:cache: يدمج كل ملفات config في ملف واحد. بعد تشغيله يتوقف env() عن العمل خارج ملفات config، فتأكد أن كل استدعاءاتك للمتغيرات تمر عبر config() لا env() مباشرة.
- php artisan route:cache: يحوّل تعريفات المسارات إلى مصفوفة مُصرَّفة، ويقلّل زمن تسجيل المسارات في التطبيقات ذات المئات منها بشكل واضح.
- php artisan view:cache: يصرّف كل قوالب Blade مسبقاً بدل تصريفها عند أول طلب لكل صفحة.
- php artisan event:cache: يخزّن ربط الأحداث والمستمعين إن كنت تستخدم الاكتشاف التلقائي.
ضع هذه الأوامر في سكربت نشر واحد يعمل بعد كل سحب للكود، وابدأه دائماً بـ php artisan config:clear إن غيّرت متغيرات البيئة. الخطأ الشائع أن يعدّل المطور ملف .env على السيرفر وينسى إعادة بناء config cache، فيبقى التطبيق يقرأ القيم القديمة المدموجة ويقضي ساعات في تتبّع مشكلة لا وجود لها في الكود.
Queue workers: لا تشغّلها بيدك
أي عملية بطيئة مثل إرسال بريد أو إشعار WhatsApp أو معالجة صورة يجب أن تذهب إلى طابور بدل أن تعطّل الطلب. لكن worker الطابور عملية طويلة العمر، وتشغيلها يدوياً عبر php artisan queue:work في جلسة SSH يعني أنها ستموت لحظة إغلاق الجلسة أو عند أول خطأ. الحل المعتمد هو Supervisor الذي يبقي العدد المطلوب من الـ workers حياً دائماً ويعيد تشغيل أي واحد يسقط خلال ثوانٍ.
إعداد Supervisor نموذجي يحدّد numprocs بعدد الـ workers المتزامنة، ويضع autostart وautorestart على true، ويوجّه المخرجات إلى ملف سجل. نقطة حرجة: استخدم دائماً queue:work مع الخيار --max-time=3600 أو حدّ للطلبات، لأن الـ worker يحمّل الكود مرة واحدة في الذاكرة، فبعد نشر جديد لن يرى التعديلات حتى تنفّذ php artisan queue:restart الذي يأمر كل الـ workers بالتوقّف بلطف بعد إنهاء مهمتها الحالية ثم يعيد Supervisor تشغيلها بالكود الجديد.
اجعل عدد الـ workers متناسباً مع طبيعة المهام. مهام مرتبطة بشبكة خارجية بطيئة تستفيد من عدد أكبر، بينما مهام تعتمد على المعالج قد تخنق الخادم إن زادت عن عدد الأنوية المتاحة.
Cron: نبضة واحدة تشغّل كل شيء
مجدول Laravel لا يحتاج إلى عشرات مدخلات cron، بل إلى مدخلة واحدة فقط تشغّل schedule:run كل دقيقة، ثم يقرر Laravel داخلياً أي مهمة حان وقتها. المدخلة القياسية توجّه المخرجات إلى العدم وتعمل تحت مستخدم التطبيق لا تحت root، وهذه نقطة أمان مهمة كثيراً ما يخطئ فيها المبتدئون.
تأكد أن مسار PHP في مدخلة cron هو نفس إصدار PHP الذي يشغّل التطبيق، فاختلاف الإصدار بين cron وطلبات الويب من أكثر مصادر الأخطاء الصامتة. تحقق أيضاً أن المجدول يعمل فعلاً عبر php artisan schedule:list الذي يعرض كل المهام ومواعيدها القادمة، فإن لم تظهر مهمة توقعتها فالمشكلة في تعريفها لا في cron.
الأذونات وملكية الملفات: الحد الأدنى دائماً
القاعدة الأمنية الأساسية أن كود Laravel لا يحتاج صلاحية الكتابة إلا على مجلدين اثنين فقط: storage وbootstrap/cache. كل شيء آخر يجب أن يكون للقراءة والتنفيذ فقط بالنسبة لمستخدم خادم الويب. هذا التقييد وحده يحدّ بشدة من قدرة أي مهاجم على زرع كود قابل للتنفيذ حتى لو وجد ثغرة رفع ملفات.
- ملكية الملفات لمستخدم النشر ومجموعة خادم الويب معاً.
- المجلدات على 755 والملفات على 644 كقاعدة عامة.
- storage وbootstrap/cache على 775 مع انتماء المجموعة لخادم الويب.
- ملف .env على 600 ليقرأه صاحبه فقط، فهو يحوي كلمات مرور قاعدة البيانات ومفاتيح الدفع.
لا تلجأ أبداً إلى 777 كحل سريع لمشكلة أذونات. هذه الصلاحية تجعل الملف قابلاً للكتابة من أي مستخدم على النظام وتحوّل ثغرة صغيرة إلى اختراق كامل. إن واجهت خطأ كتابة فالحل الصحيح تصحيح الملكية والمجموعة لا فتح الأذونات على مصراعيها.
الأمان وقائمة الإطلاق النهائية
قبل توجيه أي زائر إلى الخادم، راجع هذه البنود الأمنية غير القابلة للتفاوض:
- APP_DEBUG=false وAPP_ENV=production. ترك debug مفعّلاً يعرض مسارات ملفاتك ومتغيرات بيئتك وأحياناً بيانات اتصال قاعدة البيانات لأي زائر عند أول خطأ.
- HTTPS إجباري مع شهادة SSL سارية وإعادة توجيه كل طلب HTTP إلى HTTPS، والاستفادة من التفعيل التلقائي لـ Let's Encrypt المجاني.
- مفتاح تطبيق فريد عبر php artisan key:generate، فهو أساس تشفير الجلسات والكوكيز.
- أذونات قاعدة بيانات محصورة: مستخدم التطبيق يملك صلاحيات على قاعدته فقط لا صلاحيات إدارية على الخادم.
- جدار حماية يغلق كل المنافذ عدا 80 و443 و22، وتقييد 22 على عناوين إدارية معروفة.
- تحديثات أمنية دورية لنظام التشغيل وPHP، مع مراجعة composer audit قبل كل نشر لكشف أي حزمة مصابة بثغرة معروفة.
أخيراً، لا سيرفر إنتاج بلا مراقبة ونسخ احتياطي. راقب استهلاك الذاكرة والمعالج وطول الطابور، واجعل النسخ الاحتياطي يومياً على الأقل. على البنية السحابية السعودية يأتي النسخ الاحتياطي مضمّناً على الباقات، وهو خط دفاعك الأخير حين يفشل كل ما سبق. اجمع هذه البنود الثمانية في سكربت نشر واحد، ونفّذه في كل مرة، فالانضباط في النشر هو ما يحوّل تطبيقاً يعمل على جهازك إلى خدمة يعتمد عليها عملاؤك.


