SSD Nodes Learn 🎉 VPS من $5.50/شهر
الأدلة Matt Connorبقلم Matt Connor

Nginx أم Caddy أم Traefik؟ اختر البروكسي المناسب

لديك VPS واحد وعنوان IP عامًا؟ قارن Nginx وCaddy وTraefik في شهادات TLS، تكلفة إعداد كل تطبيق، WebSockets وتوجيه Docker.

Nginx مقابل Caddy مقابل Traefik: الإجابة المختصرة

تؤدي Nginx وCaddy وTraefik الوظيفة نفسها كـ Reverse Proxy: الاستماع على المنفذ 443، وقراءة اسم المضيف في كل طلب، وتمريره إلى الخدمة الصحيحة على VPS. يمكن لأي واحد منها وضع أربع تطبيقات مستضافة ذاتياً خلف عنوان IP عام واحد، وجميعها سريعة بما يكفي بحيث تكون تطبيقاتك هي الجزء الأبطأ. يكمن الاختلاف في طريقة حصول كل منها على شهادة TLS (أمان طبقة النقل)، وفي مقدار الإعداد الذي يتطلبه كل تطبيق إضافي. ويظهر الاختلاف الآخر لاحقاً، عندما تحتاج إلى شيء لا تتناوله البرامج التعليمية الشائعة.

اختر Caddy إذا أردت أن يتولى HTTPS العمل نيابةً عنك وكانت خدماتك تطبيقات ويب عادية. اختر Traefik إذا كانت كل خدماتك تعمل في Docker Compose وتضيف خدمة جديدة كل بضعة أسابيع. اختر Nginx إذا كنت تستخدمه بالفعل، أو إذا كنت تحتاج إلى تخزين استجابات مؤقتاً، أو شهادات العميل، أو تمرير TCP خام، أو إعداد كبير قائماً تفضّل عدم إعادة كتابته.

How does each one get a TLS certificate?

This axis decides it for most people, so start here. All three end up holding the same certificate from the same authority. The work you do to get there is not the same.

Caddy asks for the certificate because you named a hostname. Write app.example.com as a site address and Caddy requests a certificate over ACME (automatic certificate management environment) from Let's Encrypt, falls back to ZeroSSL if that fails, serves the HTTP to HTTPS redirect on port 80, and renews on its own. There is no second tool and no timer to check. Certificates live in the caddy user's data directory, /var/lib/caddy/.local/share/caddy on a package install, so add that path to your backups or accept a fresh issuance after a rebuild. For a hostname that is not public, tls internal signs with Caddy's own local certificate authority instead. That gets you the same thing as creating a self-signed certificate on Ubuntu, with the renewal handled for you.

Nginx has no ACME client. Certbot obtains the certificate, and its --nginx plugin rewrites your server block to add the 443 listener and the redirect. Renewal runs from a systemd timer the package installs, so there are two moving parts and two things to verify: systemctl list-timers | grep certbot shows the timer exists, and sudo certbot renew --dry-run proves the renewal path still works. The step by step is in Certbot on Ubuntu 24.04 with Nginx, and the same tool covers a wildcard certificate through the DNS-01 challenge when you have more subdomains than you want to list.

Traefik carries its own ACME client. You configure one certificate resolver in the static configuration, and every router can then use it. All of the state, account key and certificates included, sits in a single acme.json file. Traefik refuses to use that file if it is readable by anyone but its owner, and it tells you so before it drops the resolver:

The ACME resolver "le" is skipped from the resolvers list because: unable to get ACME account: permissions 660 for /letsencrypt/acme.json are too open, please use 600

Mount a directory and let Traefik create the file itself. Create it first with touch and it inherits your umask, which is how most people meet that line.

One thing is true for all three. The HTTP-01 challenge needs port 80 reachable from the internet, because the certificate authority connects back to it. Open 443 only and issuance fails in a way that reads like a DNS fault.

مهمة توجيه التطبيقين نفسيهما في 3 إعدادات

المطلوب: يوجّه app.example.com إلى خدمة على 127.0.0.1:8080، ويوجّه files.example.com إلى خدمة على 127.0.0.1:8081، وكلاهما عبر HTTPS. إليك الإعداد الكامل في كل Proxy، حتى يظهر الفرق في مقدار التفصيل بدلاً من الاكتفاء بوصفه.

Nginx

# /etc/nginx/sites-available/app.example.com
server {
    listen 80;
    server_name app.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;
    }
}

اربطه بعد ذلك، واختبر الإعداد، ثم أعد التحميل وأضف الشهادة.

sudo ln -s /etc/nginx/sites-available/app.example.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
sudo certbot --nginx -d app.example.com

إن طباعة syntax is ok وtest is successful عبر nginx -t هي الفحص الذي يجب تشغيله قبل كل إعادة تحميل. التطبيق الثاني يستخدم الكتلة نفسها، مع تغيير اسم المضيف والمنفذ. أسطر proxy_set_header ليست للزينة: عندما يشير proxy_pass إلى عنوان، يرسل nginx Host: 127.0.0.1:8080 إلى الخدمة الخلفية افتراضياً. لذلك، إذا كان التطبيق ينشئ عناوين URL مطلقة بالاعتماد على ترويسة Host، فسيوجّه المستخدمين إلى localhost.

Caddy

app.example.com {
	reverse_proxy 127.0.0.1:8080
}

files.example.com {
	reverse_proxy 127.0.0.1:8081
}
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy

هذا هو الملف بالكامل. يتولى reverse_proxy ضبط X-Forwarded-For وX-Forwarded-Proto وX-Forwarded-Host بنفسه. وبالافتراضي، يتجاهل كل ما أرسله العميل في هذه الترويسات، لذلك لا يستطيع الطلب تضليل الخدمة الخلفية بشأن مصدره. تنتج الشهادات وإعادة التوجيه من المنفذ 80 والتجديد كلها عن عنواني الموقع. ولا يوجد في الملف أي شيء آخر يطلب هذه الوظائف.

Traefik

يحتاج Traefik إلى إعداد ثابت قبل أن يوجّه أي طلب. وبصفته خدمة Compose، مع وسم الصورة الحالي حتى August 2026:

services:
  traefik:
    image: traefik:v3.7
    command:
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.le.acme.email=you@example.com"
      - "--certificatesresolvers.le.acme.storage=/letsencrypt/acme.json"
      - "--certificatesresolvers.le.acme.httpchallenge.entrypoint=web"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./letsencrypt:/letsencrypt

يحمل كل تطبيق بعد ذلك إعداد التوجيه الخاص به ضمن labels في ملف Compose الخاص به:

    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.app.rule=Host(`app.example.com`)"
      - "traefik.http.routers.app.entrypoints=websecure"
      - "traefik.http.routers.app.tls.certresolver=le"
      - "traefik.http.services.app.loadbalancer.server.port=8080"

يشير loadbalancer.server.port إلى المنفذ داخل الحاوية، وليس إلى منفذ منشور، لأن Traefik يصل إلى الحاوية عبر شبكة Docker مشتركة. ولا يحتاج التطبيق إلى سطر ports: على الإطلاق. وهذه هي الفائدة الفعلية: لا يُنشر إلا Traefik. يتوفر الإعداد الكامل، بما في ذلك الشبكة المشتركة والـmiddleware الخاص بإعادة التوجيه، في توجيه عدة تطبيقات باستخدام Traefik وDocker Compose.

ما مقدار الإعداد الذي يتطلبه كل تطبيق إضافي؟

ChartNon-blank config lines for the same two-app routing job
The data behind this chart
[
  {
    "tool": "Nginx",
    "proxy_setup_lines": 0,
    "lines_per_app": 11
  },
  {
    "tool": "Caddy",
    "proxy_setup_lines": 0,
    "lines_per_app": 3
  },
  {
    "tool": "Traefik",
    "proxy_setup_lines": 17,
    "lines_per_app": 5
  }
]

استناداً إلى الكتل السابقة، يتكون مقطع خادم Nginx من 11 أسطراً غير فارغ، وتعيد كتابته لكل اسم مضيف. يتكون مقطع الموقع في Caddy من 3 أسطراً. يتطلب Traefik 17 أسطراً من الإعداد الثابت قبل أن يخدم طلباً واحداً، ثم يتطلب 5 تسمية لكل تطبيق.

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

كما أن عدّ الأسطر يعطي Nginx أفضلية ظاهرية. تحتاج كل كتلة إلى رابط رمزي، وnginx -t، وإعادة تحميل، وتشغيل certbot، بينما يتطلب تعديل Caddy إعادة تحميل واحدة، ولا يتطلب تعديل Traefik أي أمر على الإطلاق. تعيد الأدوات الثلاث تحميل الإعدادات من دون قطع الاتصالات النشطة. والفرق هو عدد الخطوات المنفصلة التي يجب أن تتذكرها في الساعة 1 صباحاً.

أيٌّ منها يعرف حاوياتك؟

يراقب Traefik مقبس Docker، وينشئ الموجّهات من تسميات الحاويات عند بدء الحاويات وتوقفها. لا يفعل أيٌّ من الخيارات الأخرى ذلك. يحتاج كلٌّ من Nginx وCaddy إلى تعديل الإعداد وإعادة التحميل عند ظهور حاوية جديدة، كما يحتاجان إلى عنوان يمكنهما الوصول إليه: إما منفذ منشور على loopback، أو شبكة Docker مشتركة متصل بها الـproxy.

لهذه الميزة تكلفة، ومن المهم توضيحها صراحةً. يقرأ Traefik /var/run/docker.sock. يمكن لأي شخص يستطيع الاتصال بهذا المقبس بدء حاوية مع تركيب نظام ملفات المضيف داخلها، ما يمنحه صلاحيات root على المضيف. يقلل تركيبه للقراءة فقط من الخطر، لكنه لا يزيله. إذا كان ذلك مهماً وفق نموذج التهديد لديك، فضع socket proxy بينهما ليعرض فقط نقاط نهاية قائمة الحاويات التي يحتاج إليها Traefik.

يمكن لـCaddy إجراء الاكتشاف القائم على التسميات عبر plugin من المجتمع، لكن plugins الخاصة بـCaddy تُضمَّن وقت التجميع. لذلك عليك إنشاء binary مخصص أو image مخصصة باستخدام xcaddy، ثم تصبح مسؤولاً عن ذلك البناء وتحديثاته. بالنسبة إلى ثلاث أو أربع خدمات، يكون تعديل Caddyfile أقل جهداً.

WebSockets والبث: ما الذي يتعطل ولماذا

Nginx هو المكوّن الذي يحتاج إلى إعداد إضافي. يبدأ اتصال WebSocket كطلب HTTP يحمل Upgrade: websocket، ولا يمرّر nginx الرؤوس hop-by-hop إلى الخدمة الخلفية ما لم تطلب منه ذلك صراحةً.

# /etc/nginx/conf.d/upgrade-map.conf
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

بعد ذلك، أضف داخل كتلة location ثلاثة أسطر يجب أن تكون موجودة كلها:

        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;

إذا حذفت هذه الأسطر، تطبع وحدة تحكم المتصفح WebSocket connection to 'wss://app.example.com/ws' failed، بينما يعرض سجل الخدمة الخلفية طلب GET عادياً. يوجد map لأنّ استخدام Connection: upgrade ثابتة سيؤدي إلى إرسالها مع كل طلب، بما في ذلك الطلبات العادية التي يجب أن تحتوي على close.

هناك إعدادان افتراضيان آخران في Nginx قد يسببان مشكلات. قيمة proxy_read_timeout هي 60 ثانية، وتُطبَّق على النفق بعد الترقية، لذلك يغلق الوكيل اتصال WebSocket الذي لا يشهد أي حركة مرور لمدة دقيقة. كما تصل الأحداث المرسلة من الخادم متأخرة أو على دفعات إلى أن تضبط proxy_buffering off; على تلك المساحة، لأن nginx يحتفظ بالاستجابة في مخزنه بينما تنتظرها صفحتك.

ينفّذ Caddy الترقية ويحوّل الاتصال إلى نفق ثنائي الاتجاه من دون أي توجيهات. كما يرسل البيانات فوراً عندما تكون الاستجابة text/event-stream أو عندما لا يكون طولها معروفاً، لذلك يعمل البث من دون تعديل. ويمرّر Traefik عمليات الترقية ولا يخزّن الاستجابات مؤقتاً ما لم تضف برمجية buffering الوسيطة بنفسك. إذا كانت خدماتك تتضمن الدردشة، أو طرفية ويب، أو متابعة السجلات، أو لوحات معلومات مباشرة، فسيظهر فرق فعلي في مقدار الإعداد الذي ستكتبه وتصححه.

كتلة خادم Nginx الكاملة، بما في ذلك WebSockets وSSE
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    listen 80;
    server_name app.example.com;
    client_max_body_size 64m;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;
        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;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_read_timeout 3600s;
        proxy_buffering off;
    }
}

ينتمي map إلى سياق http، وليس داخل server، لذلك ضعه في ملف مستقل ضمن /etc/nginx/conf.d/. عطّل proxy_buffering فقط في المساحات التي تستخدم البث، لأن التخزين المؤقت هو ما يتيح لـ nginx تحرير عامل الخدمة الخلفية مبكراً في الاستجابات العادية. يعيد Certbot كتابة هذه الكتلة عند تشغيله، لذلك اقرأ الملف مرة أخرى بعد ذلك.

ماذا يحدث عندما تحتاج إلى شيء غير اعتيادي؟

هنا يبرّر Nginx الأسطر الإضافية في إعداداته.

  • شهادات العميل، وتُسمّى أيضاً mTLS (‏TLS المتبادل)، حيث يجب على العميل تقديم شهادة كذلك. يتطلب Nginx وجود ssl_client_certificate /etc/ssl/ca.pem; وssl_verify_client on; داخل كتلة الخادم. يتطلب Caddy كتلة client_auth داخل tls. ولا يمكن لملصقات Traefik التعبير عن ذلك إطلاقاً: إذ تعرّف خيار TLS في موفّر ملفات، ثم تشير إليه وحدة التوجيه باستخدام traefik.http.routers.app.tls.options=mtls@file. ويحصل نموذج وضع كل شيء في الملصقات على استثناء عند حاجتك إلى ذلك للمرة الأولى.
  • التحميلات الكبيرة. يحدّ Nginx أجسام الطلبات إلى 1 MB افتراضياً. ويُرجع التحميل الأكبر 413 Request Entity Too Large، بينما يذكر سجل الأخطاء client intended to send too large body. ارفع قيمة client_max_body_size. لا يفرض Caddy وTraefik أي حد لحجم الجسم افتراضياً، لذلك يصل الطلب إلى تطبيقك، ويحدد حد التطبيق نفسه النتيجة.
  • التخزين المؤقت للاستجابات. يوفّر Nginx proxy_cache، وهو ناضج ومستقر. يحتاج Caddy إلى إضافة مضمّنة عند الترجمة. ولا يحتوي إصدار Traefik مفتوح المصدر على تخزين مؤقت HTTP إطلاقاً، ما يفاجئ من يفترضون أن كل Proxy يوفّر التخزين المؤقت.
  • TCP أو UDP الخام، لمنفذ قاعدة بيانات أو خادم ألعاب. يوفّر Nginx وحدة stream. ويستخدم Traefik وحدات توجيه TCP وUDP ضمن نقاط دخول مستقلة. ويحتاج Caddy إلى إضافة أخرى، وبالتالي إلى إصدار مخصّص آخر.
  • خادم ويب موجود أصلاً خلف الـProxy. إذا كانت الخدمة تطبيق PHP تقليدياً، فإن حزمة LAMP على Ubuntu 24.04 تتضمن Apache أصلاً، ويؤدي وضع Proxy أمامها إلى وجود موضعين يحددان الرؤوس وموضعين يمكنهما إعادة كتابة عنوان URL. حدّد أيّهما ينهي TLS، ثم أبقِ الآخر على HTTP عادي مرتبطاً بواجهة loopback.

مصيدة الجدار الناري الناتجة عن هذا الخيار

الفكرة من استخدام Reverse Proxy هي أن يكون المنفذان 80 و443 فقط مفتوحين. لكن Docker يلغي ذلك بصمت. يؤدي نشر منفذ باستخدام -p 8080:80 إلى كتابة قاعدة DNAT في جدول nat، وتُقيَّم هذه القاعدة قبل قواعد INPUT التي يديرها ufw. لذلك لا يمنعها ufw deny 8080، ويصبح تطبيقك متاحاً على الإنترنت العام بجانب الـproxy الذي أعددته بعناية. اربط المنافذ المنشورة بعنوان loopback باستخدام 127.0.0.1:8080:80، أو احذف ports: بالكامل ودَع الـproxy يصل إلى الحاوية عبر شبكة Docker، كما يفعل مثال Traefik أعلاه. تَجد شرح الآلية والإصلاح في سبب تجاوز منافذ Docker المنشورة لـ ufw.

اختبر ذلك من جهاز ليس هو VPS، لأن الاختبار الذي يُجرى على الخادم نفسه ينجح دائماً:

curl --max-time 5 http://your.server.address:8080

النتيجة المطلوبة هي Connection refused أو انتهاء المهلة. أما استجابة HTTP فتعني أن التطبيق يمكن الوصول إليه من دون المرور عبر الـproxy، وأن كل ما أعددته أعلاه مجرد زينة.

أي Proxy تختار؟

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

مختبر منزلي قائم على docker-compose وتواصل إضافة خدمات إليه: Traefik. بعد الخدمة الثالثة، تصبح labels أقل جهداً من تعديل ملف مركزي، كما أن حذف الخدمة يؤدي إلى حذف route الخاصة بها. خصص فترة بعد الظهر للإعداد الأول، لأن entrypoints وrouters وservices وmiddlewares كلها مصطلحات جديدة. يظهر الخطأ الإملائي في label عادةً على شكل 404 من Traefik بدلاً من فشل بدء التشغيل، لذلك اقرأ docker logs traefik لمعرفة خطأ التحليل قبل افتراض أن التطبيق معطل.

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

تنطبق قاعدة واحدة بغض النظر عن اختيارك. تستمع عملية واحدة فقط على الواجهة العامة، بينما تستمع جميع العمليات الأخرى على loopback أو على شبكة Docker خاصة.

FAQ

ما هو أفضل Reverse Proxy لتشغيل عدة تطبيقات Docker على VPS واحد؟

بالنسبة إلى ثلاث أو أربع خدمات تضيف إليها خدمات جديدة من وقت إلى آخر، يثبت Traefik جدواه، لأن كل تطبيق يتضمن labels الخاصة بالتوجيه ولا يحتاج إلى تعديل ملف مركزي. إذا كانت الخدمات مستقرة وكنت تريد أساساً ألّا تعود HTTPS مشكلة عليك، فإن Caddy يتطلب تعلّماً أقل ويقلل احتمالات الأعطال. اختر Nginx إذا كنت تعرفه مسبقاً، أو إذا كنت تحتاج إلى ميزة لا يوفرها الآخران، مثل تخزين الاستجابات مؤقتاً أو مستمع TCP عادي.

هل يحتاج Caddy فعلاً إلى أي إعداد للشهادة؟

في الحالة المعتادة، نعم. في الواقع، يكفي تحديد اسم مضيف عام كعنوان للموقع؛ إذ يطلب Caddy الشهادة عبر ACME، ويقدّم إعادة التوجيه من المنفذ 80، ويجدد الشهادة قبل انتهاء صلاحيتها. لكن يجب استمرار تحقق شرطين. يجب أن يكون المنفذ 80 قابلاً للوصول من الإنترنت لتحدي HTTP-01، ويجب أن يكون سجل DNS من نوع A أو AAAA لاسم المضيف موجهاً مسبقاً إلى VPS، لأن مرجع الشهادات يحل الاسم ويتصل به مجدداً.

هل يمكنني تشغيل Nginx وTraefik على VPS نفسه؟

ليس على المنافذ نفسها. تفشل الخدمة التي تبدأ ثانياً في ربط المنفذ، ويعرض nginx bind() to 0.0.0.0:443 failed (98: Address already in use)، بينما يسجل Traefik خطأ ربط مشابهاً ثم يخرج. شغّل Reverse Proxy واحداً على المنفذين 80 و443، وضع كل شيء آخر خلفه. إذا كنت تنقل الخدمات، فانقل أسماء المضيفين واحداً تلو الآخر: اجعل الـproxy الأمامي يوجّه الطلبات مؤقتاً إلى الـproxy القديم عبر منفذ loopback إلى أن تنقل الموقع الأخير.

لماذا تنقطع اتصالات websockets لدي بعد 60 ثانية خلف Nginx؟

تكون قيمة proxy_read_timeout الافتراضية 60 ثانية، وهي تنطبق على النفق بعد اكتمال الترقية؛ لذلك يغلق الـproxy الاتصال الذي لا يشهد أي حركة مرور لمدة دقيقة، وليس التطبيق. ارفع القيمة لهذا الموقع باستخدام proxy_read_timeout 3600s;، أو اجعل التطبيق يرسل إطار ping كل 30 ثانية. لا يغلق Caddy وTraefik الاتصالات الخاملة التي تمت ترقيتها بعد مؤقت مدته دقيقة واحدة، ولذلك قد يبدو التطبيق نفسه مستقراً خلفهما وغير مستقر خلف Nginx.