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

استضافة عدة مواقع على خادم VPS واحد باستخدام كتل الخادم في Nginx

فريق تحرير IM Host25 أكتوبر 20267 دقائق قراءة
استضافة عدة مواقع على خادم VPS واحد باستخدام كتل الخادم في Nginx

يستطيع خادم VPS واحد أن يخدم مواقع كثيرة. يقرأ Nginx اسم النطاق الذي يطلبه المتصفح ويوجّه الطلب إلى الموقع المطابق. ضبط التوجيه يستغرق عشر دقائق، أما ضبط العزل فيستغرق وقتًا أطول قليلًا، وهو الجزء الذي تتجاهله معظم الأدلة: إذا كانت كل المواقع تعمل بالمستخدم نفسه، فإن إضافة مخترقة في موقع واحد تستطيع قراءة كل المواقع الأخرى على الخادم وتعديلها.

يشرح هذا الدليل إعداد موقعين على Ubuntu 24.04 مع Nginx وPHP 8.3-FPM، لكل موقع مستخدم Linux خاص ومجموعة PHP-FPM خاصة وسجلات خاصة. نفترض أن Nginx وPHP-FPM مثبتان مسبقًا. تستخدم الأمثلة النطاقين example.com وexample.org؛ استبدلهما بنطاقاتك.

كيف يختار Nginx الموقع

كل طلب HTTP يحمل ترويسة Host، وكل اتصال HTTPS يرسل اسم النطاق أثناء مصافحة TLS (وهو ما يسمى SNI). يقارن Nginx هذا الاسم بقيمة server_name في كل كتلة خادم تستمع على المنفذ. إذا لم يتطابق شيء يذهب الطلب إلى الخادم الافتراضي لذلك المنفذ. وإذا لم تحدده بنفسك فالافتراضي هو أول كتلة حمّلها Nginx، ولهذا يصل من يكتب عنوان IP الخادم إلى موقع عميل عشوائي.

الخطة إذن: كتلة خادم لكل موقع، بالإضافة إلى كتلة افتراضية صريحة ترفض أي طلب آخر.

هيكل المجلدات

/var/www/
├── site1/          owner site1, mode 750
│   ├── public/     document root for example.com
│   ├── tmp/        PHP sessions and uploads
│   └── logs/       PHP error log
└── site2/          owner site2, mode 750
    ├── public/     document root for example.org
    ├── tmp/
    └── logs/

يحصل كل موقع على مستخدم Linux يملك ملفاته ويشغّل كود PHP الخاص به. صلاحية المجلد 750 تعني أن المالك له صلاحية كاملة، والمجموعة تستطيع القراءة، وأي مستخدم آخر، بما في ذلك مستخدمو المواقع الأخرى، لا يحصل على شيء.

الخطوة 1: إنشاء مستخدم لكل موقع

sudo adduser --disabled-password --gecos "" site1
sudo mkdir -p /var/www/site1/public /var/www/site1/tmp /var/www/site1/logs
sudo chown -R site1:site1 /var/www/site1
sudo chmod 750 /var/www/site1
sudo usermod -aG site1 www-data

كرر الخطوات نفسها لـ site2. الأمر الأخير يضيف مستخدم Nginx، وهو www-data، إلى مجموعة الموقع ليستطيع قراءة الملفات الثابتة مثل الصور وملفات CSS. لا يشغّل Nginx كود PHP أبدًا، فصلاحية القراءة هذه هي كل ما يحتاجه. ارفع ملفات الموقع باسم مستخدم الموقع (مثلًا عبر SFTP بمفتاح هذا المستخدم) وليس باسم root، لتبقى الملكية صحيحة.

الخطوة 2: مجموعة PHP-FPM لكل موقع

هذا هو جوهر العزل. المجموعة (pool) هي مجموعة من عمليات PHP لها مستخدم وsocket وحدود خاصة. أنشئ ملفًا لكل موقع:

; /etc/php/8.3/fpm/pool.d/site1.conf
[site1]
user = site1
group = site1

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

pm = ondemand
pm.max_children = 10
pm.process_idle_timeout = 30s
pm.max_requests = 500

php_admin_value[open_basedir] = /var/www/site1/
php_admin_value[upload_tmp_dir] = /var/www/site1/tmp
php_admin_value[session.save_path] = /var/www/site1/tmp
php_admin_value[error_log] = /var/www/site1/logs/php-error.log
php_admin_flag[log_errors] = on

وظيفة كل إعداد:

  • user وgroup: يعمل PHP لهذا الموقع باسم site1، فيستطيع الكتابة في ملفاته ولا يستطيع قراءة /var/www/site2.

  • listen: لكل مجموعة socket خاص يتصل به Nginx، لذلك يكون مملوكًا لـ www-data بصلاحية 0660.

  • pm = ondemand: تبدأ العمليات عند وصول الطلبات وتتوقف بعد 30 ثانية من الخمول. مع مواقع كثيرة هادئة يوفّر هذا قدرًا كبيرًا من الذاكرة، والثمن بضع أجزاء من الثانية في أول طلب بعد فترة خمول. أما لموقع واحد كثير الزيارات فإن dynamic يُبقي عمليات جاهزة.

  • pm.max_children: حد أقصى لكل موقع. الموقع الذي يتعرض لهجوم لا يستخدم أكثر من 10 عمليات، وتستمر بقية المواقع في العمل.

  • open_basedir: تمنع دوال الملفات في PHP من فتح مسارات خارج مجلد الموقع. هي طبقة ثانية وليست بديلًا عن صلاحيات الملفات.

  • session.save_path وupload_tmp_dir: افتراضيًا تشترك كل المواقع في /var/lib/php/sessions. المجلدات المنفصلة تمنع موقعًا من قراءة ملفات الجلسات الخاصة بموقع آخر.

اختبر الإعداد وأعد التحميل:

sudo php-fpm8.3 -t
sudo systemctl reload php8.3-fpm
ls -l /run/php/

يجب أن ترى socket لكل موقع. وإذا لم يكن أي موقع يستخدم المجموعة الافتراضية www، فغيّر اسم www.conf إلى www.conf.disabled لتتوقف عن استهلاك الذاكرة.

الخطوة 3: كتلة خادم لكل موقع

# /etc/nginx/sites-available/example.com
server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

    root /var/www/site1/public;
    index index.php index.html;

    access_log /var/log/nginx/site1.access.log;
    error_log  /var/log/nginx/site1.error.log;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

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

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

أنشئ الملف نفسه لـ example.org مع مسارات site2 والـ socket المسمى php8.3-fpm-site2.sock. السجلات المنفصلة لكل موقع تسرّع حل المشكلات كثيرًا: عندما يُظهر موقع أخطاء تقرأ سجلًا صغيرًا واحدًا بدل البحث في سجل مشترك.

الخطوة 4: خادم افتراضي يلتقط كل ما عداها

# /etc/nginx/sites-available/00-default
server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;
    return 444;
}

server {
    listen 443 ssl default_server;
    listen [::]:443 ssl default_server;
    ssl_reject_handshake on;
}

الرمز return 444 خاص بـ Nginx، ويغلق الاتصال دون إرسال أي رد. الروبوتات التي تفحص عناوين IP لا تحصل على شيء، ولا يظهر موقع أي عميل عند طلب اسم غير معروف. والإعداد ssl_reject_handshake (متاح منذ Nginx 1.19.4، وUbuntu 24.04 يأتي بالإصدار 1.24) يفعل الشيء نفسه مع HTTPS دون الحاجة إلى شهادة للكتلة الافتراضية.

فعّل كل شيء ثم أعد التشغيل. استخدم restart هذه المرة بدل reload لتلتقط عمليات Nginx عضوية المجموعات الجديدة من الخطوة 1:

sudo rm /etc/nginx/sites-enabled/default
sudo ln -s /etc/nginx/sites-available/00-default /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/example.org /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx

الخطوة 5: الاختبار قبل تغيير DNS

لست مضطرًا إلى انتظار DNS، فأرسل ترويسة Host بنفسك:

curl -I -H "Host: example.com" http://127.0.0.1/
curl -I -H "Host: example.org" http://127.0.0.1/
curl -I -H "Host: unknown.test" http://127.0.0.1/   # connection closed, no response

ثم تأكد أن العزل يعمل فعلًا:

sudo -u site1 cat /var/www/site2/public/index.php
# cat: /var/www/site2/public/index.php: Permission denied

إذا طبع هذا الأمر محتوى الملف فالصلاحيات خاطئة. راجع صلاحية /var/www/site2 بالأمر ls -ld.

الخطوة 6: HTTPS لكل موقع

sudo certbot --nginx -d example.com -d www.example.com
sudo certbot --nginx -d example.org -d www.example.org

يضيف Certbot أسطر الشهادة وتحويلًا من HTTP إلى HTTPS في كل كتلة خادم. يحصل كل موقع على شهادته الخاصة، فتجديد موقع أو حذفه لا يؤثر في المواقع الأخرى.

قواعد البيانات: مستخدم لكل موقع أيضًا

ينهار العزل إذا اتصلت كل المواقع بـ MariaDB بالحساب نفسه. أنشئ قاعدة بيانات ومستخدم قاعدة بيانات لكل موقع، مع صلاحيات على قاعدته فقط (GRANT ALL PRIVILEGES ON site1db.* TO 'site1'@'localhost'). عندها لا تستطيع ثغرة SQL injection في موقع واحد قراءة جداول موقع آخر.

مشكلات شائعة

اسم نطاق طويل يُفشل nginx -t

nginx: [emerg] could not build server_names_hash, you should increase server_names_hash_bucket_size: 64

أضف هذا السطر واختبر مرة أخرى:

# /etc/nginx/nginx.conf, inside http { }
server_names_hash_bucket_size 128;

خطأ 502 Bad Gateway في موقع واحد فقط

مجموعة ذلك الموقع لا تعمل، أو أن اسم الـ socket في fastcgi_pass غير مطابق. راجع /var/log/nginx/site1.error.log وsystemctl status php8.3-fpm. خطأ في صيغة ملف مجموعة واحدة يوقف خدمة FPM كلها، ولهذا يأتي php-fpm8.3 -t قبل كل إعادة تحميل.

ووردبريس لا يستطيع كتابة الملفات أو يطلب بيانات FTP

الملفات مملوكة لـ root أو لمستخدم آخر وليس لمستخدم الموقع. أصلح الملكية بالأمر sudo chown -R site1:site1 /var/www/site1.

كم موقعًا على خادم VPS واحد؟

لا يوجد رقم ثابت، والذاكرة هي الحد في الغالب. كقاعدة تقريبية، تستهلك عملية PHP نشطة لنظام إدارة محتوى معتاد بين 40 و80 ميجابايت. اجمع العمليات التي تتوقع أن تكون مشغولة في اللحظة نفسها عبر كل المواقع، وأضف قاعدة البيانات والنظام، واترك 20% على الأقل فارغة. قِس ولا تخمّن: الأمر ps -o user,rss,cmd -C php-fpm8.3 يعرض الذاكرة لكل مستخدم مجموعة.

وتذكّر أن كل المواقع تشترك في نقطة فشل واحدة. إعادة تشغيل بعد تحديث النواة أو امتلاء القرص يوقفها جميعًا معًا. عندما يصبح موقع حيويًا لعملك أو أكثر زيارة بكثير من غيره، امنحه خادمًا خاصًا.

شغّلها على IM HOST

يحتاج هذا الإعداد إلى صلاحيات root كاملة. تبدأ خطط Linux VPS من IM HOST من 5 دولارات شهريًا مع صلاحيات root كاملة على KVM، وعنوان IPv4 مخصص مع IPv6 /64، وحماية من هجمات DDoS. ولعدد أكبر من المواقع وحمل أكثر ثباتًا، يمنحك Cloud VPS معالجًا وذاكرة مخصصين مع إمكانية الترقية في أي وقت. وإذا كنت تستضيف مواقع لعملائك وتفضل أن تتولى cPanel/WHM إدارة الحسابات والعزل، فاطّلع على استضافة الموزعين مع إمكانية العلامة البيضاء. لا توجد رسوم إعداد، ومهندسو الدعم متاحون على مدار الساعة، ونقل المواقع القائمة مجاني.

المزيد من مدونتنا

اكتشف المزيد من الأدلة العملية ورؤى الخدمات من فريق IM Host.

عرض كل المقالات