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

پروکسی معکوس

پنل XLADA بدون پروکسی معکوس با TLS کار نمی‌کند. در کد آن میان‌افزاری وجود دارد که اگر درخواست بدون هدرهای X-Forwarded-For و X-Forwarded-Proto: https برسد، اتصال را قطع می‌کند. این یک هوس نیست: پنل باید مطمئن باشد که نودی مورد اعتماد آن را به بیرون ارائه می‌دهد، در غیر این صورت تعیین IP واقعی کاربر ممکن نیست.

یک وب‌سرور انتخاب کنید — در هر بخش یک پیکربندی کامل و کارآمد قرار دارد، نه یک نمونه حداقلی:

گزینهچه زمانی انتخاب کنید
nginxکلاسیک، کنترل حداکثری بر هر هدر و تایم‌اوت
Caddyحداقل پیکربندی، گواهی‌ها به‌صورت خودکار صادر می‌شوند
Angieفورکی از nginx با ماژول‌های آماده؛ سینتکس آشنا
Traefikاز قبل یک استک Docker دارید و می‌خواهید مسیرها را با label‌ها توصیف کنید
Cloudflare Tunnelسرور IP سفید ندارد یا باید origin را پنهان کرد

همه گزینه‌ها یک مسئله را حل می‌کنند: دریافت HTTPS از بیرون، افزودن هدرها و ارسال درخواست به 127.0.0.1:3000 (پنل) یا 127.0.0.1:3010 (صفحه اشتراک). پنل، پایگاه داده و Valkey فقط به localhost گوش می‌دهند — آن‌ها را به بیرون ارائه نمی‌کنند.

چه چیزی در هر حالت باید تنظیم شود​

موارد فهرست‌شده در ادامه در هر پیکربندی کامل در صفحات این بخش وجود دارد. اینجا توضیح می‌دهیم اگر پیکربندی را برای خودتان تغییر می‌دهید، دقیقاً چه چیزی را بررسی کنید.

۱. هدرهای پروکسی​

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

X-Forwarded-Proto را بازنویسی نکنید

حتی اگر TLS روی یک لودبالانسر بالادست ترمینیت می‌شود، http را اینجا به‌صورت دستی جایگذاری نکنید: پنل از کار می‌افتد.

۲. WebSocket​

از پنل یک ترمینال SSH به نودها باز می‌شود — این یک WebSocket است. هدر Connection تنها زمانی جایگذاری می‌شود که کلاینت درخواست ارتقای پروتکل کند، بنابراین در nginx و Angie آن را با map $http_upgrade $connection_upgrade محاسبه می‌کنند. بدون عبور دادن هدرهای Upgrade و Connection، ترمینال بی‌صدا متصل نمی‌شود.

۳. IP واقعی کاربر​

سه چیز در XLADA به IP صحیح وابسته است:

  • محدودیت دستگاه‌ها (HWID) — بر اساس درخواست‌های اشتراک محاسبه می‌شود؛
  • ژورنال اقدامات مدیر — IP را ثبت می‌کند؛
  • محافظت از ورود در برابر جست‌وجوی فراگیر — مسدودسازی‌ها بر اساس جفت «نام کاربری + IP» محاسبه می‌شوند.

اگر جلوی وب‌سرور یک CDN یا پروکسی دوم قرار دارد، اعتماد به هدرهای آن را تنظیم کنید. فایل کامل اعتماد به زیرشبکه‌های Cloudflare برای nginx چنین است:

/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;

در Caddy مسئول این کار trusted_proxies static است، در Traefik — forwardedHeaders.trustedIPs، در Cloudflare Tunnel — طرح اختصاصی با پروکسی محلی. در غیر این صورت همه کلاینت‌ها مانند یک IP دیده می‌شوند و محدودیت‌ها آن‌طور که انتظار دارید عمل نمی‌کنند.

۴. تایم‌اوت‌ها، استریمینگ و کش​

  • اتصال‌های طولانی. ترمینال SSH و پاسخ‌های استریمینگ API دقیقه‌ها زنده می‌مانند: proxy_read_timeout را کمتر از 300 ثانیه تنظیم نکنید، برای ترمینال — یک شبانه‌روز.
  • خاموش کردن بافرینگ. برای اندپوینت‌های استریمی proxy_buffering off مفید است، در غیر این صورت پاسخ در بافر جمع می‌شود و تکه‌تکه می‌رسد.
  • اندازه بدنه درخواست. وارد کردن پیکربندی‌ها و قالب‌ها می‌تواند بزرگ باشد — client_max_body_size 32m خطاهای 413 غیرمنتظره را برمی‌دارد.
  • کش فایل‌های استاتیک. بیلد رابط کاربری در /assets/ قرار دارد و هش را در نام فایل دارد، بنابراین آن را با هدر Cache-Control: public, immutable و عمر طولانی ارائه می‌دهند.

پیکربندی کامل nginx​

در ادامه پیکربندی کامل و کارآمد پنل آمده است: گواهی، TLS، هدرها، WebSocket، تایم‌اوت‌ها، gzip و کش استاتیک. صفحه جداگانه nginx آن را با هاست صفحه اشتراک، فهرست مجاز IP و اجرا در Docker تکمیل می‌کند.

/etc/nginx/sites-available/xlada-panel
upstream xlada_panel {
server 127.0.0.1:3000;
keepalive 32;
}

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

# HTTP: تأیید مالکیت دامنه و ریدایرکت به HTTPS
server {
listen 80;
listen [::]:80;
server_name panel.example.com;

location /.well-known/acme-challenge/ {
root /var/www/certbot;
}

location / {
return 301 https://$host$request_uri;
}
}

# HTTPS
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name panel.example.com;

ssl_certificate /etc/letsencrypt/live/panel.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/panel.example.com/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/panel.example.com/chain.pem;

ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305;
ssl_prefer_server_ciphers off;

ssl_session_timeout 1d;
ssl_session_cache shared:XLADA_TLS:10m;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 1.0.0.1 8.8.8.8 8.8.4.4 valid=60s;
resolver_timeout 2s;

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

client_max_body_size 32m;
client_body_timeout 60s;

gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_min_length 256;
gzip_types
application/atom+xml
application/geo+json
application/javascript
application/json
application/ld+json
application/manifest+json
application/rdf+xml
application/rss+xml
application/wasm
application/xhtml+xml
application/xml
font/otf
font/ttf
image/svg+xml
text/css
text/javascript
text/plain
text/xml;

# استاتیک رابط کاربری: هش در نام فایل کش کردن طولانی‌مدت آن را ممکن می‌کند
location ^~ /assets/ {
proxy_pass http://xlada_panel;
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 Connection "";

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

# API و پاسخ‌های استریمی: بدون بافرینگ
location /api/ {
proxy_pass http://xlada_panel;
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_connect_timeout 10s;
proxy_send_timeout 86400s;
proxy_read_timeout 86400s;
proxy_buffering off;
proxy_cache off;
chunked_transfer_encoding on;
}

# رابط پنل
location / {
proxy_pass http://xlada_panel;
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_connect_timeout 10s;
proxy_send_timeout 86400s;
proxy_read_timeout 86400s;

proxy_buffering on;
}
}

درخواست‌های مبتنی بر IP و درخواست‌های مربوط به دامنه‌های ناشناخته را یک بلوک جایگزین جداگانه قطع می‌کند:

/etc/nginx/conf.d/00-default.conf
server {
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
server_name _;

ssl_reject_handshake on;
}

هاست را فعال کنید و nginx را دوباره بارگذاری کنید:

sudo ln -sf /etc/nginx/sites-available/xlada-panel /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

گواهی این پیکربندی را certbot صادر می‌کند — توالی کامل در صفحه nginx توضیح داده شده است.

نسخه کوتاه‌شده​

اگر برای آزمایش به یک حداقل نیاز دارید، همین پیکربندی کامل اما کوتاه مناسب است: همان ریدایرکت، TLS، هدرها و WebSocket، بدون gzip، کش استاتیک و بلوک جداگانه برای API.

/etc/nginx/sites-available/xlada-panel
server {
listen 80;
listen [::]:80;
server_name panel.example.com;

location /.well-known/acme-challenge/ {
root /var/www/certbot;
}

location / {
return 301 https://$host$request_uri;
}
}

server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name panel.example.com;

ssl_certificate /etc/letsencrypt/live/panel.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/panel.example.com/privkey.pem;

ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_timeout 1d;
ssl_session_cache shared:XLADA_TLS:10m;
ssl_session_tickets off;

client_max_body_size 32m;

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

proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

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_read_timeout 86400;
}
}

بررسی​

nginx -t && systemctl reload nginx

# پنل پاسخ می‌دهد و HTTPS را می‌بیند
curl -sI https://panel.example.com | head -3
curl -s https://panel.example.com/api/system/health

# در لاگ‌ها پیام «Reverse proxy and HTTPS are required» وجود ندارد
tail -n 50 /var/log/nginx/error.log

چک‌لیست​

  • HTTP به HTTPS ریدایرکت می‌شود، گواهی به‌صورت خودکار تمدید می‌شود.
  • X-Forwarded-For و X-Forwarded-Proto: https به پنل می‌رسند.
  • WebSocket کار می‌کند: ترمینال SSH به نود باز می‌شود.
  • IP واقعی کاربر در ژورنال اقدامات مدیر دیده می‌شود.
  • پنل و صفحه اشتراک با دامنه‌های متفاوت ارائه می‌شوند (اگر صفحه اشتراک فعال باشد).
  • پورت‌های 3000، 3001 و 3010 به بیرون بسته هستند — فقط از localhost در دسترس‌اند.

گام‌های بعدی​

  • nginx — پیکربندی کامل با TLS، gzip، کش و security headers.
  • Caddy — گواهی‌ها «از جعبه».
  • Traefik — مسیرها با label‌ها و فهرست مجاز IP.
  • امنیت پنل — چه کارهایی انجام شده و چه چیزی را باید به خاطر داشت.