◀ العودة إلى المدونة
Linux

Nginx أم Apache: مقارنة شاملة

نشر في 18 Jul 2024· 7 min قراءة
#Nginx#Apache#Linux

Nginx أم Apache: أي خادم ويب تختار؟

يعتمد الاختيار بين Nginx وApache على حالة الاستخدام لديك. إليك مقارنة موضوعية. كلاهما مشروع مفتوح المصدر ناضج ومُصان بنشاط، ويخدمان معاً حصة كبيرة جداً من مواقع الويب. لا أحد منهما «أفضل» بشكل مطلق: فقد صُمّما في حقبتين مختلفتين وبأولويات مختلفة، ولا تزال تلك الخيارات الأصلية تفسّر نقاط قوة كل منهما حتى اليوم.

البنية

يستخدم Apache نموذجاً قائماً على العمليات/الخيوط (prefork أو worker). تُعالَج كل عملية اتصال بواسطة عملية أو خيط مخصص.

يستخدم Nginx بنية غير متزامنة قائمة على الأحداث. يمكن لعملية worker واحدة أن تعالج آلاف الاتصالات المتزامنة.

عملياً، يفوّض Apache هذا السلوك إلى وحدة تُسمى MPM (Multi-Processing Module):

  • prefork: عملية لكل اتصال، دون خيوط. هو الأكثر استهلاكاً للذاكرة، لكنه الوحيد المتوافق مع mod_php، لأن PHP لم يكن تاريخياً مضموناً للعمل الآمن مع الخيوط (thread-safe).
  • worker: عدة عمليات، لكل منها عدة خيوط. أكثر اقتصاداً في الذاكرة.
  • event: تطوير لـ worker يُسند اتصالات keep-alive الخاملة إلى خيط مخصص، بدلاً من حجز خيط لكل عميل. وهو الـ MPM الافتراضي في التوزيعات الحديثة عندما لا يكون mod_php مثبتاً.

أما Nginx فيطلق عدداً قليلاً من عمليات الـ worker (غالباً واحدة لكل نواة، عبر worker_processes auto;). تعالج كل منها آلاف الاتصالات في حلقة أحداث، بالاعتماد على epoll في Linux. لا يكلّف الاتصال الخامل سوى بضعة كيلوبايتات من الذاكرة، وهذا ما يفسر ثباته أمام عدد كبير من العملاء البطيئين أو اتصالات keep-alive.

الأداء

# Benchmark with ab (Apache Bench)
ab -n 10000 -c 100 http://localhost/

# Nginx: ~15000 req/s for static content
# Apache: ~5000 req/s for static content

هذه الأرقام مجرد مراتب تقريبية ويجب التعامل معها بحذر: فهي تتفاوت كثيراً بحسب العتاد، وحجم الملفات، وإعدادات الـ MPM، وعدد الاتصالات المتزامنة. وApache المُعَدّ مع MPM event يقلّص الفارق بشكل ملحوظ. قِس دائماً على بنيتك التحتية الخاصة، ويُفضَّل من جهاز غير الخادم المختبَر، وبأداة مثل wrk تولّد حملاً أكثر واقعية من ab.

والأهم من ذلك أن خادم الويب نادراً ما يكون عنق الزجاجة في تطبيق PHP. فزمن الاستجابة يهيمن عليه تنفيذ كود PHP واستعلامات SQL والاستدعاءات الشبكية. ويظهر الفرق بين Nginx وApache أساساً في الملفات الثابتة وتحت التزامن العالي.

إعداد PHP

Apache مع mod_php:

<VirtualHost *:80>
    DocumentRoot /var/www/html/public
    <Directory /var/www/html/public>
        AllowOverride All
    </Directory>
</VirtualHost>

مع mod_php، يُحمَّل مفسّر PHP في كل عملية Apache، بما فيها تلك التي لا تخدم سوى صورة أو ملف CSS. ويفعّل AllowOverride All ملفات .htaccess: وهذا عملي، لكن Apache يضطر حينها إلى البحث عن هذه الملفات في كل مجلد على المسار، مع كل طلب.

Nginx مع PHP-FPM:

server {
    root /var/www/html/public;
    location ~ \.php$ {
        fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

لا ينفّذ Nginx كود PHP: بل يمرّر طلبات .php إلى PHP-FPM عبر FastCGI، هنا من خلال socket من نوع Unix. لكل من الخدمتين مجمّع عمليات (pool) مستقل يُضبط حجمه بشكل منفصل. واستخدام $realpath_root بدلاً من $document_root يحلّ الروابط الرمزية، وهذا مهم مع عمليات النشر الذرّية حيث يشير current إلى إصدار جديد.

بالنسبة لتطبيق Symfony، يذهب الإعداد الموصى به أبعد من ذلك: كل طلب لا يطابق ملفاً موجوداً يمرّ عبر index.php، ولا يمكن تنفيذ أي ملف PHP آخر:

server {
    listen 80;
    server_name example.com;
    root /var/www/html/public;

    location / {
        try_files $uri /index.php$is_args$args;
    }

    location ~ ^/index\.php(/|$) {
        fastcgi_pass unix:/var/run/php/php8.3-fpm.sock;
        fastcgi_split_path_info ^(.+\.php)(/.*)$;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        fastcgi_param DOCUMENT_ROOT $realpath_root;
        internal;
    }

    location ~ \.php$ {
        return 404;
    }
}

يمنع التوجيه internal استدعاء /index.php مباشرة في عنوان URL، ويعيد الكتلة الأخيرة رمز 404 لأي ملف PHP آخر: فلا يمكن تنفيذ سكربت منسيّ داخل public/.

Apache الحديث: event وPHP-FPM

ليس Apache محكوماً بالثنائي prefork + mod_php. فهو أيضاً قادر على تفويض PHP إلى PHP-FPM عبر mod_proxy_fcgi، واستخدام MPM event. على Debian أو Ubuntu:

sudo apt install php8.3-fpm
sudo a2dismod php8.3 mpm_prefork
sudo a2enmod mpm_event proxy_fcgi setenvif
sudo a2enconf php8.3-fpm
sudo apachectl configtest && sudo systemctl restart apache2

عندها يمرّر المضيف الافتراضي (virtual host) ملفات PHP إلى socket الخاص بـ PHP-FPM. وإذا لم تكن بحاجة إلى .htaccess، فعطّلها وضع القواعد مباشرة في الإعدادات:

<VirtualHost *:80>
    ServerName example.com
    DocumentRoot /var/www/html/public

    <Directory /var/www/html/public>
        AllowOverride None
        Require all granted
        FallbackResource /index.php
    </Directory>

    <FilesMatch \.php$>
        SetHandler "proxy:unix:/run/php/php8.3-fpm.sock|fcgi://localhost"
    </FilesMatch>
</VirtualHost>

يؤدي FallbackResource الدور نفسه الذي يؤديه try_files في Nginx: أي عنوان URL لا يطابق ملفاً يُخدَم عبر index.php. بهذا الإعداد، يستهلك Apache ذاكرة أقل بكثير ويقترب من Nginx في إدارة الاتصالات.

مقارنة سريعة

المعيارNginxApache
النموذجقائم على الأحداث، غير متزامنعمليات/خيوط عبر MPM (prefork، worker، event)
PHPعبر PHP-FPM فقطmod_php أو PHP-FPM
الإعداد على مستوى المجلدلا (إعداد مركزي)نعم، عبر .htaccess
المحتوى الثابتفعّال جداًمقبول، خاصة مع event
الوكيل العكسي وموازنة الحملاستخدام أصيل وواسع الانتشارممكن عبر mod_proxy
اختبار الإعداداتnginx -tapachectl configtest

متى تختار هذا أو ذاك؟

  • Nginx: المحتوى الثابت، الوكيل العكسي (reverse proxy)، التزامن العالي، الخدمات المصغّرة (microservices)
  • Apache: عند الحاجة إلى .htaccess، أو وحدات خاصة، أو التوافق مع أنظمة قديمة
  • كلاهما: Nginx كوكيل عكسي أمام Apache للجمع بين مزايا الاثنين

كثيراً ما نصادف حالة «كلاهما» في الاستضافة المشتركة أو أثناء ترحيل تدريجي: يستمع Nginx على المنافذ العامة، ويخدم الملفات الثابتة، ويتولى TLS، ثم يمرّر الباقي إلى Apache على منفذ محلي:

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

في هذا التركيب، فعّل mod_remoteip من جهة Apache حتى ترى السجلات والتطبيق عنوان IP الحقيقي للعميل وليس عنوان Nginx. ومن جهة Symfony، صرّح بـ 127.0.0.1 في framework.trusted_proxies حتى تؤخذ ترويسات X-Forwarded-* بعين الاعتبار.

أخطاء شائعة

  • الجمع بين mod_php وMPM event: غير ممكن، إذ يرفض Apache الإقلاع. انتقل إلى PHP-FPM لاستخدام event.
  • ترجمة .htaccess بشكل أعمى: عند الترحيل إلى Nginx، يجب نقل قواعد إعادة الكتابة يدوياً إلى الإعدادات، وإلا فستختفي عمليات إعادة توجيه أو حمايات بصمت.
  • نسيان اختبار الإعدادات: نفّذ دائماً nginx -t أو apachectl configtest قبل إعادة تحميل الخدمة.
  • ضبط جانب واحد فقط: مع PHP-FPM، غالباً ما يكون pm.max_children هو ما يحدّ من الإنتاجية، وليس خادم الويب.

في البيئات ذات الحركة العالية كما هو الحال في CCM Benchmark، يُفضَّل Nginx لإدارته الفعّالة للاتصالات المتزامنة.

باختصار: لمشروع PHP أو Symfony جديد، يُعدّ Nginx مع PHP-FPM خياراً افتراضياً متيناً وبسيطاً. ويبقى Apache مناسباً عندما تكون ملفات .htaccess ضرورية، أو عندما يعتمد التطبيق على وحدات خاصة، أو عندما يتقن الفريق منظومته مسبقاً. وفي الحالتين، فإن الإعدادات، ولا سيما إعدادات PHP-FPM، هي ما سيصنع الفرق في بيئة الإنتاج.