لدي تثبيت n8n (الإصدار الأخير) يعمل في Docker، خلف خادم وكيل عكسي Nginx مع SSL. بسبب متطلبات الشبكة، يتم الوصول الخارجي من خلال منفذ غير قياسي (مثل 8443)، والوكيل يعيد التوجيه داخلياً إلى حاوية n8n على منفذها الافتراضي.
لاحظت سلوكاً غريباً لم أتمكن من حله:
عندما يتم تفعيل مشغل تلقائي (على سبيل المثال، webhook من Telegram) للworkflow، يعمل كل شيء بشكل مثالي: يتم تنفيذ التدفق بالكامل، السجلات لا تظهر أخطاء ولا توجد قطعات اتصال.
ومع ذلك، إذا قمت بتنفيذ نفس workflow يدويًا من المحرر (زر “Execute workflow” أو “Execute node”)، بعد بضع ثوان تظهر الرسالة “Lost connection to the server” في الواجهة. ينقطع المحرر، وأحياناً يتوقف عن الاستجابة حتى أقوم بإعادة تحميل الصفحة. يحدث هذا حتى مع workflows بسيطة جداً لا تأخذ وقتاً في المعالجة.
تفاصيل الإعدادات:
n8n مع متغيرات WEBHOOK_URL و N8N_HOST معدلة وفقاً للعنوان الخارجي (مع المنفذ غير القياسي).
Nginx مع دعم WebSocket (Upgrade، Connection) وأوقات انتظار عالية (proxy_read_timeout 300s، إلخ).
شهادة SSL صحيحة والتحقق منها من الخارج.
جربت تغيير N8N_PUSH_BACKEND بين sse و websocket، بدون تحسن.
سجلات n8n (docker logs) تظهر فقط أحداث إعادة الاتصال عند حدوث الخطأ، لكنها لا تشير إلى سبب واضح.
السؤال:
هل عاني أي شخص من قطع الاتصال الذي يحدث فقط أثناء التنفيذ اليدوي، وليس مع المشغلات التلقائية؟ هل يمكن أن يكون السبب أن المحرر يستخدم اتصالاً دائماً (SSE/WebSocket) يقطعه الوكيل مبكراً، وفي المقابل طلبات webhook البسيطة هي HTTP عادي لا تعاني من المشكلة؟ هل هناك أي إعدادات إضافية في Nginx أو في n8n لمنع قطع اتصال واجهة المستخدم عند بدء التنفيذ اليدوي؟
@n8n_ca التنفيذات اليدوية تقوم بتدفق النتائج مرة أخرى إلى المحرر عبر اتصال push (/rest/push) الذي لا تستخدمه webhooks، لذلك فقط التنفيذات اليدوية تسقط. عند قطع الاتصال، افتح أدوات مطور المتصفح > network وابحث عن إدخال /rest/push، هل فشلترقية websocket (الحالة ليست 101) أم أنها تتصل ثم تسقط بعد بضع ثوان؟ هذا يخبرنا ما إذا كانت المشكلة في ترقية nginx أو في backend push.
لإصلاح هذه المشكلة، يجب عليك إخبار Nginx بشكل صريح بعدم تخزين مؤقت للاستجابة والتعامل مع الاتصال كتدفق.
حدّث إعدادات Nginx لكتلة الموقع n8n باستخدام هذه التوجيهات المحددة:
location / {
proxy_pass http://localhost:5678; # أو اسم حاوية docker/عنوان IP الخاص بك
# 1. ضروري لـ WebSockets
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# 2. ضروري لـ SSE (الـ "إصلاح" لمشكلتك المحددة)
proxy_buffering off;
proxy_cache off;
chunked_transfer_encoding on;
# 3. رؤوس الوكيل القياسية
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;
# 4. المهل الزمنية (احتفظ بها عالية لسير العمل الطويل المدى)
proxy_connect_timeout 60s;
proxy_send_timeout 3600s;
proxy_read_timeout 3600s;
}
قائمة التحقق لإعدادك المحددة:
proxy_buffering off;: هذا هو السطر الأكثر أهمية. يضمن أنه بمجرد إرسال n8n حدث “بدء العقدة”، يمرره Nginx إلى متصفحك فوراً.
N8N_PUSH_BACKEND: نظراً لأنك تستخدم وكيل عكسي، أنصحك بتعيين هذا إلى websocket. في حين أن sse يعمل، فإن WebSockets تكون عموماً أكثر موثوقية عبر Nginx إذا تم تكوين رؤوس Upgrade بشكل صحيح كما هو موضح أعلاه.
تحقق من WEBHOOK_URL: تأكد من أنه يتضمن المنفذ غير القياسي الخاص بك.
صحيح:WEBHOOK_URL=https://yourdomain.com:8443/
تصحيح الأخطاء في المتصفح: إذا استمرت الخطأ، افتح أدوات مطور المتصفح (F12) -\ علامة التبويب الشبكة.
ابحث عن طلب يسمى events (إذا كنت تستخدم SSE) أو طلب 101 Switching Protocols (إذا كنت تستخدم WebSockets).
إذا رأيت حالة الطلب “قيد الانتظار” لفترة طويلة ثم فجأة “فشل” أو “ملغاة” في اللحظة التي يظهر فيها الخطأ، فهذا يؤكد أن الوكيل يقطع التدفق.
@n8n_ca إذا كان المحرر يتحمل بشكل صحيح ولا يمكن لـ websocket /rest/push الاتصال فقط على :8443، فهذا عادةً يعني أن nginx يخدم هذا vhost عبر http/2، لا يمكن لترقية websocket أن تحدث عبر اتصال http/2 لذلك فشل المصافحة بينما تعمل كل طلب عادي بشكل صحيح. كيف يبدو سطر listen الخاص بك؟ إذا كان listen 8443 ssl http2; فأزل http2 (أو قسّمه على listener منفصل) حتى يتفاوض /rest/push عبر http/1.1، وهذا يصلح الـ push دون لمس الـ buffering.
أفهم الالتباس حول التوصية العامة “proxy_pass http://localhost:5678; # Or your docker container name/IP”، لكن في هذا السيناريو المحدد يجب الحفاظ على http://localhost:5678 وعدم تغييره باستخدام النطاق الخارجي. إليك التفاصيل:
Nginx وn8n يعيشان على نفس الجهاز.
حاوية n8n تنشر المنفذ 5678 إلى المضيف من خلال تعيين - "5678:5678" في ملف docker-compose. لذلك، من وجهة نظر نظام المضيف (حيث يعمل Nginx)، يكون n8n قابلاً للوصول عبر localhost:5678 أو 127.0.0.1:5678.
Nginx يعمل كوكيل عكسي، وليس كعميل خارجي.
عندما يستقبل Nginx طلباً خارجياً على المنفذ 443 (أو 8443)، يعيد توجيهه داخلياً إلى العنوان الذي تحدده باستخدام proxy_pass. إذا وضعت https://domini.com:8443، فستحاول Nginx الاتصال بنفسها عبر الشبكة العامة، مما يؤدي إلى حلقة لا نهائية أو خطأ في الاتصال لأنها ستكون تستدعي نفسها.
العبارة “or your docker container name/IP” تشير إلى أسماء الحاويات أو عناوين IP الداخلية لـ Docker، وليس النطاق العام.
على سبيل المثال، إذا كانت Nginx أيضاً داخل حاوية Docker على نفس الشبكة مثل n8n، فعندئذ يمكنك استخدام http://n8n:5678. لكن بما أن Nginx تعمل مباشرة على المضيف، فإن الوصول الصحيح هو http://localhost:5678.
تحدثت مع ChatGPT وقد أعد لي هذا الملخص.
مرحبا بالجميع،
كنت أحاول حل مشكلة مستمرة تتعلق بـ “فقدان الاتصال بالخادم” في n8n الذي يعمل خلف بروكسي عكسي Nginx، وبعد البحث في السجلات، أنا عالق عند خطأ “Invalid origin!” لنقطة نهاية /rest/push. سأكون ممتنًا حقًا لأي آراء جديدة حول هذا الموضوع.
الإعداد:
أحدث صورة Docker لـ n8n، خلف Nginx على نفس المضيف.
يتم الوصول الخارجي عبر https://n8n.example.com:8443 (منفذ غير قياسي)، وتحويله بواسطة جدار حماية إلى منفذ Nginx الداخلي 443.
يستمع Nginx على 443 (SSL)، ويعيد التوجيه إلى http://localhost:5678.
شهادة SSL من Let’s Encrypt، صحيحة وتعمل بشكل جيد.
يعمل webhook من Telegram بشكل جيد، لذا الوظائف الأساسية للبروكسي العكسي موجودة.
ما قمت بتكوينه حتى الآن:
كتلة موقع nginx:
location / {
proxy_pass http://localhost:5678;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# الحفاظ على المنفذ الأصلي في رأس Host
proxy_set_header Host $http_host;
proxy_buffering off;
proxy_cache off;
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 X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-Port 8443;
proxy_connect_timeout 300s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
}
(لقد قمت أيضًا بإزالة أي http2 من التوجيه listen.)
تم إعادة إنشاء الحاوية بالكامل بعد كل تغيير، وقد تحققت من وجود المتغيرات داخل الحاوية.
السلوك الحالي:
محرر n8n يتم تحميله بشكل جيد، لكن بعد بضع ثوان (أو على الفور عند تشغيل سير العمل يدويًا) تعرض الواجهة “فقدان الاتصال بالخادم”.
في سجلات حاوية n8n أرى بشكل متكرر:
Origin header does NOT match the expected origin.
(Origin: "undefined" → "N/A", Expected: "undefined" → "undefined", Protocol: "undefined")
ResponseError: Invalid origin!
at Push.handleRequest (/usr/local/lib/node_modules/n8n/src/push/index.ts:156:10)
لذا فإن وحدة الدفع لا تزال تتوقع أصلاً “undefined”، حتى على الرغم من تعيين N8N_PUSH_ALLOWED_ORIGINS.
جربت تعيين N8N_PUSH_BACKEND=websocket، نفس الخطأ.
يستمر webhook من Telegram في العمل بشكل مثالي، لذا فإن المشكلة خاصة باتصال SSE/WebSocket /rest/push المستخدم من قبل المحرر.
السؤال:
لماذا لا يزال n8n يتوقع أصلاً undefined على الرغم من تعيين N8N_PUSH_ALLOWED_ORIGINS؟ هل هناك متغير أو تنسيق مختلف مطلوب؟ هل يمكن أن يكون N8N_PUSH_ALLOWED_ORIGINS يتم تجاهله عند استخدام SSE؟ أم أن هناك شيء آخر في تكوين البروكسي قد يزيل الأصل قبل وصوله إلى n8n؟
سأكون ممتنًا لأي مؤشرات. أنا مستعد لتقديم سجلات إضافية أو اختبار سيناريوهات محددة. شكرًا مسبقًا!
@n8n_ca هذا الأصل غير الصحيح هو فحص أصل الدفع الخاص بـ n8n (push/origin-validator). في بيئة الإنتاج، يقارن Origin في المتصفح مع X-Forwarded-Host + X-Forwarded-Proto (مع العودة إلى Host)، ويحافظ على المنافذ غير الافتراضية، لذلك على :8443 يتوقع بالضبط n8n.example.com:8443 عبر https. إذا أرسل nginx Host كـ localhost:5678 أو X-Forwarded-Host بدون :8443، سيحدث عدم تطابق. تحقق من سجل تحذير n8n، فهو يطبع حرفياً Origin → X، Expected → Y لذلك ترى عدم التطابق الدقيق، ثم اضبط X-Forwarded-Host على $http_host و X-Forwarded-Proto على https حتى يتطابق المتوقع مع الأصل.
مرحبا @n8n_ca، تصحيح واحد قبل أن تحاول آخر إعدادات kjooleng، فرض proxy_set_header Origin ليس هو الحل الصحيح هنا وقد لا يحتى يسري مفعوله بشكل موثوق على طلب ترقية WebSocket. المشكلة الحقيقية، كما أشار achamm، هي أن فحص الأصل (origin check) في n8n يبني قيمة “Expected” الخاصة به من X-Forwarded-Host + X-Forwarded-Proto، وليس من رأس Origin مُحقون يدويًا.
لإصلاحها فعليًا:
تحقق من سطر السجل الدقيق، فهو يطبع حرفيًا Origin: X → Expected: Y. ألصق هذا السطر الدقيق هنا؛ هذا يخبرنا بالضبط ما يقارنه n8n.
تأكد من أن X-Forwarded-Host يتضمن المنفذ، $http_host يجب أن يتضمن بالفعل :8443 إذا كان المتصفح يرسله في رأس Host الخاص به. تأكد باستخدام proxy_set_header X-Forwarded-Host $http_host; (لا حاجة لـ X-Forwarded-Port منفصل، n8n يشتق المنفذ من رأس المضيف، وليس من رأس منفصل).
أسقط N8N_PROXY_HOPS=1 مؤقتًا إذا كان لديك طبقة proxy واحدة فقط بين المتصفح و n8n، عدم تطابق عدد الHops يمكن أن يتسبب في قراءة n8n للرأس الخاطئ في سلسلة الـ forwarded.
أزل proxy_set_header Origin بالكامل إذا أضفته، فهو ليس جزءًا من منطق التحقق من الأصل في n8n ويمكن أن يخفي رأس n8n الفعلي الذي يتحقق منه.
إذا ألصقت سطر السجل الدقيق Origin: X → Expected: Y من محاولتك الأخيرة، سيوضح لنا بالضبط أي رأس غير متطابق.