پرش به مطلب اصلی

Cloudflare Tunnel

Cloudflare Tunnel (دیمن cloudflared) یک اتصال خروجی با شبکه Cloudflare برقرار می‌کند و سرویس شما را بدون IP اختصاصی و بدون پورت‌های باز 80 و 443 منتشر می‌کند. روی هاست نه پروکسی معکوس با گواهی لازم است و نه آدرس عمومی: ترافیک از Cloudflare از طریق تونل از پیش برقرارشده می‌آید و TLS در سمت Cloudflare پایان می‌یابد.

1. چه زمانی تونل مناسب است​

تونل در این موارد مناسب است:

  • سرور IP اختصاصی ایستا ندارد یا ارائه‌دهنده پورت‌های ورودی را مسدود می‌کند؛
  • باید آدرس origin را از اسکن پنهان کنید؛
  • از قبل از Cloudflare برای DNS و فیلترکردن حملات استفاده می‌کنید.

تونل در این موارد مناسب نیست:

  • پنل باید بدون وابستگی به سرویس شخص ثالث کار کند؛
  • آماده نیستید که Cloudflare تمام ترافیک را به‌صورت باز ببیند؛
  • به ترمینال SSH پایدار به نودها نیاز دارید: تونل اتصال‌های بی‌کار WebSocket را به‌طور محسوسی تهاجمی‌تر از پروکسی مستقیم قطع می‌کند.

طرح ترافیک چنین است: مرورگر از طریق HTTPS به Cloudflare مراجعه می‌کند، edge Cloudflare درخواست را از تونل از پیش بازشده به هاست منتقل می‌کند و cloudflared آن را با HTTP به سرویس محلی می‌دهد. روی هاست نه گواهی لازم است و نه پورت باز — به بیرون تنها اتصال خروجی دیمن نگاه می‌کند.

خطر

Quick Tunnel (آدرسی مانند *.trycloudflare.com) فقط برای تست خوب است: سقف اتصال‌های هم‌زمان، دامنه تصادفی دارد و تضمینی برای دسترس‌پذیری وجود ندارد. برای پروداکشن از تونل نام‌دار و دامنه خودتان استفاده کنید.

2. نصب cloudflared​

دیمن به پورت‌های ورودی نیاز ندارد: خودش اتصال‌های خروجی با نزدیک‌ترین گره Cloudflare برقرار می‌کند. در firewall کافی است اتصال‌های خروجی به پورت 7844 را مجاز کنید (UDP برای پروتکل quic و TCP برای http2)؛ پورت 443 تنها برای بررسی به‌روزرسانی‌ها لازم است. اگر ارائه‌دهنده UDP را مسدود می‌کند، پرچم --protocol http2 را به دستور اجرا اضافه کنید.

mkdir -p /opt/xlada/cloudflared && cd /opt/xlada/cloudflared

curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg \
-o /usr/share/keyrings/cloudflare-main.gpg

echo "deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] \
https://pkg.cloudflare.com/cloudflared any main" \
> /etc/apt/sources.list.d/cloudflared.list

apt update && apt install -y cloudflared
cloudflared --version

3. احراز هویت و ساخت تونل​

# یک پیوند باز می‌شود: دسترسی به دامنه را در مرورگر تأیید کنید
cloudflared tunnel login

# یک تونل نام‌دار می‌سازیم. فایل JSON کلیدها در ~/.cloudflared/ ظاهر می‌شود
cloudflared tunnel create xlada

# UUID را ببینیم — در config.yml لازم می‌شود
cloudflared tunnel list

4. رکوردهای DNS​

cloudflared tunnel route dns xlada panel.example.com
cloudflared tunnel route dns xlada sub.example.com

این دستورها رکوردهای CNAME به آدرس تونل می‌سازند و نیازی به افزودن دستی چیزی نیست. پروکسی‌کردن (ابر نارنجی) به‌طور خودکار فعال می‌شود. حالت SSL/TLS را روی Full یا Full (strict) بگذارید و Always Use HTTPS را فعال کنید تا بازدیدکننده نتواند از طریق http به پنل بیاید.

5. پیکربندی تونل​

/opt/xlada/cloudflared/config.yml
# UUID از خروجی `cloudflared tunnel list`
tunnel: 00000000-0000-0000-0000-000000000000

# فایل JSON کلیدها که با دستور cloudflared tunnel create ساخته شده است
credentials-file: /root/.cloudflared/00000000-0000-0000-0000-000000000000.json

# لاگ‌ها و صفحه محلی متریک‌های تونل
loglevel: info
logfile: /var/log/cloudflared.log
metrics: 127.0.0.1:49312

ingress:
# صفحه اشتراک: ترافیک از پروکسی محلی روی 8081 می‌گذرد
- hostname: sub.example.com
service: http://127.0.0.1:8081
originRequest:
# Host اصلی را حفظ می‌کنیم، وگرنه برنامه 127.0.0.1 را می‌بیند
httpHostHeader: sub.example.com

# پنل: پروکسی محلی روی 8080 از بخش 6
- hostname: panel.example.com
service: http://127.0.0.1:8080
originRequest:
httpHostHeader: panel.example.com
connectTimeout: 30s

# قاعده الزامی پیش‌فرض: هر چیزی که منطبق نشد
- service: http_status:404

هر دو سرویس به پروکسی محلی اشاره می‌کنند، نه مستقیماً به پورت‌های 3000 و 3010. این‌گونه هدرها همیشه توسط یک گره یکسان ساخته می‌شوند و پنل مقادیر قابل پیش‌بینی می‌گیرد، مستقل از رفتار cloudflared.

پیش از اجرا پیکربندی را بررسی کنید — این دستور به اشتباه‌های تایپی در قاعده‌ها اشاره می‌کند:

cloudflared tunnel ingress validate
cloudflared tunnel ingress rule https://panel.example.com/

6. هدرها و پروکسی محلی​

پنل و صفحه اشتراک اتصال را قطع می‌کنند اگر درخواست بدون X-Forwarded-For و X-Forwarded-Proto: https آمده باشد. بررسی کنید که این هدرها در تونل چگونه رفتار می‌کنند:

  • Cloudflare درخواست را می‌پذیرد و X-Forwarded-Proto را با مقدار طرحی که بازدیدکننده با آن آمده بازنویسی می‌کند. با فعال بودن Always Use HTTPS این مقدار همیشه https است؛
  • Cloudflare هدر X-Forwarded-For را تکمیل می‌کند، نه جایگزین: اگر کلاینت هدر خودش را فرستاده باشد، در زنجیره ابتدا مقادیر جای‌گذاشته‌شده توسط او قرار می‌گیرند و آدرس واقعی — در انتها. نمی‌توان به مقدار سمت چپ تکیه کرد؛
  • cloudflared درخواست را همان‌طور که هست به سرویس originRequest می‌دهد و نمی‌تواند به‌اجبار X-Forwarded-Proto: https را تنظیم کند. اگر بازدیدکننده با http آمده باشد، پنل چنین درخواستی را رد می‌کند.

بنابراین گزینه قابل اتکا این است که یک پروکسی کوچک جلوی پنل بگذارید که خودش هدرها را تنظیم کند. در ادامه پیکربندی کامل هر دو سرور origin محلی آمده است: پنل روی 127.0.0.1:8080 و صفحه اشتراک روی 127.0.0.1:8081.

/etc/nginx/sites-available/xlada-origin
# سرورهای origin محلی برای Cloudflare Tunnel.

map $http_upgrade $cf_connection_upgrade {
default upgrade;
'' close;
}

gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_min_length 256;
gzip_types
application/javascript
application/json
application/manifest+json
image/svg+xml
text/css
text/javascript
text/plain
text/xml;

# ─── پنل XLADA: cloudflared دامنه panel.example.com را به اینجا می‌دهد ─────────
server {
listen 127.0.0.1:8080;
server_name panel.example.com;

client_max_body_size 32m;

# فایل‌های ایستا بیلد پنل: هش در نام فایل امکان کش کردن آن را می‌دهد
location ^~ /assets/ {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;

proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header Connection "";

expires 365d;
add_header Cache-Control "public, max-age=31536000, immutable";
access_log off;
}

location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;

# WebSocket — ترمینال SSH به نودها
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $cf_connection_upgrade;

proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;

# فقط آدرس واقعی کاربر، بدون زنجیره جعل‌شده
proxy_set_header X-Forwarded-For $remote_addr;

# خط کلیدی: پنل منتظر https است
proxy_set_header X-Forwarded-Proto https;

proxy_connect_timeout 10s;
proxy_send_timeout 86400s;
proxy_read_timeout 86400s;
}
}

# ─── صفحه اشتراک: cloudflared دامنه sub.example.com را به اینجا می‌دهد ──────
server {
listen 127.0.0.1:8081;
server_name sub.example.com;

client_max_body_size 10m;

location ^~ /assets/ {
proxy_pass http://127.0.0.1:3010;
proxy_http_version 1.1;

proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header Connection "";

expires 365d;
add_header Cache-Control "public, max-age=31536000, immutable";
access_log off;
}

location / {
proxy_pass http://127.0.0.1:3010;
proxy_http_version 1.1;

proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto https;

# WebSocket: فعلاً استفاده نمی‌شود، اما پیکربندی برای آن آماده است
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $cf_connection_upgrade;

proxy_connect_timeout 10s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
}
}
ln -sf /etc/nginx/sites-available/xlada-origin /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx

هر دو server_name با httpHostHeader در قاعده‌های config.yml یکسان هستند، بنابراین پنل و صفحه اشتراک دامنه اصلی را می‌گیرند، نه 127.0.0.1 را.

7. اجرا با کانتینر​

/opt/xlada/cloudflared/docker-compose.yml
services:
cloudflared:
image: cloudflare/cloudflared:latest
container_name: cloudflared
restart: unless-stopped
# تونل به بیرون می‌رود، پورت‌های ورودی لازم نیستند
network_mode: host
command: tunnel --no-autoupdate --config /etc/cloudflared/config.yml run
volumes:
- ./config.yml:/etc/cloudflared/config.yml:ro
- ./credentials.json:/etc/cloudflared/credentials.json:ro
# فایل JSON کلیدها را از ~/.cloudflared/ کنار فایل compose کپی کنید
cp /root/.cloudflared/00000000-0000-0000-0000-000000000000.json \
/opt/xlada/cloudflared/credentials.json

docker compose up -d && docker compose logs -f

در کانتینر، فایل config.yml تنها در مسیر کلیدهای تونل تفاوت دارد. فایل کامل برای این روش:

/opt/xlada/cloudflared/config.yml
# UUID از خروجی `cloudflared tunnel list`
tunnel: 00000000-0000-0000-0000-000000000000

# کلیدهای تونل کنار docker-compose.yml قرار دارند
credentials-file: /etc/cloudflared/credentials.json

# لاگ‌ها و صفحه محلی متریک‌های تونل
loglevel: info
logfile: /var/log/cloudflared.log
metrics: 127.0.0.1:49312

ingress:
# صفحه اشتراک: پروکسی محلی روی 8081
- hostname: sub.example.com
service: http://127.0.0.1:8081
originRequest:
httpHostHeader: sub.example.com

# پنل: پروکسی محلی روی 8080
- hostname: panel.example.com
service: http://127.0.0.1:8080
originRequest:
httpHostHeader: panel.example.com
connectTimeout: 30s

# قاعده الزامی پیش‌فرض: هر چیزی که منطبق نشد
- service: http_status:404

8. اجرا به‌صورت سرویس​

mkdir -p /etc/cloudflared
cp config.yml /etc/cloudflared/config.yml
cp /root/.cloudflared/*.json /etc/cloudflared/

cloudflared service install
systemctl enable --now cloudflared
systemctl status cloudflared

9. محافظت بیشتر از پنل​

آدرس عمومی پنل را همه می‌بینند، بنابراین یک رمز عبور کافی نیست. دو روش برای بستن ورود وجود دارد:

  • Cloudflare Access — سیاست Zero Trust جلوی برنامه: پنل تنها پس از ورود به identity provider شما باز می‌شود. صفحه اشتراک را می‌توان عمومی گذاشت یا جداگانه محافظت کرد؛ برای آن در .env متغیرهای CLOUDFLARE_ZERO_TRUST_CLIENT_ID و CLOUDFLARE_ZERO_TRUST_CLIENT_SECRET در نظر گرفته شده‌اند؛
  • قاعده WAF با محدودیت بر اساس کشور یا فهرست آدرس‌ها، اگر مدیران از شبکه‌های شناخته‌شده کار می‌کنند.

هر دو سازوکار مکمل تنظیمات خود پنل هستند، نه جایگزین آن: احراز هویت دوعاملی را فعال کنید و محدودیت تلاش‌های ورود را غیرفعال نکنید.

10. محدودیت‌ها​

  • IP واقعی کاربر. Cloudflare هدر CF-Connecting-IP را با آدرس کلاینت اضافه می‌کند؛ منبع حقیقت توصیه‌شده دقیقاً همین است، نه X-Forwarded-For. اگر جلوی پنل nginx قرار دارد، فقط به محدوده‌های Cloudflare اعتماد کنید. فایل کامل /etc/nginx/conf.d/real-ip.conf چنین است:
/etc/nginx/conf.d/real-ip.conf
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 104.16.0.0/13;
set_real_ip_from 104.24.0.0/14;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 131.0.72.0/22;

real_ip_header CF-Connecting-IP;
real_ip_recursive on;

فهرست محدوده‌های Cloudflare به‌طور دوره‌ای به‌روزرسانی می‌شود — آن را با نسخه به‌روز مقایسه کنید.

  • WebSocket و ترمینال SSH. WebSocket از طریق Cloudflare کار می‌کند، اما اتصال‌های بی‌کار قطع می‌شوند. برای ترمینال به نودها قابل اتکاتر است مستقیماً به پنل متصل شوید و صفحه اشتراک را از طریق تونل ارائه دهید.
  • محدودیت‌ها. طرح رایگان محدودیت‌هایی در اندازه فایل‌های بارگذاری‌شده و تعداد اتصال‌های هم‌زمان دارد؛ مقادیر به‌طور دوره‌ای تغییر می‌کنند.
  • SSL/TLS. حالت Flexible به ریدایرکت حلقه‌ای و از دست رفتن هدرها می‌انجامد. از Full یا Full (strict) استفاده کنید.
  • پورت‌ها. تونل به بیرون فقط پورت‌های استاندارد Cloudflare را منتشر می‌کند (443، 80 و بخشی از پورت‌های جایگزین). آدرس‌های داخلی مانند 3010 محلی باقی می‌مانند.

11. بررسی​

cloudflared tunnel info xlada
cloudflared tunnel ingress validate

curl -sI https://panel.example.com | head -3
curl -s -o /dev/null -w '%{http_code}\n' https://sub.example.com/

journalctl -u cloudflared -n 30
tail -n 30 /var/log/cloudflared.log

ریشه پنل باید صفحه ورود را برگرداند، نه 502 و نه خطای مربوط به هدرها. در صورت 502 مقدار service در config.yml و اینکه پروکسی محلی به پورت درست گوش می‌دهد را بررسی کنید.

قدم بعدی​