البداية — ليه قررنا ننقل أصلاً؟
المشروع كان شغّال من سنين على VPS بالسحابة. الموقع كان فيه أكتر من 17 ساب دومين، داشبورد إدارة مشتركة لبراندين مختلفين، وقاعدة بيانات كبيرة فيها كل بيانات العملاء والمنتجات.
القرار مجاش من فراغ. الموضوع كان مزيج من: تكلفة الاستضافة بدأت ترتفع، والرغبة في تحكم أكبر في البنية التحتية، وإننا نلاقي بيئة أكثر استقرار وأوفر.
الـ stack المستخدم قديم — PHP 7.4 مع Yii2 Framework، وعندنا نوعين من الـ Frontend Tooling: Webpack 2 مع Vue 2 لجزء، وWebpack 5 مع Sass للجزء التاني. القرار كان واضح من الأول: ننقل من غير ما نعدل كود.
تجهيز السيرفر الجديد من الصفر
أول حاجة عملناها بعد ما اشترينا السيرفر الجديد — إننا نتأكد من إصدار الـ OS. وهنا واجهنا أول مفاجأة.
السيرفر اتفتح بـ Ubuntu 24.04 — وده كان مشكلة. النسخة دي مش بتدعم MySQL 5.7 بشكل طبيعي، والـ PHP 7.4 بيحتاج workarounds كتير عليها. الحل؟ إعادة تنصيب الـ OS فوراً على Ubuntu 22.04 LTS قبل ما نعمل أي حاجة.
بعد كده، بدأنا التجهيز الحقيقي بالترتيب:
تحديث النظام وتنصيب الأدوات الأساسية
apt update, apt upgrade، وتنصيب كل الأدوات المهمة زي curl وgit وufw وfail2ban.
إنشاء يوزر منفصل عن الـ root
أفضل ممارسة أمنية — ما تشتغلش على الـ root طول الوقت. عملنا deploy user بصلاحيات sudo.
إعداد الـ Firewall (UFW)
فتحنا بس 3 بورتات: 22 للـ SSH، 80 للـ HTTP، 443 للـ HTTPS. كل حاجة تانية مقفولة.
تفعيل fail2ban
حماية تلقائية من Brute Force — لو حد غلط في الباسورد 5 مرات، اتبان تلقائياً لمدة ساعة.
ufw status verbose Status: active To Action From ── ────── ──── 22/tcp ALLOW IN Anywhere 80/tcp ALLOW IN Anywhere 443/tcp ALLOW IN Anywhere
تنصيب البيئة الكاملة
الموقع بيحتاج بيئة محددة — والهدف إننا نطابق البيئة القديمة بالظبط من غير ما نرقي حاجة:
Apache 2.4 · PHP 7.4 مع كل الـ Extensions · MySQL 8.0 بوضع التوافق مع 5.7 · Composer · Node.js نسختين مختلفتين (12 و16) عبر nvm
سبب نسختين من Node؟ — جزء من الـ Frontend (الأقدم) بيحتاج Webpack 2 مع Node 12، والجزء التاني بيحتاج Webpack 5 مع Node 16. الاتنين لازم يشتغلوا على نفس السيرفر.
MySQL 5.7 مش متاح على Ubuntu 22.04 خالص. الحل الاحترافي: تنصيب MySQL 8.0 وضبطه يتصرف زي 5.7 عن طريق إضافة إعدادات توافق في ملف الـ config — وده نجح 100% مع الكود القديم من غير أي تعديلات.
php -m | grep -E 'pdo_mysql|intl|gd|mbstring|curl|zip|opcache' gd intl mbstring opcache pdo_mysql curl zip # ✓ كل الـ Extensions الضرورية موجودة
نقل الملفات وقاعدة البيانات
الباك اب كان جاهز — ملف tar.gz واحد فيه كل ملفات الموقع، وملفات SQL منفصلة لكل قاعدة بيانات. النقل حصل مباشرة من السيرفر القديم للجديد — أسرع بكتير من إنك تنزله لوكال وترفعه تاني.
# من السيرفر القديم — نقل مباشر للسيرفر الجديد scp ~/full-backup.tar.gz root@NEW_SERVER_IP:/var/www/project/ # نقل ملفات الـ DB scp ~/database1.sql ~/database2.sql root@NEW_SERVER_IP:/var/www/project/backups/db/ # على السيرفر الجديد — فك الضغط cd /var/www/project && tar -xzvf full-backup.tar.gz
هيكل المجلدات اتنظم بشكل احترافي — كل مشروع في مجلد منفصل تحت /var/www/project/، مع مجلدات منفصلة للـ logs والـ backups.
بالنسبة لقواعد البيانات — عندنا 4 databases لمشاريع مختلفة. كل واحدة اتعمل لها:
-- مش بنستخدم root للتطبيق — كل مشروع ليه يوزر منفصل CREATE DATABASE project_db CHARACTER SET utf8mb4; CREATE USER 'project_user'@'localhost' IDENTIFIED WITH mysql_native_password BY 'StrongPassword!'; GRANT ALL PRIVILEGES ON project_db.* TO 'project_user'@'localhost'; -- ثم الاستيراد mysql -u root -p project_db < /backups/db/project.sql
لو الباسورد فيه رمز # — لازم تحطه بين quotation marks في ملف الـ .env. مثال: DB_PASSWORD="MyPass#2024" — من غير الـ quotes، كل حاجة بعد الـ # بتتجاهل وبيجي خطأ في الـ connection.
ربط الـ Domains وإعداد الـ Apache Virtual Hosts
17 ساب دومين — كل واحد محتاج ملف .conf منفصل في Apache يوجّهه للمجلد الصح. الموضوع منظم ومش معقد، بس محتاج دقة.
<VirtualHost *:80>
ServerName subdomain.domain.com
DocumentRoot /var/www/project/app/web
<Directory /var/www/project/app/web>
AllowOverride All
Require all granted
</Directory>
ErrorLog /var/www/project/logs/apache/sub-error.log
</VirtualHost>قبل ما نلمس الـ DNS — عملنا تست لوكال من خلال ملف الـ hosts على الجهاز:
# نضيف مؤقتاً لاختبار السيرفر الجديد بدون تغيير DNS NEW_SERVER_IP domain.com NEW_SERVER_IP admin.domain.com NEW_SERVER_IP api.domain.com # بعد التأكد من كل حاجة — نشيل السطور دي ونغير الـ DNS
التست عبر ملف الـ hosts خلّانا نشوف كل الـ 17 ساب دومين شغالين بشكل كامل على السيرفر الجديد — قبل ما أي مستخدم يحس بأي حاجة.
الـ SSL Certificates — الموقع يبقى Secure
بعد تحويل الـ DNS وتأكدنا إن كل حاجة وصلت للسيرفر الجديد — جه دور الـ SSL. استخدمنا Let’s Encrypt عبر Certbot، وطلبنا certificate واحد يغطي كل الـ subdomains مرة واحدة.
certbot --apache \ -d domain.com -d www.domain.com \ -d admin.domain.com \ -d api.domain.com \ -d storage.domain.com \ --email admin@domain.com \ --agree-tos --no-eff-email Successfully received certificate. Congratulations! You have successfully enabled HTTPS.
لو عندك www كـ CNAME record بيشاور على الـ root domain — Certbot مش هيقدر يعمل SSL ليه. الحل: احذف الـ CNAME واعمل A record مباشرة بالـ IP الجديد.
التحديات — الجزء اللي مش بيتقال عادةً
المشاريع الكبيرة فيها مفاجآت — وده طبيعي. الفرق بين المحترف والمبتدئ إن المحترف يعرف يتوقع المشاكل ويحلها بسرعة. إيه اللي صادفناه:
MySQL 5.7 مش متاح على Ubuntu 22.04
الحل كان MySQL 8.0 مع ضبط إعدادات التوافق — نجح بدون أي تعديل في الكود.
short_open_tag معطّل افتراضياً
الـ Views القديمة بتستخدم <? بدل <?php. تفعيل الـ short_open_tag في php.ini حلّ المشكلة مرة واحدة لكل الملفات.
الـ # في الباسورد بيكسر الـ Dotenv
الـ # في ملف الـ .env بيُعامَل كـ comment. الحل: أي باسورد فيه # لازم يتحط بين quotes.
DNS Propagation وقت الانتظار
تغيير الـ DNS محتاج وقت — من 5 دقايق لـ 48 ساعة حسب الـ TTL. الحل: نخفض الـ TTL لـ 5 دقايق قبل النقل بـ 24 ساعة.
الصور مش بتظهر بعد الـ SSL
ملفات الـ .env كانت بتشاور على storage بـ https لكن الـ SSL ما تفعّلش لسه. الترتيب مهم: SSL الأول، ثم التحقق من الصور.
نسختين من Node على نفس السيرفر
Webpack 2 بيحتاج Node 12، وWebpack 5 بيحتاج Node 16. الحل: nvm لإدارة الإصدارات وسهل التبديل بينها.
النتيجة النهائية
في النهاية — الموقع شغّال بالكامل على السيرفر الجديد. كل الخدمات مربوطة. كل الساب دومينات آمنة بـ HTTPS. والأهم: ما اتغيّر سطر كود واحد.
نقل موقع ضخم مش بيعتمد على القوة — بيعتمد على الترتيب والتخطيط والاختبار قبل التحويل. لو اتبعت الخطوات بالترتيب الصح واختبرت كل حاجة لوكال قبل ما تلمس الـ DNS — هتعدي المشروع من غير ما أي مستخدم يحس بأي حاجة.