تجاوز CGNAT عبر نفق عكسي مع VPS وfrp
لا تملك IP عاماً خلف CGNAT؟ تعرّف إلى frp على VPS منخفض التكلفة، وافتح النفق من منزلك، وأنهِ HTTPS بشهادة حقيقية على الطرف العام.
لماذا لا تعمل إعادة توجيه المنافذ خلف CGNAT
خلف CGNAT، أي ترجمة عناوين الشبكة على مستوى مزود الخدمة، يكون عنوان WAN في موجّهك مشتركاً مع مشتركين آخرين. لذلك لا يخصك أي عنوان IP عام، ولا يوجد منفذ يمكنك إعادة توجيهه. يحل نفق عكسي هذه المشكلة: يحتفظ VPS منخفض التكلفة بعنوان IP العام، ويتصل جهازك المنزلي بـVPS عبر اتصال صادر، ثم تعود الطلبات الواردة عبر الاتصال الذي فتحه الجهاز المنزلي مسبقاً. يمكنك الاحتفاظ بالعتاد الذي تملكه. وتستأجر الشيء الوحيد الذي لن يبيعه لك مزود خدمة الإنترنت، وهو عنوان قابل للتوجيه.
كل أمر أدناه موضّح بالجهاز الذي يُنفَّذ عليه. تحتاج إلى جهازين: VPS بعنوان IP عام، والجهاز المنزلي الذي يشغّل الخدمة التي تريد الوصول إليها.
كيفية معرفة ما إذا كنت خلف CGNAT فعلاً
افتح صفحة إدارة الموجّه واقرأ عنوان WAN الذي يعرضه. ثم اسأل الإنترنت عن العنوان الذي يراه.
# on the home box
curl -4 -s https://ifconfig.me; echoإذا تطابق العنوانان، فلديك IP عام ولا تحتاج إلى أي مما يلي. أعد توجيه المنفذ وتوقف عن القراءة. إذا كان عنوان WAN في الموجّه ضمن 100.64.0.0/10، فأنت خلف CGNAT. هذه الكتلة هي مساحة عناوين مشتركة وفق RFC 6598، ومخصّصة لهذا الاستخدام تحديداً. تضع بعض مزوّدات خدمة الإنترنت 10.0.0.0/8 على جهة WAN بدلاً من ذلك، وهذه هي الحالة نفسها بتسمية مختلفة.
تحقق من أمر واحد قبل أن تستأجر أي شيء. يوزّع كثير من مزوّدي خدمة الإنترنت الذين يستخدمون CGNAT بادئة IPv6 حقيقية، وإذا كان جهازك المنزلي يملك عنوان IPv6 عاماً، فيمكنك فتح الجدار الناري على ذلك العنوان وتجاوز النفق بالكامل. يتوقف هذا عن العمل فور اتصال زائر بشبكة لا تدعم إلا IPv4، ولهذا ينتهي الأمر بمعظم الأشخاص هنا على أي حال.
كيف يعمل نفق عكسي عبر VPS، يبدأ الاتصال من الداخل إلى الخارج
تحظر CGNAT وموجّهات المنازل العادية الاتصالات الواردة غير المطلوبة. وينطبق ذلك أيضاً على جدران الحماية في الشركات. لكنها لا تحظر الاتصالات الصادرة، لأن كل متصفح وكل عميل تحديث يجريان اتصالات صادرة طوال اليوم. عندما يرى جهاز NAT اتصال TCP صادراً، ينشئ له تعييناً، ثم يسمح بحركة المرور الراجعة عبر الاتصال نفسه. ولا يستطيع أي شيء من الخارج بدء اتصال بصندوقك المنزلي. لذلك يبدأ الصندوق المنزلي الاتصال، ويحمل النفق حركة المرور مجدداً عبر الاتصال نفسه في الاتجاه المعاكس.
هذه هي الآلية كاملة. يتصل الصندوق المنزلي بـVPS عبر منفذ واحد، ويحافظ على الاتصال مفتوحاً. يستقبل VPS الطلبات العامة ويمررها عبر الاتصال القائم نفسه. لا يحاول أي شيء الوصول إلى عنوان IP المنزلي لديك، ولذلك لا حاجة إلى ذلك.
تترتب على هذا نتيجتان، وكلتاهما مفيدتان. يشير سجل DNS لديك إلى VPS، وليس إلى منزلك. كما يصبح عنوانك العام هو عنوان VPS، ولذلك فإن أي معلومات يتعلمها مراقب من خلال البحث عن IP ستتعلق بخادم مستأجر بدلاً من خط الإنترنت المنزلي لديك.
3 طرق لبنائه
ssh -R: يكون مثبتاً مسبقاً على الطرفين، ويناسب خدمة واحدة أو عرضاً تجريبياً مؤقتاً. لا يوفّر لوحة تحكم، ولا آلية إعادة اتصال يمكن الاعتماد عليها.- frp: خادم Go صغير (
frps) وعميل مطابق له (frpc). يناسب إعداداً دائماً يستضيف عدة خدمات خلف اسم مضيف واحد. يشكّل هذا الجزء معظم الدليل. - شبكة VPN شبكية: مثل Tailscale، أو خادم WireGuard تديره بنفسك. تناسبك عندما تريد أن تتصل أجهزتك ببعضها بشكل خاص، بدلاً من نشر خدمة على الإنترنت العام.
اختر الشبكة الشبكية إذا كان هدفك الوصول الخاص من الأجهزة التي تتحكم بها. يشرح Tailscale Serve وFunnel نشر الخدمات خارج tailnet، ويوفّر لك شبكة WireGuard VPN مستضافة ذاتياً على VPS نفسه البنية نفسها من دون وجود خادم تنسيق تابع لجهة خارجية ضمن مسار الاتصال. اقرأ أحد هذين القسمين وتجاوز بقية هذه الصفحة. يفترض كل ما يلي أنك تريد اسم مضيف HTTPS عاماً يمكن لأي شخص تحميله.
النسخة المختصرة: ssh -R لخدمة واحدة
لنفترض أن الجهاز المنزلي يشغّل تطبيقاً على 127.0.0.1:3000، وأن لديك وصول SSH إلى VPS مسبقاً.
# on the home box
ssh -N \
-o ExitOnForwardFailure=yes \
-o ServerAliveInterval=30 \
-o ServerAliveCountMax=3 \
-R 127.0.0.1:8080:127.0.0.1:3000 \
tunnel@vps.example.comيطلب -R 127.0.0.1:8080:127.0.0.1:3000 من sshd على VPS الاستماع على 127.0.0.1:8080 الخاص به، وإرسال كل ما يصل إليه إلى 127.0.0.1:3000 على الجهاز المنزلي. تعني -N عدم تشغيل shell. يجعل خيارا ServerAlive ssh يكتشف انقطاع الاتصال خلال نحو تسعين ثانية، بدلاً من الاستمرار في الانتظار على اتصال لم يعد موجوداً.
الآن نصل إلى الجزء الذي يسبب الالتباس عادةً. يستمع ذلك المنفذ على loopback، لذلك يفشل curl http://vps.example.com:8080 من أي مكان آخر. يأتي sshd مضبوطاً على GatewayPorts no، ما يعني أن إعادة التوجيه عن بُعد ترتبط بواجهة loopback فقط. لا تصلح ذلك بتعيين GatewayPorts yes. اترك إعادة التوجيه على loopback، وضع nginx أمامها، بالطريقة نفسها التي يستخدمها إعداد frp أدناه، بحيث يكون المنفذ العام هو 443 مع شهادة، ولا يواجه منفذ النفق الإنترنت. إذا لم تكن متأكداً مما يستمع حالياً وعلى أي واجهة، فسيستغرق استعراض مختصر للمنافذ والمستمعين على Linux عشر دقائق ويستحقها.
إذا كان المنفذ مستخدماً مسبقاً على VPS، يطبع ssh الرسالة التالية، ويجعل ExitOnForwardFailure=yes الأمر يتوقف بدلاً من إنشاء اتصال بلا نفق يعمل:
Warning: remote port forwarding failed for listen port 8080السبب المعتاد هو جلسة سابقة انقطعت من دون أن يكتشف sshd ذلك. عيّن ClientAliveInterval 30 وClientAliveCountMax 3 في /etc/ssh/sshd_config الخاص بـVPS، لكي تُزال الجلسات المنقطعة وتُحرَّر المنافذ. ضع الأمر كاملاً في وحدة systemd مع Restart=always ومفتاح مخصص، أو استخدم autossh. لأي شيء يتضمن أكثر من خدمة، توقف هنا واستخدم frp.
ثبّت frp على VPS مع تثبيته على tag محدد
يُطرح frp كملف Go ثنائي ثابت، ولا يوجد في مستودعات Ubuntu أو Debian، لذلك نزّل إصداراً وتحقق منه بنفسك. ثبّت الإصدار. تغيّر تنسيق الإعدادات في v0.52.0، وتغيّرت أسماء الخيارات منذ ذلك الحين، لذلك سيزوّدك البرنامج التعليمي القديم بمفاتيح لا يتعرّف عليها ملفك الثنائي. يستخدم هذا الدليل v0.71.0، الذي نُشر في 14 August 2026.
# on the VPS
FRP_VERSION=0.71.0
ARCH=amd64 # use arm64 if `uname -m` prints aarch64
cd /tmp
curl -fsSLO "https://github.com/fatedier/frp/releases/download/v${FRP_VERSION}/frp_${FRP_VERSION}_linux_${ARCH}.tar.gz"
curl -fsSLO "https://github.com/fatedier/frp/releases/download/v${FRP_VERSION}/frp_sha256_checksums.txt"
sha256sum --check --ignore-missing frp_sha256_checksums.txtيجب أن يطبع sha256sum سطراً واحداً بالضبط:
frp_0.71.0_linux_amd64.tar.gz: OKيلزم استخدام --ignore-missing لأن ملف checksum يغطي أصول الإصدار الثمانية عشر جميعها، بينما نزّلت واحداً منها. من دون هذا الخيار، يبلّغ sha256sum عن الأصول السبعة عشر الأخرى باعتبارها مفقودة، وينتهي برمز خروج غير صفري. وقد يبدو ذلك كأن التحقق فشل رغم عدم وجود مشكلة.
# on the VPS
tar xzf "frp_${FRP_VERSION}_linux_${ARCH}.tar.gz"
sudo install -m 755 "frp_${FRP_VERSION}_linux_${ARCH}/frps" /usr/local/bin/frps
sudo useradd --system --no-create-home --shell /usr/sbin/nologin frp
sudo install -d -m 750 -o root -g frp /etc/frp
frps --versionيطبع frps --version القيمة 0.71.0. يجب وضع frps فقط على VPS. أما frpc فيجب وضعه على الجهاز المنزلي. تثبيت الملفين الثنائيين على كل جهاز هو ما يؤدي إلى تشغيل خادم نفق في المنزل عن طريق الخطأ.
إعداد VPS: الرمز، وفرض TLS، والمستمعون على loopback
أنشئ رمزاً أولاً. فهذا الرمز هو الحماية الوحيدة بين النفق وأي جهة تفحص منافذ VPS.
# on the VPS
openssl rand -base64 32اكتب هذه القيمة في /etc/frp/frps.toml:
bindAddr = "0.0.0.0"
bindPort = 7000
# Every listener frp creates for a proxy, including the HTTP vhost, stays on loopback.
proxyBindAddr = "127.0.0.1"
vhostHTTPPort = 8080
auth.method = "token"
auth.token = "PASTE_THE_OPENSSL_OUTPUT_HERE"
transport.tls.force = true
webServer.addr = "127.0.0.1"
webServer.port = 7500
webServer.user = "admin"
webServer.password = "PASTE_A_SECOND_SECRET_HERE"
log.level = "info"تتولى أربعة أسطر من هذه الإعدادات مهام الأمان، لذا راجعها واحداً تلو الآخر.
يجب أن تتطابق auth.token مع auth.token على العميل. من دونها، يقبل frps أي عميل يعثر على المنفذ 7000، ويمكن لذلك العميل نشر أي خدمة يريدها عبر VPS وشهادتك.
ترفض transport.tls.force = true أي اتصال تحكم لا يستخدم TLS (أمان طبقة النقل). يفعّل العملاء TLS افتراضياً منذ v0.50.0، لذلك لا يترتب على ذلك أي تكلفة عملية، كما أنه يمنع حالة اتصال عميل قديم أو مُنشأ يدوياً من دون تشفير ومن دون إبلاغك.
proxyBindAddr = "127.0.0.1" هو السطر الذي تحذفه معظم الأدلة، وهو سبب أمان ترك هذا الإعداد قيد التشغيل. فهو ينقل كل مستمع يفتحه frp نيابةً عن proxy، بما في ذلك HTTP vhost وأي remotePort يطلبه العميل، إلى واجهة loopback. ولا يمكن للإنترنت الوصول إلى هؤلاء المستمعين إطلاقاً. أما المنفذ العام الوحيد فهو nginx على 443، وهو ما تضبطه وتتحكم فيه.
يُبقي webServer.addr = "127.0.0.1" لوحة المعلومات خارج الواجهة العامة. تعرض لوحة المعلومات خريطة كاملة لخدماتك الخاصة وحركة المرور الخاصة بها، وتحميها كلمة مرور واحدة للمصادقة الأساسية عبر HTTP، لذلك لا مكان لها على 0.0.0.0.
اضبط الملكية حتى لا يكون الرمز قابلاً للقراءة من جميع المستخدمين، ثم افحص الصياغة قبل تشغيل أي شيء:
# on the VPS
sudo chown root:frp /etc/frp/frps.toml
sudo chmod 640 /etc/frp/frps.toml
sudo -u frp frps verify -c /etc/frp/frps.tomlيعرض الملف الصحيح:
frps: the configuration file /etc/frp/frps.toml syntax is okملاحظة عن التنسيق ستوفر عليك ساعة من العمل. يحدد frp محلله استناداً إلى امتداد الملف، وهو يعرف .toml و.yaml و.yml و.json. ولا تزال ملفات .ini القديمة تُحمّل عبر مسار تحويل قديم، لكن INI مهجور، وتُوثّق الخيارات الجديدة لـ TOML فقط. إذا عرض لك أحد الأدلة قسماً من نوع [common] وserver_addr = x.x.x.x، فهذا الدليل أقدم من v0.52.0، ولن تتطابق أسماء مفاتيحه مع الملف الثنائي الذي ثبّتَّه للتو.
شغّل frps كخدمة دون صلاحيات مميّزة
bindPort هو 7000 وvhostHTTPPort هو 8080. كلاهما أعلى من 1024، لذلك لا يحتاج frps مطلقاً إلى root ولا يحتاج إلى CAP_NET_BIND_SERVICE. لهذا السبب لا تضع vhost على المنفذ 80 وتترك لـnginx تولّي ذلك بدلاً منه.
اكتب /etc/systemd/system/frps.service:
[Unit]
Description=frp reverse tunnel server
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=frp
Group=frp
ExecStart=/usr/local/bin/frps -c /etc/frp/frps.toml
Restart=on-failure
RestartSec=5s
LimitNOFILE=65535
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ProtectKernelTunables=true
[Install]
WantedBy=multi-user.target# on the VPS
sudo systemctl daemon-reload
sudo systemctl enable --now frps
sudo journalctl -u frps -n 20 --no-pagerيجب أن يعرض السجل كلا المستمعين، وتهم العناوين أكثر من المنافذ:
frps tcp listen on 0.0.0.0:7000
http service listen on 127.0.0.1:8080يجعل ProtectSystem=strict نظام الملفات بأكمله للقراءة فقط لهذه الخدمة. يتوافق frps مع ذلك لأن سجله يُرسل إلى الإخراج القياسي افتراضياً، ويلتقطه journald. إذا عيّنت log.to إلى مسار ملف، تفشل الخدمة في الكتابة إليه إلى أن تضيف سطراً مطابقاً من ReadWritePaths=، لذلك اترك الإعداد الافتراضي كما هو.
جدار الحماية: افتح منفذاً واحداً، وليس نطاقاً
# on the VPS
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 7000/tcp
sudo ufw enable
sudo ufw status numberedأربع قواعد، وإحداها موجودة فقط لتجديد الشهادة. المنفذ 22 مخصص لـSSH. يعيد المنفذ 80 التوجيه إلى 443 ويستجيب لتحدي ACME (بيئة إدارة الشهادات تلقائياً). يقدّم المنفذ 443 كل تطبيقات الأنفاق. المنفذ 7000 هو منفذ التحكم في frp، وهو المنفذ الوحيد الذي يحتاج العميل إلى الوصول إليه.
الأدلة التي تطلب منك فتح نطاق مثل sudo ufw allow 20000:30000/tcp تشرح التصميم الآخر، حيث تخصص كل خدمة منفذ TCP عاماً خاصاً بها. لا تحتاج إلى ذلك هنا، لأن كل الاتصالات تصل عبر 443، ويوجّهها frp وفق اسم المضيف. إذا احتجت لاحقاً إلى منفذ TCP عام فعلياً، فأعد proxyBindAddr إلى 0.0.0.0 وأضف قيوداً بحيث لا يستطيع العميل استخدام إلا المنافذ التي حددتها:
allowPorts = [
{ start = 20000, end = 20010 }
]
maxPortsPerClient = 5يشغّل معظم موفري الخدمة أيضاً جدار حماية للشبكة في لوحة التحكم، منفصلاً عن ufw على الخادم. إذا بدت القاعدة صحيحة في sudo ufw status واستمر انتهاء مهلة الاتصال، فعادةً ما يكون الاتصال محجوباً هناك. يشرح قواعد ufw التي يحتاج إليها VPS فعلياً إعداد الرفض الافتراضي الذي يفترضه هذا القسم.
إنهاء HTTPS على VPS باستخدام شهادة حقيقية
وجّه سجل A للنطاق home.example.com إلى عنوان IP العام لـVPS. لا توجّهه إلى منزلك. لا يملك منزلك عنواناً يمكن التوجيه إليه، وهذه هي المشكلة التي تحاول حلها.
# on the VPS
sudo apt update
sudo apt install -y nginx certbot python3-certbot-nginxأنشئ /etc/nginx/sites-available/home.example.com باستخدام كتلة عادية على المنفذ 80 أولاً، لكي يملك certbot server_name مطابقاً للعمل معه:
server {
listen 80;
listen [::]:80;
server_name home.example.com;
location / { return 404; }
}# on the VPS
sudo ln -s /etc/nginx/sites-available/home.example.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
sudo certbot certonly --nginx -d home.example.comيطبع nginx -t القيمة nginx: configuration file /etc/nginx/nginx.conf test is successful عند تحليل الملفات بنجاح. شغّله قبل كل إعادة تحميل. يحتفظ nginx بالإعدادات القديمة قيد التشغيل عند فشل إعادة التحميل، لذلك يبدو التعديل غير الصحيح كأنك أجريت تعديلاً ولم يحدث شيء.
تحتاج ترقيات WebSocket إلى map واحدة على مستوى http. ضعها في /etc/nginx/conf.d/upgrade.conf:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}استبدل الآن ملف الموقع بالملف الفعلي:
server {
listen 80;
listen [::]:80;
server_name home.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name home.example.com;
ssl_certificate /etc/letsencrypt/live/home.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/home.example.com/privkey.pem;
client_max_body_size 512m;
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_set_header Host $host; إلزامي هنا. يوجّه خادم HTTP الظاهري في frp الطلبات وفق ترويسة Host، ويطابقها مع قائمة customDomains في إعدادات العميل. إذا حذفت هذه الترويسة، يرسل nginx Host: 127.0.0.1، ولا يعثر frp على proxy لهذا الاسم، ويحصل الزائر على 404 مجردة من frp بدلاً من صفحة التطبيق. يشرح كل سطر في كتلة reverse proxy لـnginx وظيفة الترويسات الأخرى.
# on the VPS
sudo nginx -t && sudo systemctl reload nginx
sudo certbot renew --dry-runيثبت الاختبار الجاف أن التجديد سيعمل بعد تسعين يوماً، عندما لن تكون تراقبه. ويتطلب ذلك إمكانية الوصول إلى المنفذ 80، ولهذا يوجد rule في ufw.
الجانب المنزلي: تشغيل frpc كخدمة بصلاحيات غير مميّزة
ثبّت frpc على الجهاز المنزلي بالطريقة نفسها التي ثبّت بها frps تماماً، باستخدام الإصدار نفسه وخطوة التحقق من checksum نفسها، ثم أنشئ المستخدم frp والدليل /etc/frp نفسيهما. اكتب /etc/frp/frpc.toml:
serverAddr = "vps.example.com"
serverPort = 7000
auth.method = "token"
auth.token = "PASTE_THE_SAME_TOKEN_HERE"
transport.tls.enable = true
loginFailExit = false
proxies = [
{ name = "home-app", type = "http", localIP = "127.0.0.1", localPort = 3000, customDomains = ["home.example.com"] }
]ترتيب المفاتيح مهم في هذا الملف، وليس لأسباب تتعلق بالتنسيق. يربط TOML كل مفتاح يأتي بعد عنوان جدول بذلك الجدول، ولذلك فإن إعداداً عاماً مثل serverAddr إذا كُتب أسفل عنوان جدول proxy يتحول بصمت إلى إعداد proxy ويتجاهله frp. إن كتابة قائمة proxy في مصفوفة inline، كما تظهر أعلاه، تتجنب هذا الخطأ؛ إذ يبقى كل مفتاح عام ضمن المستوى العام بوضوح.
يوجّه type = "http" هذا proxy عبر مستمع vhost بدلاً من تخصيص منفذ TCP عام خاص به، ولهذا بقيت قواعد الجدار الناري أربعاً. يجب أن يحتوي customDomains على اسم المضيف الذي يمرره nginx في ترويسة Host، ولذلك تكون قيمته home.example.com، وليس عنوان IP الخاص بـVPS.
يكتسب loginFailExit = false أهمية أكبر مما يبدو. القيمة الافتراضية هي true، ما يجعل frpc ينهي عمله إذا فشلت أول محاولة تسجيل دخول. على جهاز منزلي يكتمل إقلاعه قبل توفر اتصال ISP، تصبح الخدمة متوقفة إلى أن تلاحظ ذلك. اضبطه على false، وسيواصل frpc إعادة المحاولة حتى يستجيب VPS.
اكتب /etc/systemd/system/frpc.service:
[Unit]
Description=frp reverse tunnel client
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=frp
Group=frp
ExecStart=/usr/local/bin/frpc -c /etc/frp/frpc.toml
Restart=always
RestartSec=10s
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
[Install]
WantedBy=multi-user.target# on the home box
sudo chown root:frp /etc/frp/frpc.toml
sudo chmod 640 /etc/frp/frpc.toml
sudo -u frp frpc verify -c /etc/frp/frpc.toml
sudo systemctl daemon-reload
sudo systemctl enable --now frpc
sudo journalctl -u frpc -n 20 --no-pagerيسجل العميل الذي اتصل run id:
login to server success, get run id [3a1f9c2b7d4e5f60]افتح https://home.example.com في متصفح، وستحصل على التطبيق الموجود على 127.0.0.1:3000 في المنزل. وجود Restart=always على العميل مقصود: قد تنقطع الاتصالات المنزلية، ويجب أن تعود الخدمة من دون تدخل منك.
أبقِ لوحة المعلومات خارج الواجهة العامة
باستخدام webServer.addr = "127.0.0.1"، تستجيب لوحة المعلومات على VPS نفسه فقط. يمكنك الوصول إليها من حاسوبك المحمول عبر إعادة توجيه محلية بدلاً من فتح منفذ:
# on your laptop
ssh -N -L 7500:127.0.0.1:7500 you@vps.example.comافتح http://127.0.0.1:7500 وسجّل الدخول باستخدام webServer.user وwebServer.password من frps.toml. تعرض الصفحة كل عميل متصل وعدادات حركة الشبكة لكل proxy، ما يجعلها أسرع طريقة للإجابة عن السؤال: «هل الجهاز المنزلي متصل الآن أصلاً؟». عند إغلاق جلسة SSH، يتعذر الوصول إلى لوحة المعلومات مرة أخرى.
ما الذي لا يفعله النفق
اقرأ هذا الجزء مرتين، لأن الأخطاء هنا قد تسبب مشكلات خطيرة. يجعل النفق خدمة خاصة قابلة للوصول من الإنترنت العام. لكنه لا يوثّق هوية الأشخاص الذين يصلون إليها. بعد أن يُحل https://home.example.com، ستعثر عليها أدوات الفحص خلال أيام، سواء أخبرت أي شخص بالاسم أم لا. تنشر سجلات شفافية الشهادات كل اسم مضيف أصدرت له شهادة، لذلك يصبح الاسم عاماً فور نجاح certbot.
يجب أن تتولى كل خدمة تكشفها توثيق الوصول إليها بنفسها. إذا كان التطبيق يوفّر تسجيل دخول فعلياً مع تحديد معدل الطلبات، فهذا جيد. أما إذا كان تسجيل الدخول يعتمد على كلمة مرور مشتركة واحدة، أو إذا لم يوفّر تسجيل دخول إطلاقاً، فضع proxy موثِّقاً أمامه على VPS. يُعد وجود oauth2-proxy أمام التطبيق الحل المعتاد، ويمكن وضعه بين nginx وfrp vhost دون تغيير أي من طرفي النفق.
يحمي الرمز المميز في frps.toml النفق، وليس التطبيقات. فهو يمنع شخصاً غريباً من تسجيل proxy خاص به على VPS. لكنه لا يفعل شيئاً تجاه طلب يصل إلى 443 لاسم مضيف نشرته عمداً.
من المفيد الالتزام بعادتين. دوّر الرمز المميز بتعديل الملفين وإعادة تشغيل الخدمتين، لأنه لا تنتهي صلاحيته تلقائياً. وحافظ على تحديث frp؛ فهذا الملف الثنائي هو نقطة الدخول العامة إلى خادمك، وتذكر ملاحظات الإصدار v0.71.0 حدوث panic في الخادم بسبب قيمة غير صحيحة يرسلها عميل، وهذه فئة من الأخطاء ينبغي تصحيحها بدلاً من محاولة تفسيرها.
أنماط الأعطال، مع النصوص التي ستظهر لك
لا يتصل العميل مطلقاً. يكرر journalctl -u frpc الرسالة connect to server error:، ثم تنتهي محاولة الاتصال بمهلة. لا يصل أي شيء إلى المنفذ 7000. افحص ufw على VPS، ثم جدار الحماية الشبكي لدى مزود الخدمة في لوحة التحكم، ثم تأكد من أن الاسم يُحلّ باستخدام getent hosts vps.example.com.
الرمز المميز غير صحيح. يوضح العميل ذلك حرفياً:
login to the server failed: token in login doesn't match token from configurationانسخ الرمز المميز مرة أخرى. تتسبب سطر جديد في نهايته، أو $ داخل سلسلة shell غير مقتبسة وتم توسيعه إلى قيمة فارغة، في معظم هذه الحالات. لذلك يجب وضع ناتج openssl rand -base64 32 بين علامتي اقتباس داخل ملف TOML.
النفق يعمل، لكن المتصفح يعرض 404 مجرداً. سجّل frpc نجاح تسجيل الدخول، وتعرض لوحة المعلومات الـproxy، لكن الصفحة تعرض 404 من دون أي تنسيق خاص بالتطبيق. يعني ذلك أن frp لا يملك proxy لهذا الرأس Host. اختبر الـvhost مباشرة على VPS، مع تجاوز nginx وTLS معاً:
# on the VPS
curl -s -o /dev/null -w '%{http_code}\n' -H 'Host: home.example.com' http://127.0.0.1:8080/تعني استجابة 404 من هذا الأمر أن customDomains غير صحيح. أما أي رمز آخر فمعناه أن الطلب لم يحصل على Host الصحيح من nginx.
استجابة 502 من nginx. يجيب nginx، بينما لا يجيب frp. يجب أن يعرض sudo ss -lntp | grep 8080 على VPS أن frps يستمع على 127.0.0.1:8080. تعني النتيجة الفارغة أن frps متوقف، أو أن vhostHTTPPort غير مضبوط في frps.toml.
يعتقد التطبيق أن كل زائر محلي. يسجل تطبيقك 127.0.0.1 لكل طلب. يضبط frp قيمة X-Forwarded-For، ويضيف nginx إليها، ولذلك يكون عنوان العميل الحقيقي موجوداً في ذلك الرأس. اضبط التطبيق ليثق به. لا تتجاوز هذه الخطوة إذا كان التطبيق يفرض حدوداً للمعدل حسب عنوان IP، لأن كل زائر على الإنترنت يشترك حالياً في مجموعة واحدة.
تنقطع الطلبات الطويلة بعد 60 ثانية. تتوقف عمليات الرفع أو الاستجابات المتدفقة قبل اكتمالها. هذا هو proxy_read_timeout الافتراضي في nginx، وليس مشكلة في النفق. يرفع الإعداد أعلاه قيمته إلى 3600s. أما client_max_body_size فهو الحد المطابق لحجم الرفع، وقيمته الافتراضية 1 MB ترفض الأجسام الأكبر باستجابة 413.
يعمل كل شيء، ثم يتوقف بعد إعادة تشغيل الموجّه. يعالج ذلك Restart=always في وحدة frpc، مع loginFailExit = false. تأكد باستخدام sudo systemctl is-enabled frpc، الذي يجب أن يطبع enabled.
FAQ
كيف أعرف ما إذا كنت خلف CGNAT؟
قارن عنوان WAN الظاهر في صفحة إدارة الموجّه بما يعرضه curl -4 -s https://ifconfig.me من داخل الشبكة نفسها. إذا اختلف العنوانان وكان عنوان WAN في الموجّه ضمن 100.64.0.0/10، فهذا يعني أن مزوّد خدمة الإنترنت يشغّل NAT على مستوى شركة الاتصالات. هذا النطاق هو مساحة العناوين المشتركة المحددة في RFC 6598، وهو مخصّص لهذا الغرض. يستخدم بعض مزوّدي الخدمة 10.0.0.0/8 على جانب WAN بدلاً من ذلك، وهذا يعني الشيء نفسه. إذا تطابق العنوانان، فلديك عنوان IP عام: وجّه المنفذ وستنتهي.
هل أحتاج إلى اسم نطاق لنفق عكسي؟
نعم، بالنسبة إلى إعداد HTTPS الموضّح هنا. تُصدر الشهادة لاسم مضيف، ويوجّه frp الطلبات وفق رأس Host الخاص بـHTTP vhost، لذلك يحتاج الطرفان إلى اسم متفق عليه. يعمل TCP proxy خام على منفذ مرقّم باستخدام عنوان VPS IP المجرد، من دون نطاق على الإطلاق، لكنك لن تحصل عندها على شهادة أو توجيه حسب اسم المضيف، ولذلك يخدم منفذ عام واحد خدمة واحدة فقط.
هل تشغيل frp على VPS عام آمن؟
يكون ذلك آمناً عندما يكون منفذ التحكم هو الشيء الوحيد المكشوف وعندما يكون محمياً بالمصادقة. عيّن auth.token إلى قيمة عشوائية على الطرفين، وعيّن transport.tls.force = true على الخادم. ثم عيّن proxyBindAddr = "127.0.0.1" بحيث لا يواجه أي شيء يفتحه frp للـproxy الإنترنت، وأبقِ لوحة المعلومات على webServer.addr = "127.0.0.1"، مع الوصول إليها عبر SSH local forward. حدّث الملف التنفيذي عند صدور إصدارات جديدة، لأنه العملية التي تستمع على عنوانك العام.
لماذا لا يستطيع أحد الوصول إلى منفذ ssh -R الذي أعدت توجيهه؟
يأتي sshd مضبوطاً على GatewayPorts no، لذلك لا يرتبط remote forward إلا بواجهة loopback في VPS. يعمل curl عند تشغيله على VPS نفسه، بينما تنتهي مهلة curl عند تشغيله من أي مكان آخر. الحل الصحيح هو إبقاء forward على loopback ووضع nginx أمامه على المنفذ 443. يؤدي ضبط GatewayPorts yes إلى نشر منفذ خام بلا شهادة وبلا TLS، وهذا أسوأ من المشكلة التي يعالجها.
هل أستخدم frp أم شبكة VPN شبكية مثل Tailscale أو WireGuard؟
استخدم شبكة VPN شبكية عندما تحتاج أجهزتك أنت فقط إلى الوصول، لأنك عندها لا تنشر أي شيء ولا توفر اسم مضيف عاماً يمكن لأي شخص فحصه. استخدم frp عندما تحتاج إلى عنوان HTTPS عاماً يمكن لأي متصفح تحميله، مثل مستقبِل webhook أو صفحة تشاركها مع أشخاص لن يثبّتوا عميل VPN. يمكن تشغيل الحلين معاً على VPS واحد، باستخدام منافذ مختلفة وتنفيذ مهمتين مختلفتين.