دور زدن محدودیت CGNAT با استفاده از VPS و Reverse Tunnel
اگر پشت CGNAT هستید و امکان Port Forwarding ندارید، با نصب frp روی یک VPS ارزان، ترافیک را به شبکه خانگی هدایت کنید. این راهنما نحوه ایجاد تونل امن و دریافت IP عمومی را آموزش میدهد.
چرا port forwarding پشت CGNAT کار نمیکند
پشت CGNAT (ترجمه آدرس شبکه در سطح اپراتور)، آدرس WAN روتر شما با سایر مشترکین به اشتراک گذاشته میشود؛ بنابراین هیچ IP عمومی متعلق به شما نیست و پورتی برای forward کردن وجود ندارد. یک reverse tunnel این مشکل را حل میکند: یک VPS ارزانقیمت دارای IP عمومی است، دستگاه خانگی شما به VPS متصل میشود و درخواستهای ورودی از طریق همان اتصالی که دستگاه خانگی از قبل برقرار کرده است، به سمت شما بازمیگردند. شما سختافزاری که دارید را حفظ میکنید و تنها چیزی را که ISP به شما نمیفروشد، یعنی یک آدرس قابل مسیریابی، اجاره میکنید.
هر دستوری که در ادامه آمده است، با نام ماشینی که باید روی آن اجرا شود مشخص شده است. این کار به دو ماشین نیاز دارد: 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 است که دقیقاً برای همین کاربرد رزرو شده است. برخی از ISPها به جای آن، 10.0.0.0/8 را در سمت WAN قرار میدهند که همان وضعیت با برچسبی متفاوت است.
پیش از اجاره هر چیزی، یک مورد را بررسی کنید. بسیاری از ISPهای دارای CGNAT، یک پیشوند IPv6 واقعی ارائه میدهند؛ اگر دستگاه خانگی شما یک آدرس IPv6 سراسری دارد، میتوانید فایروال را روی آن آدرس باز کنید و کلاً از تونل صرفنظر کنید. این روش به محض اینکه بازدیدکننده در یک شبکه فقط IPv4 باشد از کار میافتد، و به همین دلیل است که اکثر افراد در نهایت به اینجا میرسند.
نحوه عملکرد تونل معکوس VPS، برقراری ارتباط از داخل به خارج
شبکههای CGNAT و روترهای خانگی معمولی، اتصالات ورودی ناخواسته را مسدود میکنند. فایروالهای شرکتی نیز همینطور. هیچکدام از آنها اتصالات خروجی را مسدود نمیکنند، زیرا اتصالات خروجی همان کاری است که هر مرورگر و هر کلاینت بهروزرسانی در تمام طول روز انجام میدهد. یک دستگاه NAT که یک اتصال TCP خروجی را مشاهده میکند، نگاشتی برای آن ایجاد کرده و سپس اجازه میدهد ترافیک بازگشتی از همان اتصال عبور کند. هیچچیز از بیرون نمیتواند اتصالی را به سمت سیستم خانگی شما آغاز کند. بنابراین، سیستم خانگی اتصال را برقرار میکند و تونل، ترافیک را در جهت مخالف از همان اتصال عبور میدهد.
این کل مکانیزم است. سیستم خانگی به یک پورت روی VPS متصل میشود و آن اتصال را باز نگه میدارد. VPS درخواستهای عمومی را میپذیرد و آنها را از طریق همان اتصال موجود به پایین هدایت میکند. هیچچیز هرگز سعی نمیکند به آدرس IP خانگی شما دسترسی پیدا کند، بنابراین نیازی به انجام چنین کاری نیست.
دو نتیجه حاصل میشود که هر دو مفید هستند. رکورد DNS شما به VPS اشاره میکند، نه به خانه شما. و آدرس عمومی شما اکنون آدرس VPS است، بنابراین هر آنچه یک ناظر از طریق جستجوی IP میآموزد، مربوط به یک سرور اجارهای است و نه خط اینترنت خانگی شما.
سه روش برای پیادهسازی
ssh -R: بهصورت پیشفرض در هر دو سمت نصب شده است و برای یک سرویس واحد یا دموهای موقت مناسب است. این ابزار فاقد داشبورد مدیریتی است و منطق بازاتصال (reconnection) قابلتوجهی ندارد.- frp: یک سرور کوچک به زبان Go (
frps) و یک کلاینت متناظر (frpc) است. این گزینه برای راهاندازیهای دائمی که چندین سرویس را پشت یک نام دامنه قرار میدهند، مناسب است. بخش اصلی این راهنما به این ابزار اختصاص دارد. - یک Mesh VPN: مانند Tailscale یا یک سرور WireGuard که خودتان مدیریت میکنید. این گزینه زمانی مناسب است که میخواهید دستگاههای شخصیتان بهصورت خصوصی به یکدیگر متصل شوند، نه اینکه سرویسی را در اینترنت عمومی منتشر کنید.
اگر هدف شما دسترسی خصوصی از دستگاههایی است که تحت کنترل دارید، از Mesh VPN استفاده کنید. Tailscale Serve and Funnel نحوه انتشار سرویس از طریق tailnet را پوشش میدهد و یک سرور WireGuard شخصی روی همان 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 یک لینک قطعشده را در حدود 90 ثانیه تشخیص دهد، به جای اینکه روی اتصالی که دیگر وجود ندارد معلق بماند.
حالا بخشی که باعث سردرگمی همه میشود. این شنونده (listener) روی loopback قرار دارد، بنابراین curl http://vps.example.com:8080 از هر جای دیگری با شکست مواجه میشود. sshd بهصورت پیشفرض با GatewayPorts no عرضه میشود، که به این معنی است که یک forward از راه دور فقط به رابط loopback متصل میشود. این مورد را با تنظیم GatewayPorts yes اصلاح نکنید. forward را روی loopback باقی بگذارید و nginx را جلوی آن قرار دهید، دقیقاً به همان روشی که در تنظیمات frp در ادامه آمده است؛ به این ترتیب پورت عمومی 443 با گواهی خواهد بود و پورت تونل هرگز در معرض اینترنت قرار نمیگیرد. اگر مطمئن نیستید که در حال حاضر چه چیزی و روی کدام رابط در حال گوش دادن است، یک گشت کوتاه در پورتها و شنوندهها در لینوکس ارزش 10 دقیقه وقت گذاشتن را دارد.
اگر پورت از قبل در VPS اشغال شده باشد، ssh این پیام را چاپ میکند و ExitOnForwardFailure=yes باعث میشود که به جای اتصال بدون تونل فعال، عملیات متوقف شود:
Warning: remote port forwarding failed for listen port 8080دلیل معمول این اتفاق، یک نشست (session) قبلی است که بدون اطلاع sshd قطع شده است. گزینههای ClientAliveInterval 30 و ClientAliveCountMax 3 را در /etc/ssh/sshd_config در VPS تنظیم کنید تا نشستهای مرده حذف شوند و پورت آزاد گردد. کل دستور را در یک unit از systemd با Restart=always و یک کلید اختصاصی قرار دهید، یا از autossh استفاده کنید. برای هر چیزی که بیش از یک سرویس دارد، در اینجا متوقف شوید و از frp استفاده کنید.
نصب frp روی VPS با استفاده از نسخه مشخص (pinned)
نرمافزار frp به صورت یک باینری استاتیک Go عرضه میشود و در مخازن رسمی Ubuntu یا Debian موجود نیست؛ بنابراین باید یک نسخه (release) را دانلود و شخصاً آن را تایید کنید. نسخه را ثابت (pin) نگه دارید. فرمت پیکربندی در نسخه v0.52.0 تغییر کرد و نام گزینهها از آن زمان جابهجا شدهاند؛ بنابراین آموزشهای قدیمی کلیدهایی را به شما میدهند که باینری شما آنها را نمیشناسد. این راهنما از v0.71.0 استفاده میکند که در تاریخ 14 اوت 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.txtsha256sum باید دقیقاً یک خط خروجی داشته باشد:
frp_0.71.0_linux_amd64.tar.gz: OK--ignore-missing به این دلیل لازم است که فایل checksum شامل تمام هجده دارایی (asset) منتشر شده است، در حالی که شما فقط یکی از آنها را دانلود کردهاید. بدون این پرچم، 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 --versionfrps --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 (امنیت لایه انتقال) نباشد را رد میکند. کلاینتها از نسخه v0.50.0 به بعد بهصورت پیشفرض TLS را فعال دارند، بنابراین این تنظیم در عمل هزینهای برای شما ندارد و راه را بر کلاینتهای قدیمی یا دستساز که بدون اطلاع شما بهصورت متن آشکار (clear text) متصل میشوند، میبندد.
گزینه proxyBindAddr = "127.0.0.1" خطی است که اکثر راهنماها از قلم میاندازند و دلیل امن بودن این تنظیمات برای اجرا در طولانیمدت همین است. این گزینه تمام شنودکنندههایی (listener) که frp به نمایندگی از یک پروکسی باز میکند—هم vhostهای HTTP و هم هر remotePort که کلاینت درخواست کند—را به رابط loopback منتقل میکند. اینترنت به هیچوجه نمیتواند به این شنودکنندهها دسترسی داشته باشد. تنها درگاه عمومی، nginx روی پورت 443 است که شما آن را پیکربندی و کنترل میکنید.
گزینه webServer.addr = "127.0.0.1" داشبورد را از رابط عمومی دور نگه میدارد. داشبورد نقشهای کامل از سرویسهای خصوصی شما و ترافیک آنهاست که تنها با یک رمز عبور HTTP basic auth محافظت میشود، بنابراین نباید روی 0.0.0.0 در دسترس باشد.
مالکیت فایل را طوری تنظیم کنید که توکن برای همه قابل خواندن نباشد، سپس پیش از شروع هر کاری، نحو (syntax) فایل را بررسی کنید:
# 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 بهعنوان یک سرویس بدون دسترسی ریشه (unprivileged)
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لاگ باید هر دو listener را نشان دهد و آدرسها اهمیت بیشتری نسبت به پورتها دارند:
frps tcp listen on 0.0.0.0:7000
http service listen on 127.0.0.1:8080ProtectSystem=strict کل سیستم فایل را برای این سرویس فقطخواندنی (read-only) میکند. frps این وضعیت را میپذیرد زیرا لاگ آن بهصورت پیشفرض به خروجی استاندارد (standard output) ارسال میشود و توسط 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 درست به نظر میرسد اما همچنان با خطای timeout مواجه میشود، معمولاً در آنجا مسدود شده است. قوانین ufw که یک VPS واقعاً به آنها نیاز دارد، تنظیمات پیشفرض deny را که این بخش بر اساس آن فرض شده است، بررسی میکند.
خاتمه 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 را چاپ میکند. پیش از هر بار reload، این دستور را اجرا کنید. اگر reload با خطا مواجه شود، 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; در اینجا اختیاری نیست. vhost پروتکل HTTP در frp، درخواستها را بر اساس هدر Host مسیریابی میکند و آن را با لیست customDomains در پیکربندی کلاینت تطبیق میدهد. اگر این هدر را حذف کنید، nginx مقدار Host: 127.0.0.1 را ارسال میکند؛ در نتیجه frp پروکسی مناسبی برای آن نام پیدا نمیکند و بازدیدکننده به جای صفحه برنامه، با یک خطای 404 از سمت frp مواجه میشود. مطلب توضیح خطبهخط یک بلاک reverse proxy در nginx عملکرد سایر هدرها را بررسی میکند.
# on the VPS
sudo nginx -t && sudo systemctl reload nginx
sudo certbot renew --dry-runاجرای آزمایشی (dry run) تضمین میکند که تمدید گواهی در 90 روز آینده، زمانی که شما نظارتی ندارید، بهدرستی انجام خواهد شد. این فرآیند نیاز دارد که پورت 80 در دسترس باشد؛ به همین دلیل است که آن قانون 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 هر کلیدی را که پس از سرتیتر یک جدول (table header) بیاید به همان جدول اختصاص میدهد؛ بنابراین یک تنظیم سطح بالا مانند serverAddr که زیر سرتیتر یک proxy نوشته شود، به اشتباه به عنوان تنظیمات همان پراکسی در نظر گرفته شده و توسط frp نادیده گرفته میشود. نوشتن لیست پراکسیها به صورت آرایه خطی (inline array)، همانطور که در بالا نمایش داده شده، از این مشکل جلوگیری میکند: در این حالت هر کلید سطح بالا، بدون ابهام در سطح بالا باقی میماند.
گزینه type = "http" این پراکسی را از طریق vhost listener هدایت میکند و نیازی به اشغال یک پورت 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 پاسخ میدهد. بهجای باز کردن یک پورت، با استفاده از یک local forward از لپتاپ خود به آن دسترسی پیدا کنید:
# 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 وارد شوید. این صفحه تمام کلاینتهای متصل و شمارندههای ترافیک برای هر پروکسی را فهرست میکند؛ این سریعترین راه برای پاسخ به این پرسش است که «آیا سیستم خانگی در حال حاضر اصلاً متصل است یا خیر». با بستن نشست ssh، داشبورد دوباره غیرقابلدسترس میشود.
کارهایی که تونل انجام نمیدهد
این بخش را دو بار بخوانید، زیرا بسیاری از کاربران در همینجا دچار مشکل میشوند. تونل، یک سرویس خصوصی را از طریق اینترنت عمومی قابلدسترسی میکند. این تونل، هویت افرادی که به آن دسترسی پیدا میکنند را احراز نمیکند. به محض اینکه https://home.example.com به آدرس IP متصل شود، اسکنرها ظرف چند روز آن را پیدا خواهند کرد؛ فرقی نمیکند که شما نام دامنه را به کسی گفته باشید یا خیر. لاگهای شفافیت گواهی (Certificate Transparency logs) هر نام دامنهای که برای آن گواهی صادر میکنید را منتشر میکنند، بنابراین به محض موفقیت certbot، نام دامنه عمومی میشود.
هر چیزی که در معرض اینترنت قرار میدهید باید سیستم احراز هویت اختصاصی خود را داشته باشد. اگر برنامه دارای یک سیستم ورود واقعی با قابلیت محدودسازی نرخ درخواست (rate limiting) است، عالی است. اگر سیستم ورود آن تنها یک رمز عبور مشترک است، یا اصلاً سیستم ورودی ندارد، یک پروکسی احراز هویتکننده در مقابل آن روی VPS قرار دهید. استفاده از oauth2-proxy در مقابل برنامه راهکار معمول است و بدون نیاز به تغییر در دو سمت تونل، بین Nginx و vhost مربوط به frp قرار میگیرد.
توکن موجود در frps.toml از تونل محافظت میکند، نه از برنامهها. این توکن مانع از آن میشود که یک فرد غریبه، پروکسی خودش را روی VPS شما ثبت کند. این توکن هیچ تأثیری بر درخواستی که روی پورت 443 برای نام دامنهای که خودتان عمداً منتشر کردهاید ارسال میشود، ندارد.
دو عادت را همیشه رعایت کنید. توکن را با ویرایش هر دو فایل و راهاندازی مجدد هر دو سرویس تغییر دهید (Rotate)، زیرا این توکن بهصورت خودکار منقضی نمیشود. همچنین frp را بهروز نگه دارید: این فایل باینری، درب ورودی عمومی شماست و در یادداشتهای نسخه v0.71.0 به یک خطای server panic اشاره شده که توسط یک مقدار نامعتبر ارسالی از سمت کلاینت ایجاد میشد؛ این دقیقاً همان نوع باگی است که باید آن را وصله (patch) کنید، نه اینکه بخواهید درباره آن تحلیل کنید.
حالتهای شکست و پیامهای مربوط به آنها
کلاینت هرگز متصل نمیشود. journalctl -u frpc مدام connect to server error: را تکرار میکند و پس از آن خطای dial timeout رخ میدهد. هیچ درخواستی به پورت 7000 نمیرسد. وضعیت ufw را روی VPS بررسی کنید، سپس فایروال شبکه در پنل مدیریت ارائهدهنده را چک کنید و در نهایت با getent hosts vps.example.com مطمئن شوید که نام دامنه به درستی resolve میشود.
توکن اشتباه است. کلاینت دقیقاً همین را اعلام میکند:
login to the server failed: token in login doesn't match token from configurationتوکن را دوباره کپی کنید. وجود یک کاراکتر newline در انتها، یا یک $ در رشتهای که در shell بدون کوتیشن قرار گرفته و به هیچ تبدیل شده است، دلیل اکثر این خطاهاست. به همین دلیل است که خروجی openssl rand -base64 32 باید داخل کوتیشن در فایل TOML قرار بگیرد.
تونل برقرار است اما مرورگر خطای 404 ساده برمیگرداند. frpc لاگ ورود موفق را ثبت کرده و داشبورد نیز پروکسی را لیست میکند، اما صفحه خطای 404 میدهد و استایلهای برنامه بارگذاری نمیشوند. این یعنی frp پروکسی برای این هدر 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 تنظیم نشده است.
برنامه تصور میکند همه بازدیدکنندگان محلی (local) هستند. برنامه شما برای همه درخواستها 127.0.0.1 را ثبت میکند. frp هدر X-Forwarded-For را تنظیم میکند و nginx آن را اضافه میکند، بنابراین آدرس واقعی کلاینت در آن هدر قرار دارد. برنامه را طوری پیکربندی کنید که به این هدر اعتماد کند. اگر برنامه بر اساس IP محدودیت نرخ (rate limit) اعمال میکند، این مرحله را نادیده نگیرید؛ زیرا در حال حاضر همه بازدیدکنندگان اینترنت در یک دسته قرار میگیرند.
درخواستهای طولانی در 60 ثانیه قطع میشوند. آپلودها یا پاسخهای streaming در میانه راه متوقف میشوند. این مقدار پیشفرض proxy_read_timeout در nginx است، نه تونل. بلوک بالا آن را به 3600 ثانیه افزایش میدهد. client_max_body_size محدودیت مشابه برای حجم آپلود است و مقدار پیشفرض 1 MB آن، فایلهای بزرگتر را با خطای 413 رد میکند.
همه چیز کار میکند، اما پس از reboot روتر از کار میافتد. تنظیم Restart=always در unit مربوط به frpc به همراه loginFailExit = false این مشکل را حل میکند. با دستور sudo systemctl is-enabled frpc تأیید کنید که باید خروجی enabled را چاپ کند.
FAQ
چگونه بفهمم پشت CGNAT هستم؟
آدرس WAN در صفحه مدیریت روتر خود را با آنچه curl -4 -s https://ifconfig.me از داخل همان شبکه گزارش میدهد، مقایسه کنید. اگر این دو متفاوت باشند و آدرس WAN روتر در محدوده 100.64.0.0/10 قرار داشته باشد، ISP شما از Carrier-Grade NAT استفاده میکند. این محدوده، فضای آدرسدهی اشتراکی RFC 6598 است که دقیقاً برای همین منظور ایجاد شده است. برخی از ISPها در سمت WAN از 10.0.0.0/8 استفاده میکنند که معنای مشابهی دارد. اگر این دو آدرس یکسان باشند، شما یک IP عمومی دارید: پورت را فوروارد کنید و کار تمام است.
آیا برای reverse tunnel به نام دامنه نیاز دارم؟
برای راهاندازی HTTPS که در اینجا توضیح داده شد، بله. گواهی برای یک نام میزبان (hostname) صادر میشود و HTTP vhost در frp درخواستها را بر اساس هدر Host هدایت میکند، بنابراین هر دو طرف باید روی یک نام توافق داشته باشند. یک پروکسی TCP خام روی یک پورت عددی، بدون نیاز به دامنه و مستقیماً با IP خام VPS کار میکند، اما در این حالت شما گواهی ندارید و مسیریابی بر اساس نام میزبان ممکن نیست؛ بنابراین هر پورت عمومی فقط به یک سرویس اختصاص مییابد.
آیا اجرای frp روی یک VPS عمومی امن است؟
اگر فقط پورت کنترل در معرض دید باشد و احراز هویت شده باشد، امن است. مقدار auth.token را در هر دو سمت روی یک مقدار تصادفی تنظیم کنید و transport.tls.force = true را در سمت سرور فعال نمایید. سپس proxyBindAddr = "127.0.0.1" را طوری تنظیم کنید که هیچکدام از پورتهایی که frp برای پروکسی باز میکند مستقیماً رو به اینترنت نباشند و داشبورد را روی webServer.addr = "127.0.0.1" نگه دارید که فقط از طریق SSH local forward قابل دسترسی باشد. با انتشار نسخههای جدید، فایل باینری را بهروزرسانی کنید، زیرا این فرآیند همان چیزی است که روی آدرس عمومی شما گوش میدهد.
چرا هیچکس نمیتواند به پورت فوروارد شده ssh -R من دسترسی پیدا کند؟
برنامه sshd بهصورت پیشفرض با GatewayPorts no عرضه میشود، بنابراین یک remote forward فقط به رابط loopback در VPS متصل میشود. در نتیجه curl که روی خود VPS اجرا میشود کار میکند، اما curl از هر جای دیگری با timeout مواجه میشود. راه حل درست این است که فوروارد روی loopback باقی بماند و nginx روی پورت 443 در مقابل آن قرار گیرد. تنظیم GatewayPorts yes باعث انتشار یک پورت خام بدون گواهی و بدون TLS میشود که از مشکلی که حل میکند، خطرناکتر است.
آیا باید از frp استفاده کنم یا یک mesh VPN مثل Tailscale یا WireGuard؟
زمانی که فقط دستگاههای خودتان نیاز به دسترسی دارند از mesh VPN استفاده کنید، زیرا در این حالت هیچ چیزی منتشر نمیشود و نام میزبان عمومی برای اسکن شدن توسط دیگران وجود ندارد. زمانی از frp استفاده کنید که به یک آدرس HTTPS عمومی نیاز دارید که هر مرورگری بتواند آن را باز کند؛ مانند دریافتکننده webhook یا صفحهای که با افرادی به اشتراک میگذارید که کلاینت VPN نصب نمیکنند. این دو ابزار میتوانند بهخوبی در کنار هم روی یک VPS، در پورتهای متفاوت و برای وظایف مختلف، همزیستی داشته باشند.