Caddy
مرور کلی
Caddy یک پروکسی معکوس است که خودش گواهیهای Let's
Encrypt را دریافت و تمدید میکند: نیازی به کلاینت ACME جداگانه، cron و بارگذاری مجدد دستی نیست. پنل XLADA
و صفحه اشتراک فقط پشت پروکسی با HTTPS کار میکنند — میانافزار آنها
اگر درخواست بدون هدرهای X-Forwarded-For و
X-Forwarded-Proto: https برسد، اتصال را قطع میکند.
Caddy خودش X-Forwarded-* را تنظیم میکند. در پیکربندی زیر با این حال
آنها را بهصورت صریح تعیین میکنیم: اینگونه اگر CDN یا
لود بالانسر جلوی Caddy قرار بگیرد رفتار تغییر نمیکند و X-Forwarded-Proto به http تبدیل نمیشود.
در مثالها دامنههای 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 یا ثبتکننده ایجاد کنید:
| نوع | نام | مقدار |
|---|---|---|
A | panel | آدرس IPv4 سرور |
A | sub | آدرس IPv4 سرور |
AAAA | panel | آدرس IPv6 سرور |
AAAA | sub | آدرس IPv6 سرور |
در Cloudflare رکوردها در بخش DNS → Records → Create record اضافه میشوند:
نوع A، در فیلد نام panel یا sub و در فیلد آدرس IP سرور.
- ابر خاکستری — ترافیک مستقیم به سرور شما میرود و Caddy IP واقعی کاربر را میبیند. اگر Cloudflare در دسترس نیست یا مزاحمت ایجاد میکند این حالت را انتخاب کنید.
- ابر نارنجی — ترافیک از شبکه Cloudflare عبور میکند: IP سرور پنهان است و
محافظت در برابر حملات فعال است. در اینجا حتماً محدودههای Cloudflare را در
trusted_proxies staticدر Caddyfile مشخص کنید، وگرنه همه کلاینتها بهصورت یک آدرس Cloudflare دیده میشوند و محدودیتهای HWID، ممیزی و محافظت از ورود در برابر حمله جستوجوی فراگیر بیمعنی میشوند.
منتظر بهروزرسانی رکوردها بمانید: dig +short panel.example.com باید آدرس
سرور و در حالت ابر نارنجی آدرسهای Cloudflare را برگرداند.
گواهیها
Caddy بهمحض اینکه دامنه در Caddyfile مشخص شود، گواهی را بهصورت خودکار دریافت
میکند. برای احراز دامنه، پورتهای 80 (HTTP-01) و 443 (TLS-ALPN-01) باید باز
باشند؛ تمدید نیز خودکار است — حدود ۳۰ روز پیش از انقضا. در
بلوک global کافی است email را مشخص کنید: اعلانهای مربوط به
مشکلات گواهی به این آدرس میرسند.
Caddyfile کامل
دایرکتوری و فایل را بسازید:
sudo mkdir -p /opt/caddy && cd /opt/caddy
sudo nano Caddyfile
# ─── پارامترهای global ────────────────────────────────────────────
{
# ایمیل برای اعلانهای Let's Encrypt
email admin@example.com
# IP واقعی کاربر پشت Cloudflare. محدودهها را Cloudflare منتشر میکند؛
# بهتر است فهرست آنها را بهصورت دورهای با نسخه بهروز بررسی کنید.
servers {
trusted_proxies static \
173.245.48.0/20 103.21.244.0/22 103.22.200.0/22 103.31.4.0/22 \
141.101.64.0/18 108.162.192.0/18 190.93.240.0/20 188.114.96.0/20 \
197.234.240.0/22 198.41.128.0/17 162.158.0.0/15 104.16.0.0/13 \
104.24.0.0/14 172.64.0.0/13 131.0.72.0/22 2400:cb00::/32 \
2606:4700::/32 2803:f800::/32 2405:b500::/32 2405:8100::/32 \
2a06:98c0::/29 2c0f:f248::/32
client_ip_headers CF-Connecting-IP X-Forwarded-For
}
}
# ─── پنل XLADA ────────────────────────────────────────────────────
panel.example.com {
encode zstd gzip
# Security headers
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Content-Type-Options "nosniff"
X-Frame-Options "SAMEORIGIN"
Referrer-Policy "strict-origin-when-cross-origin"
-Server
}
# استاتیک بیلد: هش در نام فایل امکان کش طولانیمدت آن را میدهد
@assets path /assets/*
header @assets Cache-Control "public, max-age=31536000, immutable"
reverse_proxy 127.0.0.1:3000 {
# پنل بدون این دو هدر اتصال را قطع میکند.
# X-Forwarded-For را بازنویسی میکنیم تا کلاینت IP خود را جعل نکند.
header_up X-Forwarded-For {client_ip}
header_up X-Forwarded-Proto https
header_up X-Real-IP {client_ip}
header_up Host {host}
# WebSocket: Caddy خودش Upgrade و Connection را مدیریت میکند،
# هدرهای جداگانه لازم نیستند. flush_interval -1
# بافرینگ را غیرفعال میکند — این برای SSE و استریم آمار مهم است.
flush_interval -1
}
}
# ─── صفحه اشتراک ───────────────────────────────────────────────
sub.example.com {
encode zstd gzip
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Content-Type-Options "nosniff"
X-Frame-Options "SAMEORIGIN"
Referrer-Policy "strict-origin-when-cross-origin"
-Server
}
# استاتیک بیلد: هش در نام فایل امکان کش طولانیمدت آن را میدهد
@assets path /assets/*
header @assets Cache-Control "public, max-age=31536000, immutable"
reverse_proxy 127.0.0.1:3010 {
header_up X-Forwarded-For {client_ip}
header_up X-Forwarded-Proto https
header_up X-Real-IP {client_ip}
header_up Host {host}
flush_interval -1
}
}
# ─── درخواستها بر اساس IP و به دامنههای ناشناخته ───────────────────────────
:443 {
tls internal
respond 204
}
دایرکتیوهای کلیدی:
| دایرکتیو | کاربرد |
|---|---|
trusted_proxies static | اعتماد به هدرهای Cloudflare از این زیرشبکهها |
client_ip_headers | گرفتن IP واقعی از CF-Connecting-IP |
header_up X-Forwarded-For | انتقال IP واقعی کاربر به پنل |
header_up X-Forwarded-Proto https | پنل فقط https را میپذیرد |
flush_interval -1 | ارسال استریمی بدون بافرینگ |
@assets و header | کش طولانی برای استاتیک بیلد از /assets/ |
:443 با tls internal | اسکنرهایی را که با IP در میزنند قطع میکند |
روی دامنه sub.example.com احراز هویت HTTP اضافی اضافه نکنید:
صفحه اشتراک را اپلیکیشنهای کلاینت درخواست میکنند که نمیتوانند
نام کاربری و رمز عبور وارد کنند. از ورود به پنل با احراز هویت خود پنل و TOTP محافظت کنید.
اجرا در Docker
services:
caddy:
image: caddy:2.9
container_name: caddy
restart: unless-stopped
# حالت host لازم است تا Caddy بتواند 127.0.0.1:3000 و 127.0.0.1:3010 را ببیند
network_mode: host
environment:
- TZ=UTC
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- ./data:/data # گواهیها و کلیدهای ACME
- ./config:/config # وضعیت داخلی Caddy
cd /opt/caddy
docker compose up -d
docker compose logs -f -t
حالت Host عمداً انتخاب شده است: فقط به این ترتیب کانتینر Caddy پورتهای loopback
پنل و صفحه اشتراک را میبیند و گواهیها روی میزبان باقی میمانند. پورتهای 80 و 443 را
خود Caddy گوش میدهد و 3000، 3010 و 3001 به بیرون باز نمیشوند.
بررسی و بارگذاری مجدد
# بررسی سینتکس بدون راهاندازی مجدد
caddy validate --config /opt/caddy/Caddyfile
# مرتبسازی فایل به شکل متعارف
caddy fmt --overwrite /opt/caddy/Caddyfile
# بارگذاری مجدد نرم: اتصالها قطع نمیشوند
systemctl reload caddy
# در Docker همان دستورها داخل کانتینر اجرا میشوند
docker compose exec caddy caddy validate --config /etc/caddy/Caddyfile
docker compose exec caddy caddy reload --config /etc/caddy/Caddyfile
در لاگها به چه چیزهایی توجه کنیم
journalctl -u caddy -f # نصب روی میزبان
docker compose logs -f caddy # اجرا در Docker
certificate obtained successfully— گواهی صادر شد؛serving initial configuration— پیکربندی پذیرفته شد؛tls.obtainیاacme— DNS، پورتهای80و443و محدودیتهای Let's Encrypt را بررسی کنید؛dial tcp 127.0.0.1:3000: connect: connection refused— پنل اجرا نشده است.
مطمئن شوید که هر دو سایت پاسخ میدهند:
curl -I https://panel.example.com
curl -I https://sub.example.com
پاسخ باید با HTTP/2 200 شروع شود. بررسی IP واقعی کاربر در
ممیزی پنل راحت است: اگر آنجا آدرسهای Cloudflare باشد، یعنی trusted_proxies فعال نشده است.