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

Angie

مرور کلی​

Angie یک فورک از nginx با مجموعه ماژول‌های گسترده‌تر، کلاینت ACME داخلی و پشتیبانی از HTTP/3 است. برای پنل XLADA همانند nginx استفاده می‌شود: TLS را ترمینیت می‌کند و درخواست‌ها را به پورت‌های loopback پنل و صفحه اشتراک پروکسی می‌کند.

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

در مثال‌ها دامنه‌های panel.example.com و sub.example.com، آپ‌استریم‌های 127.0.0.1:3000 (پنل) و 127.0.0.1:3010 (صفحه اشتراک) پورت‌های کانتینرهای xpanel و xpanel-subpage روی loopback میزبان هستند.

هشدار

به یک دامنه ثبت‌شده نیاز دارید. تا زمانی که دامنه به سرور شما اشاره نکند، صدور گواهی Let's Encrypt ممکن نیست.

DNS​

آدرس IP سرور را بررسی کنید (ترجیحاً استاتیک) و رکوردها را نزد ارائه‌دهنده DNS یا ثبت‌کننده ایجاد کنید:

نوعناممقدار
Apanelآدرس IPv4 سرور
Asubآدرس IPv4 سرور
AAAApanelآدرس IPv6 سرور
AAAAsubآدرس IPv6 سرور

در Cloudflare رکوردها در بخش DNS → Records → Create record ساخته می‌شوند.

  • ابر خاکستری — ترافیک مستقیم می‌رود و Angie IP واقعی کاربر را می‌بیند؛ اگر Cloudflare در دسترس نیست یا مزاحمت ایجاد می‌کند از این حالت استفاده کنید.
  • ابر نارنجی — ترافیک از شبکه Cloudflare عبور می‌کند: IP سرور پنهان می‌شود و محافظت در برابر حملات فعال است. در این حالت Angie آدرس‌های Cloudflare را می‌بیند، بنابراین برای IP صحیح ماژول realip و اعتماد به هدر CF-Connecting-IP را فعال کنید.

گواهی‌های TLS​

دایرکتوری‌هایی برای گواهی‌ها و بررسی‌های ACME بسازید:

sudo mkdir -p /etc/angie/ssl /var/www/acme

گزینه ۱: certbot​

sudo apt-get update && sudo apt-get install -y certbot
sudo certbot certonly --webroot -w /var/www/acme \
-d panel.example.com -d sub.example.com \
--email admin@example.com --agree-tos --no-eff-email

sudo ln -sf /etc/letsencrypt/live/panel.example.com/fullchain.pem /etc/angie/ssl/fullchain.pem
sudo ln -sf /etc/letsencrypt/live/panel.example.com/privkey.pem /etc/angie/ssl/privkey.key

گزینه ۲: acme.sh​

curl https://get.acme.sh | sh -s email=admin@example.com

acme.sh --issue --webroot /var/www/acme \
-d panel.example.com -d sub.example.com \
--key-file /etc/angie/ssl/privkey.key \
--fullchain-file /etc/angie/ssl/fullchain.pem \
--reloadcmd "docker exec angie angie -s reload"

هر دو کلاینت گواهی را به‌صورت خودکار تمدید می‌کنند: certbot با تایمر systemd خود، acme.sh با یک وظیفه در cron هر ۶۰ روز. پورت 80 باید باز باشد و به مسیر بررسی ACME که در پیکربندی زیر توضیح می‌دهیم پاسخ دهد.

Angie کلاینت ACME اختصاصی خود را نیز دارد: گواهی را بدون certbot و acme.sh صادر می‌کند. پیکربندی کامل این حالت در زیربخش «گزینه با ACME داخلی» آمده است.

پیکربندی کامل angie.conf​

sudo mkdir -p /opt/angie && cd /opt/angie
sudo nano angie.conf
/opt/angie/angie.conf
# ─── آپ‌استریم‌ها: کانتینرهای xpanel و xpanel-subpage ──────────────────
upstream xpanel_panel {
server 127.0.0.1:3000;
keepalive 32;
}

upstream xpanel_subpage {
server 127.0.0.1:3010;
keepalive 16;
}

# WebSocket: هدر Connection را فقط هنگام Upgrade تنظیم می‌کنیم
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}

# ─── Gzip برای همه میزبان‌های مجازی ────────────────────────────────
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/xhtml+xml
application/xml
application/wasm
font/eot
font/otf
font/ttf
image/svg+xml
text/css
text/javascript
text/plain
text/xml;

# ─── HTTP: بررسی ACME و ریدایرکت به HTTPS ─────────────────────────
server {
listen 80;
listen [::]:80;
server_name panel.example.com sub.example.com;

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

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

# ─── پنل XLADA ────────────────────────────────────────────────────
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name panel.example.com;

# TLS (Mozilla Intermediate)
ssl_certificate /etc/angie/ssl/fullchain.pem;
ssl_certificate_key /etc/angie/ssl/privkey.key;
ssl_trusted_certificate /etc/angie/ssl/fullchain.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;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL: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 208.67.222.222 208.67.220.220 valid=60s;
resolver_timeout 2s;

# Security headers
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 25m;

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

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 https;
proxy_set_header Connection "";

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

location / {
proxy_http_version 1.1;
proxy_pass http://xpanel_panel;

# پنل بدون این دو هدر اتصال را قطع می‌کند
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 https;
proxy_set_header X-Forwarded-Host $host;

# WebSocket — ترمینال SSH به نودها
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; # استریم و SSE
proxy_cache off;
}
}

# ─── صفحه اشتراک ───────────────────────────────────────────────
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name sub.example.com;

ssl_certificate /etc/angie/ssl/fullchain.pem;
ssl_certificate_key /etc/angie/ssl/privkey.key;
ssl_trusted_certificate /etc/angie/ssl/fullchain.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;
ssl_prefer_server_ciphers off;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 8.8.8.8 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 Referrer-Policy strict-origin-when-cross-origin always;

client_max_body_size 10m;

# استاتیک بیلد صفحه اشتراک
location ^~ /assets/ {
proxy_http_version 1.1;
proxy_pass http://xpanel_subpage;

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 https;
proxy_set_header Connection "";

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

location / {
proxy_http_version 1.1;
proxy_pass http://xpanel_subpage;

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 https;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;

proxy_connect_timeout 10s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
proxy_buffering off;
}
}

# ─── قطع TLS handshake برای IP و دامنه‌های ناشناخته ──────────────
server {
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
server_name _;

ssl_reject_handshake on;
}

هنگام نصب روی میزبان، فایل را به /etc/angie/http.d/xlada.conf کپی کنید. هنگام اجرا در Docker، همان‌طور که در زیر نشان داده شده، کل فایل در کانتینر mount می‌شود.

از دایرکتیو reuseport استفاده نمی‌کنیم: با چند میزبان مجازی روی یک آدرس و پورت واحد سازگار نیست — Angie با خطای duplicate listen options خاتمه می‌یابد.

گزینه با ACME داخلی​

اگر به کلاینت‌های خارجی نیازی نیست، Angie خودش گواهی را صادر می‌کند. پیکربندی زیر خودکفاست: نیازی به صدور از قبل نیست؛ Angie گواهی را در اولین اجرا دریافت می‌کند و آن را به‌صورت خودکار تمدید می‌کند. احراز مالکیت دامنه از طریق TLS-ALPN روی پورت 443 انجام می‌شود، بنابراین پورت 80 فقط ریدایرکت می‌کند. وضعیت ACME در /var/lib/angie/acme/ ذخیره می‌شود — این دایرکتوری باید از راه‌اندازی مجدد کانتینر جان سالم به در ببرد.

/opt/angie/angie.conf
# ─── آپ‌استریم‌ها: کانتینرهای xpanel و xpanel-subpage ──────────────────
upstream xpanel_panel {
server 127.0.0.1:3000;
keepalive 32;
}

upstream xpanel_subpage {
server 127.0.0.1:3010;
keepalive 16;
}

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

# ─── ACME داخلی ─────────────────────────────────────────────────
resolver 1.1.1.1 1.0.0.1 8.8.8.8 8.8.4.4 valid=60s;
resolver_timeout 2s;
acme_client acme_le https://acme-v02.api.letsencrypt.org/directory;

# ─── Gzip برای همه میزبان‌های مجازی ────────────────────────────────
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/xhtml+xml
application/xml
application/wasm
font/eot
font/otf
font/ttf
image/svg+xml
text/css
text/javascript
text/plain
text/xml;

# ─── HTTP: ریدایرکت به HTTPS ─────────────────────────────────────────
server {
listen 80;
listen [::]:80;
server_name panel.example.com sub.example.com;

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

# ─── پنل XLADA ────────────────────────────────────────────────────
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name panel.example.com;

acme acme_le;
ssl_certificate $acme_cert_acme_le;
ssl_certificate_key $acme_cert_key_acme_le;

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;
ssl_prefer_server_ciphers off;
ssl_session_timeout 1d;
ssl_session_cache shared:ACME_TLS:10m;
ssl_session_tickets off;

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 25m;

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

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 https;
proxy_set_header Connection "";

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

location / {
proxy_http_version 1.1;
proxy_pass http://xpanel_panel;

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 https;
proxy_set_header X-Forwarded-Host $host;

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

# ─── صفحه اشتراک ───────────────────────────────────────────────
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name sub.example.com;

acme acme_le;
ssl_certificate $acme_cert_acme_le;
ssl_certificate_key $acme_cert_key_acme_le;

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;
ssl_prefer_server_ciphers off;
ssl_session_timeout 1d;
ssl_session_cache shared:ACME_TLS:10m;
ssl_session_tickets off;

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

client_max_body_size 10m;

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

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 https;
proxy_set_header Connection "";

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

location / {
proxy_http_version 1.1;
proxy_pass http://xpanel_subpage;

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 https;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;

proxy_connect_timeout 10s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
proxy_buffering off;
}
}

# ─── قطع TLS handshake برای IP و دامنه‌های ناشناخته ──────────────
server {
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
server_name _;

ssl_reject_handshake on;
}

پیکربندی را بررسی و Angie را دوباره بارگذاری کنید:

sudo angie -t && sudo angie -s reload
docker compose exec angie angie -t && docker compose exec angie angie -s reload

اجرا در Docker​

/opt/angie/docker-compose.yml
services:
angie:
image: docker.angie.software/angie:1.9.0
container_name: angie
restart: unless-stopped
# حالت host تا Angie بتواند 127.0.0.1:3000 و 127.0.0.1:3010 را ببیند
network_mode: host
environment:
- TZ=UTC
volumes:
- ./angie.conf:/etc/angie/http.d/default.conf:ro
- /etc/angie/ssl:/etc/angie/ssl:ro # گواهی‌های Let's Encrypt
- /var/www/acme:/var/www/acme:ro # دایرکتوری بررسی‌های ACME
- angie-ssl-data:/var/lib/angie/acme/ # وضعیت ACME داخلی

volumes:
angie-ssl-data:
name: angie-ssl-data
cd /opt/angie
docker compose up -d
docker compose logs -f -t

حالت Host عمداً انتخاب شده است: کانتینر همان پورت‌های loopback 127.0.0.1:3000 و 127.0.0.1:3010 را می‌بیند که میزبان می‌بیند، بنابراین آپ‌استریم‌های angie.conf را نیازی نیست به نام کانتینرها بازنویسی کنید. گواهی‌ها و دایرکتوری بررسی‌های ACME در این حالت روی میزبان باقی می‌مانند.

بررسی و بارگذاری مجدد​

# بررسی سینتکس
sudo angie -t
docker compose exec angie angie -t

# دامپ کامل پیکربندی نهایی
sudo angie -T

# بارگذاری مجدد نرم بدون قطع اتصال‌ها
sudo angie -s reload
docker compose exec angie angie -s reload

در لاگ‌ها به چه چیزهایی توجه کنیم​

journalctl -u angie -f # نصب روی میزبان
docker compose logs -f -t angie
  • [emerg] — خطای پیکربندی؛ angie -t خط را نشان می‌دهد؛
  • connect() failed (111: Connection refused) — پنل یا صفحه اشتراک روی پورت مورد انتظار اجرا نشده است؛
  • upstream timed out — برای عملیات طولانی proxy_read_timeout را افزایش دهید؛
  • SSL_do_handshake() failed — اسکنرهای معمول؛ نیازی به واکنش ندارند.
curl -I https://panel.example.com
curl -I https://sub.example.com

هر دو پاسخ باید با HTTP/2 200 شروع شوند. اگر در ممیزی پنل آدرس‌های Cloudflare دیده می‌شود، اعتماد به CF-Connecting-IP را از طریق ماژول realip فعال کنید.

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