بلاگ/راه‌اندازی Caddy به‌عنوان ریورس پروکسی با HTTPS خودکار روی سرور مجازی و اختصاصی
به‌روز شده

راه‌اندازی Caddy به‌عنوان ریورس پروکسی با HTTPS خودکار روی سرور مجازی و اختصاصی

1405/06/110 بازدید
راه‌اندازی Caddy به‌عنوان ریورس پروکسی با HTTPS خودکار روی سرور مجازی و اختصاصی

راه‌اندازی Caddy به‌عنوان ریورس پروکسی با HTTPS خودکار روی سرور مجازی و اختصاصی

Caddy یک وب‌سرور و ریورس پروکسی است که دریافت و تمدید گواهی TLS را به‌صورت خودکار انجام می‌دهد. در بسیاری از پروژه‌ها کافی است دامنه را به سرور متصل کنید، آدرس اپلیکیشن را در یک فایل پیکربندی بنویسید و سرویس Caddy را اجرا کنید. پس از آن، Caddy گواهی HTTPS را دریافت می‌کند و درخواست‌ها را به برنامه می‌فرستد. در این راهنما، راه‌اندازی Caddy روی سرور لینوکس را از نصب تا تنظیم ریورس پروکسی، اتصال به کانتینرها، توزیع بار و رفع خطاهای متداول بررسی می‌کنیم. دستورها برای Ubuntu و Debian نوشته شده‌اند، اما ساختار Caddyfile در بیشتر توزیع‌ها یکسان است.

نمای کلی ارتباط کاربر، Caddy و اپلیکیشن

Caddy چگونه در معماری سرور قرار می‌گیرد؟

فرض کنید یک اپلیکیشن Node.js، Go، Python یا PHP روی پورت 3000 اجرا می‌شود. باز کردن مستقیم این پورت روی اینترنت معمولاً انتخاب مناسبی نیست. بهتر است برنامه فقط روی آدرس داخلی سرور گوش کند و Caddy درخواست‌های عمومی پورت‌های 80 و 443 را تحویل بگیرد. مسیر هر درخواست به این شکل است: 1. کاربر آدرس app.example.com را باز می‌کند. 2. DNS دامنه، کاربر را به IP سرور می‌رساند. 3. Caddy اتصال HTTP یا HTTPS را دریافت می‌کند. 4. درخواست به اپلیکیشن روی 127.0.0.1:3000 فرستاده می‌شود. 5. پاسخ اپلیکیشن از طریق Caddy به کاربر برمی‌گردد. این معماری مدیریت TLS، هدایت HTTP به HTTPS، ثبت لاگ و توزیع بار را از کد اپلیکیشن جدا می‌کند. همچنین لازم نیست هر برنامه مستقیماً با گواهی‌ها و پورت عمومی سروکار داشته باشد.

پیش‌نیازهای راه‌اندازی Caddy روی سرور

پیش از نصب، موارد زیر را آماده کنید: - یک سرور مجازی یا اختصاصی با Ubuntu یا Debian - دسترسی کاربر دارای مجوز sudo - یک دامنه یا زیردامنه - رکورد DNS از نوع A برای IPv4 و در صورت نیاز AAAA برای IPv6 - باز بودن پورت‌های TCP شماره 80 و 443 - اپلیکیشنی که روی یک پورت محلی مانند 3000 اجرا می‌شود رکورد دامنه باید به IP عمومی همان سروری اشاره کند که Caddy روی آن نصب می‌شود. می‌توانید نتیجه DNS را بررسی کنید:

dig +short app.example.com

اگر رکورد AAAA تعریف کرده‌اید، IPv6 نیز باید به همین سرور برسد. وجود یک رکورد IPv6 اشتباه ممکن است باعث شکست اعتبارسنجی دامنه شود، حتی اگر رکورد IPv4 درست باشد. وضعیت پورت‌های در حال استفاده را نیز ببینید:

sudo ss -lntp

اگر Nginx، Apache یا سرویس دیگری پورت‌های 80 و 443 را اشغال کرده باشد، باید آن را متوقف کنید یا معماری پورت‌ها را تغییر دهید.

نصب Caddy روی Ubuntu و Debian

برای نصب نسخه رسمی، ابتدا ابزارهای موردنیاز و کلید مخزن را اضافه کنید:

sudo apt update
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf \
  'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \

  | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg

curl -1sLf \
  'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' \

  | sudo tee /etc/apt/sources.list.d/caddy-stable.list

سپس فهرست بسته‌ها را به‌روز و Caddy را نصب کنید:

sudo apt update
sudo apt install caddy

بسته رسمی یک سرویس systemd می‌سازد. وضعیت آن را با دستور زیر بررسی کنید:

systemctl status caddy

فایل اصلی پیکربندی در مسیر /etc/caddy/Caddyfile قرار دارد. سرویس نیز معمولاً با کاربر محدودشده caddy اجرا می‌شود؛ بنابراین فایل‌هایی که Caddy باید بخواند، باید برای این کاربر قابل دسترسی باشند.

ساخت اولین Caddyfile برای ریورس پروکسی

ساده‌ترین پیکربندی برای دامنه‌ای که باید به اپلیکیشن پورت 3000 متصل شود، فقط چند خط دارد:

app.example.com {
    reverse_proxy 127.0.0.1:3000
}

فایل را ویرایش کنید:

sudo nano /etc/caddy/Caddyfile

پیش از اعمال تغییر، قالب و صحت پیکربندی را بررسی کنید:

sudo caddy fmt --overwrite /etc/caddy/Caddyfile
sudo caddy validate --config /etc/caddy/Caddyfile

اگر اعتبارسنجی بدون خطا انجام شد، تنظیم جدید را بدون قطع کامل سرویس بارگذاری کنید:

sudo systemctl reload caddy

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

curl -I https://app.example.com

بهتر است اپلیکیشن فقط روی 127.0.0.1 گوش کند. برای نمونه، اگر برنامه روی 0.0.0.0:3000 اجرا شود، ممکن است پورت آن مستقیماً از اینترنت قابل دسترسی باشد؛ مگر اینکه فایروال جلوی این اتصال را بگیرد.

HTTPS خودکار در Caddy چگونه کار می‌کند؟

وقتی در Caddyfile یک نام دامنه معتبر می‌نویسید، Caddy به‌طور پیش‌فرض فرایند دریافت گواهی را آغاز می‌کند. این فرایند شامل اثبات کنترل دامنه، ذخیره گواهی و تمدید آن پیش از انقضا است. مرجع صدور گواهی می‌تواند بر اساس تنظیمات و شرایط دسترسی انتخاب شود.

چرخهٔ مدیریت خودکار گواهی TLS در Caddy: اثبات کنترل دامنه، ذخیرهٔ امن گواهی و تمدید آن پیش از انقضا

برای موفقیت این فرایند باید: - دامنه به IP درست اشاره کند؛ - پورت‌های 80 و 443 از اینترنت در دسترس باشند؛ - ساعت سیستم صحیح باشد؛ - Caddy اجازه نوشتن در فضای داده خود را داشته باشد؛ - محدودیت مکرر صدور گواهی ایجاد نشده باشد. اگر از UFW استفاده می‌کنید، دسترسی وب را باز کنید:

sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw status

در سرورهای ابری، علاوه بر فایروال سیستم‌عامل باید Security Group یا فایروال شبکه ارائه‌دهنده را نیز بررسی کنید. برای محیط توسعه‌ای که دامنه عمومی ندارد، می‌توان از TLS داخلی استفاده کرد:

internal.example.test {
    tls internal
    reverse_proxy 127.0.0.1:3000
}

گواهی صادرشده با tls internal روی دستگاه کاربران عمومی معتبر شناخته نمی‌شود، مگر اینکه ریشه داخلی Caddy روی آن دستگاه‌ها نصب شده باشد. بنابراین این روش جایگزین گواهی عمومی برای سایت اینترنتی نیست.

افزودن هدرها، فشرده‌سازی و لاگ

یک Caddyfile کاربردی‌تر می‌تواند فشرده‌سازی پاسخ‌ها، هدرهای امنیتی و لاگ دسترسی را نیز فعال کند:

app.example.com {
    encode zstd gzip
    header {
        X-Content-Type-Options nosniff
        X-Frame-Options DENY
        Referrer-Policy strict-origin-when-cross-origin
        -Server
    }
    log {
        output file /var/log/caddy/app-access.log
        format json
    }
    reverse_proxy 127.0.0.1:3000
}

برای نوشتن لاگ در یک مسیر سفارشی، ابتدا مطمئن شوید کاربر caddy مجوز لازم را دارد:

sudo install -d -o caddy -g caddy /var/log/caddy
sudo systemctl reload caddy

هدرهای امنیتی را متناسب با برنامه انتخاب کنید. برای مثال، X-Frame-Options DENY نمایش سایت داخل iframe را متوقف می‌کند و ممکن است برای پنل‌هایی که عمداً در سایت دیگری جاسازی می‌شوند مناسب نباشد. Caddy اطلاعات لازم برای شناسایی پروتکل و IP مبدأ را هنگام پروکسی‌کردن مدیریت می‌کند. با این حال، اپلیکیشن باید برای اعتماد به پراکسی تنظیم شود؛ به‌خصوص وقتی تولید URL، کوکی امن یا محدودسازی درخواست بر اساس IP به این اطلاعات وابسته است.

اتصال Caddy به اپلیکیشن‌های Docker

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

services:
  app:
    image: example/my-app:latest
    ports:
      - "127.0.0.1:3000:3000"

Caddyfile متناظر چنین است:

app.example.com {
    reverse_proxy 127.0.0.1:3000
}

اتصال پورت به 127.0.0.1 مانع دسترسی مستقیم اینترنت به پورت 3000 می‌شود. اگر Caddy نیز داخل Docker Compose اجرا شود، هر دو سرویس را در یک شبکه قرار دهید و به‌جای localhost از نام سرویس استفاده کنید:

app.example.com {
    reverse_proxy app:3000
}

داخل کانتینر Caddy، آدرس 127.0.0.1 به همان کانتینر اشاره می‌کند، نه کانتینر اپلیکیشن. استفاده اشتباه از localhost یکی از دلایل رایج خطای 502 Bad Gateway در این معماری است. در اجرای کانتینری، مسیرهای داده و پیکربندی Caddy را روی volume پایدار نگه دارید. حذف فضای داده می‌تواند اطلاعات حساب مرجع صدور و گواهی‌های ذخیره‌شده را از بین ببرد و باعث درخواست دوباره گواهی شود.

توزیع بار میان چند نمونه اپلیکیشن

Caddy می‌تواند درخواست‌ها را میان چند upstream توزیع کند. برای سه نمونه از یک اپلیکیشن بنویسید:

app.example.com {
    reverse_proxy 127.0.0.1:3001 127.0.0.1:3002 127.0.0.1:3003 {
        lb_policy least_conn
        health_uri /health
        health_interval 10s
        health_timeout 2s
        fail_duration 30s
        max_fails 3
    }
}

سیاست least_conn درخواست جدید را به نمونه‌ای می‌فرستد که اتصال فعال کمتری دارد. بررسی فعال سلامت نیز مسیر /health را در بازه مشخص آزمایش می‌کند. این endpoint باید سریع باشد و فقط زمانی پاسخ موفق بدهد که نمونه واقعاً توانایی سرویس‌دهی دارد. اگر برنامه وضعیت نشست را در حافظه همان پردازه نگه می‌دارد، پخش درخواست‌ها میان چند نمونه می‌تواند نشست کاربران را مختل کند. راه بهتر، انتقال نشست به فضای مشترکی مانند Redis یا طراحی بدون وضعیت است. چسباندن هر کاربر به یک نمونه، وابستگی عملیاتی بیشتری ایجاد می‌کند و خرابی همان نمونه را دشوارتر می‌سازد.

مقایسه Caddy و Nginx برای ریورس پروکسی

هر دو ابزار برای ارائه محتوای وب و ریورس پروکسی مناسب‌اند، اما تجربه پیکربندی آن‌ها یکسان نیست.

معیارCaddyNginx
HTTPS عمومیدریافت و تمدید خودکار در تنظیم پیش‌فرضمعمولاً با Certbot یا فرایند جداگانه
فایل پیکربندیکوتاه و مناسب تنظیمات متداولجزئی‌تر و دارای گزینه‌های گسترده
بارگذاری تغییراتاعتبارسنجی و reload بدون توقف کاملتست با nginx -t و سپس reload
توزیع بارداخلی و ساده برای سناریوهای رایجداخلی، باسابقه و قابل تنظیم
افزونه‌هابسیاری از ماژول‌ها نیازمند ساخت سفارشی هستنداکوسیستم بزرگ و بسته‌های متنوع
انتخاب مناسبراه‌اندازی سریع HTTPS و پراکسی اپلیکیشنزیرساخت موجود، تنظیمات قدیمی یا نیازهای خاص Nginx

برای یک اپلیکیشن جدید که چند دامنه و HTTPS خودکار می‌خواهد، Caddy معمولاً پیکربندی کمتری نیاز دارد. اگر سازمان از قبل قالب‌ها، ماژول‌ها و ابزارهای عملیاتی مبتنی بر Nginx دارد، مهاجرت فقط برای کوتاه‌تر شدن فایل تنظیمات لزوماً ارزشمند نیست.

مدیریت چند دامنه و مسیر

می‌توانید چند سایت را در یک Caddyfile تعریف کنید:

app.example.com {
    reverse_proxy 127.0.0.1:3000
}
api.example.com {
    reverse_proxy 127.0.0.1:8080
}
static.example.com {
    root * /srv/static
    file_server
}

برای ارسال یک مسیر خاص به سرویس جداگانه نیز از matcher استفاده کنید:

example.com {
    handle /api/* {
        reverse_proxy 127.0.0.1:8080
    }
    handle {
        reverse_proxy 127.0.0.1:3000
    }
}

دستور handle کمک می‌کند فقط یک شاخه برای هر درخواست اجرا شود. درباره حفظ یا حذف پیشوند مسیر دقت کنید: handle_path پیشوند تطبیق‌داده‌شده را حذف می‌کند، اما handle مسیر را بدون این تغییر به upstream می‌فرستد.

عیب‌یابی خطاهای متداول

برای مشاهده زنده لاگ سرویس اجرا کنید:

journalctl -u caddy -f

اگر خطای دریافت گواهی می‌بینید، ابتدا DNS و دسترسی پورت‌ها را بررسی کنید. استفاده از CDN یا پراکسی DNS نیز ممکن است نحوه رسیدن درخواست اعتبارسنجی به سرور را تغییر دهد. خطای 502 Bad Gateway معمولاً یعنی Caddy به upstream دسترسی ندارد. وضعیت برنامه و پاسخ محلی آن را آزمایش کنید:

curl -v http://127.0.0.1:3000
sudo ss -lntp | grep 3000

اگر برنامه داخل Docker است، نام شبکه، نام سرویس و پورت داخلی کانتینر را بررسی کنید. پورت سمت میزبان و پورت داخل کانتینر الزاماً یکسان نیستند. خطای permission denied برای فایل‌های استاتیک یا گواهی‌های دستی به مجوز کاربر سرویس مربوط است. به‌جای اجرای Caddy با کاربر root، مالکیت و سطح دسترسی مسیر موردنیاز را اصلاح کنید. پس از هر تغییر این ترتیب را رعایت کنید:

sudo caddy fmt --overwrite /etc/caddy/Caddyfile
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy

این کار احتمال خارج‌شدن سرویس از دسترس به‌دلیل یک اشتباه نحوی را کاهش می‌دهد.

نکات امنیتی و نگهداری

پورت داخلی اپلیکیشن را عمومی نکنید و فقط پورت‌های موردنیاز را در فایروال باز بگذارید. Caddy و سیستم‌عامل را نیز مرتب به‌روز کنید:

sudo apt update
sudo apt upgrade

از /etc/caddy/Caddyfile نسخه پشتیبان بگیرید. اگر Caddy را در کانتینر اجرا می‌کنید، volume مربوط به /data نیز مهم است، زیرا وضعیت گواهی‌ها و حساب‌های صدور در آن نگهداری می‌شود. داشبوردهای مدیریتی، پایگاه داده و سرویس‌های مانیتورینگ را صرفاً با افزودن یک دامنه عمومی نکنید. برای چنین سرویس‌هایی احراز هویت، محدودیت IP یا دسترسی از طریق VPN در نظر بگیرید. راهکارهای بیشتر سطح سیستم‌عامل در راهنمای افزایش امنیت سرور لینوکس بررسی شده است. ظرفیت سرور نیز باید با تعداد اتصال‌ها، مصرف اپلیکیشن و سربار TLS تناسب داشته باشد. اگر برای استقرار برنامه به زیرساخت تازه نیاز دارید، می‌توانید مشخصات سرور مجازی ابری آریاسرویس را بررسی کنید و سپس Caddy و اپلیکیشن را روی همان سرور قرار دهید.

جمع‌بندی

راه‌اندازی Caddy روی سرور برای یک ریورس پروکسی معمولی به نصب بسته، اتصال DNS، باز کردن پورت‌های وب و تعریف reverse_proxy در Caddyfile محدود می‌شود. Caddy صدور و تمدید گواهی HTTPS را مدیریت می‌کند و می‌تواند فشرده‌سازی، هدرهای امنیتی، لاگ و توزیع بار را نیز بر عهده بگیرد. پیش از انتقال ترافیک واقعی، پاسخ upstream را از داخل سرور آزمایش کنید، فایل تنظیمات را اعتبارسنجی کنید و لاگ‌های systemd را زیر نظر بگیرید. با محدودکردن پورت اپلیکیشن به شبکه داخلی و نگهداری درست داده‌های Caddy، یک ورودی HTTPS ساده و قابل نگهداری برای سرویس‌های میزبان یا کانتینری خواهید داشت.