آموزش سرور

Nginx به‌عنوان Reverse Proxy برای اپلیکیشن‌های داکر؛ چند سرویس، چند دامنه، یک سرور

تهیه: کلودیفای (نگارش با کمک هوش مصنوعی) · انتشار: · زمان مطالعه: ۷ دقیقه

NginxReverseProxyآموزش سرورNginx به‌عنوان Reverse Proxyبرای اپلیکیشن‌های داکر؛ چندسرویس، چند دامنه، یک سرورمجلهٔ کلودیفایclodify.net/blog
پاسخ کوتاه

برای انتشار امن اپلیکیشن‌های داکر، پورت هر کانتینر را فقط روی 127.0.0.1 منتشر کنید و Nginx نصب‌شده روی سرور را روی پورت‌های ۸۰ و ۴۴۳ قرار دهید تا درخواست هر دامنه را با proxy_pass به کانتینر مربوط برساند. هدرهای Host و X-Forwarded-* را تنظیم کنید، برای WebSocket هدرهای Upgrade را اضافه کنید و با Certbot برای همهٔ دامنه‌ها گواهی بگیرید. این ساختار از دور زدن فایروال توسط داکر هم جلوگیری می‌کند.

چرا Reverse Proxy جلوی کانتینرها؟

وقتی چند اپلیکیشن داکری روی یک سرور دارید، مثلاً یک API، یک پنل مدیریت و ابزاری مثل n8n یا Uptime Kuma، هرکدام روی پورت جداگانه‌ای اجرا می‌شود. انتشار مستقیم این پورت‌ها روی اینترنت چند مشکل دارد: کاربران باید آدرس‌هایی مثل example.com:5678 را تایپ کنند، هر اپلیکیشن باید جداگانه HTTPS داشته باشد و مهم‌تر از همه، پورت‌هایی که داکر منتشر می‌کند ممکن است قوانین فایروال UFW را دور بزنند.

راه‌حل استاندارد، قرار دادن یک Reverse Proxy جلوی همه است. Nginx روی پورت‌های ۸۰ و ۴۴۳ همهٔ درخواست‌ها را می‌گیرد، با توجه به نام دامنه یا مسیر تشخیص می‌دهد درخواست مال کدام اپلیکیشن است و آن را به کانتینر مربوط می‌رساند. HTTPS، فشرده‌سازی، محدودیت نرخ و لاگ متمرکز هم در همین یک نقطه مدیریت می‌شود.

یک ورودی، چند کانتینر
HTTPSproxy_passبر اساس دامنهکاربرapi.example.comNginx۸۰ و ۴۴۳ + HTTPSکانتینر api127.0.0.1:3000کانتینر dashboard127.0.0.1:8081HTTPSproxy_passبر اساس دامنهکاربرapi.example.comNginx۸۰ و ۴۴۳ + HTTPSکانتینر api127.0.0.1:3000کانتینر dashboard127.0.0.1:8081
شکل ۱ Nginx بر اساس نام دامنه، هر درخواست را به کانتینر درست می‌رساند؛ کانتینرها از اینترنت مستقیم در دسترس نیستند.

معماری پیشنهادی

ساده‌ترین و قابل‌فهم‌ترین ساختار برای یک سرور این است:

  • Nginx مستقیم روی سرور نصب است (نه در کانتینر) و روی ۸۰ و ۴۴۳ گوش می‌دهد.
  • هر کانتینر پورتش را فقط روی 127.0.0.1 منتشر می‌کند؛ مثلاً 127.0.0.1:3000:3000.
  • فایروال فقط پورت‌های ۲۲، ۸۰ و ۴۴۳ را باز دارد.

به این ترتیب کانتینرها از اینترنت مستقیم در دسترس نیستند و تنها راه رسیدن به آن‌ها Nginx است. پیش‌نیازها: داکر نصب‌شده طبق آموزش نصب داکر روی اوبونتو و Nginx:

sudo apt install -y nginx

گام ۱: انتشار کانتینر روی localhost

در فایل Compose یا دستور docker run، پورت را با پیشوند 127.0.0.1 منتشر کنید:

services:
  api:
    image: ghcr.io/example/api:2.1
    restart: unless-stopped
    ports:
      - "127.0.0.1:3000:3000"
  dashboard:
    image: ghcr.io/example/dashboard:1.0
    restart: unless-stopped
    ports:
      - "127.0.0.1:8081:80"

پس از اجرا، از خود سرور تست کنید که کانتینرها پاسخ می‌دهند:

curl -I http://127.0.0.1:3000
curl -I http://127.0.0.1:8081

اگر این‌ها کار نکنند، Nginx هم کاری از پیش نمی‌برد؛ ابتدا کانتینر را درست کنید. اگر با Compose آشنا نیستید، آموزش Docker Compose را ببینید.

گام ۲: پیکربندی Nginx برای هر دامنه

برای هر اپلیکیشن یک فایل در /etc/nginx/sites-available/ بسازید:

# /etc/nginx/sites-available/api.example.com
server {
    listen 80;
    server_name api.example.com;

    location / {
        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 $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_read_timeout 60s;
        client_max_body_size 20m;
    }
}

همین ساختار را برای dashboard.example.com با proxy_pass http://127.0.0.1:8081 تکرار کنید. سپس فعال‌سازی و تست:

sudo ln -s /etc/nginx/sites-available/api.example.com /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

نقش هدرها:

  • Host: نام دامنهٔ اصلی را به اپلیکیشن می‌رساند؛ بسیاری از فریم‌ورک‌ها برای ساخت لینک و بررسی امنیتی به آن نیاز دارند.
  • X-Real-IP و X-Forwarded-For: آی‌پی واقعی کاربر؛ بدون آن‌ها همهٔ درخواست‌ها از 127.0.0.1 دیده می‌شوند.
  • X-Forwarded-Proto: به اپلیکیشن می‌گوید کاربر با HTTPS آمده است؛ بدون آن، برخی اپلیکیشن‌ها لینک‌های HTTP می‌سازند یا در حلقهٔ ریدایرکت می‌افتند.
  • client_max_body_size: حداکثر حجم آپلود؛ مقدار پیش‌فرض Nginx کوچک است و آپلودهای بزرگ‌تر خطای 413 می‌گیرند.
لایه‌های دسترسی
فایروالفقط ۲۲، ۸۰ و ۴۴۳ باز
NginxHTTPS، هدرها، محدودیت حجم و نرخ
پورت‌های localhost127.0.0.1:3000، 127.0.0.1:8081
کانتینرهای داکراپلیکیشن‌ها و دیتابیس‌ها
شکل ۲ هر لایه فقط با لایهٔ کنارش در تماس است؛ اینترنت هرگز مستقیم به کانتینر نمی‌رسد.

گام ۳: HTTPS برای همهٔ دامنه‌ها

پس از اینکه رکوردهای DNS هر زیردامنه به آی‌پی سرور اشاره کرد (راهنما در تنظیم رکوردهای DNS)، با Certbot برای همه گواهی بگیرید:

sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d api.example.com -d dashboard.example.com

Certbot بلوک‌های listen 443 ssl را اضافه و ریدایرکت HTTP به HTTPS را تنظیم می‌کند و تمدید خودکار را فعال می‌کند. جزئیات بیشتر در آموزش Nginx و Let's Encrypt آمده است.

WebSocket و اتصال‌های طولانی

اپلیکیشن‌هایی که از WebSocket استفاده می‌کنند (چت، داشبوردهای زنده، ابزارهایی مثل n8n و Uptime Kuma) به دو هدر اضافه نیاز دارند تا Nginx اتصال را ارتقا دهد:

location / {
    proxy_pass http://127.0.0.1:3001;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_read_timeout 3600s;
}

بدون این هدرها، صفحه بارگذاری می‌شود اما بخش‌های زنده کار نمی‌کنند یا مدام قطع و وصل می‌شوند. proxy_read_timeout بالاتر هم جلوی قطع شدن اتصال‌های بیکار طولانی را می‌گیرد.

مسیر به‌جای زیردامنه

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

location /api/ {
    proxy_pass http://127.0.0.1:3000/;
}

به اسلش انتهای proxy_pass دقت کنید: با اسلش، پیشوند /api/ از مسیر حذف می‌شود و اپلیکیشن درخواست /users را می‌بیند؛ بدون اسلش، همان /api/users را دریافت می‌کند. همچنین بسیاری از اپلیکیشن‌ها برای کار روی زیرمسیر به تنظیم «base path» یا «root URL» نیاز دارند؛ در غیر این صورت لینک‌ها و فایل‌های CSS و JS را از ریشهٔ دامنه درخواست می‌کنند و صفحه خراب می‌شود. به همین دلیل، استفاده از زیردامنه معمولاً کم‌دردسرتر است.

حالت جایگزین: Nginx داخل Compose

اگر ترجیح می‌دهید همه‌چیز در کانتینر باشد، Nginx را هم به‌عنوان یک سرویس در همان فایل Compose تعریف کنید. در این حالت اپلیکیشن‌ها اصلاً پورتی منتشر نمی‌کنند و Nginx آن‌ها را با نام سرویس در شبکهٔ داخلی Compose صدا می‌زند؛ مثلاً proxy_pass http://api:3000. فقط Nginx پورت‌های ۸۰ و ۴۴۳ را منتشر می‌کند. این روش قابل‌حمل‌تر است، اما مدیریت گواهی‌ها کمی پیچیده‌تر می‌شود و باید Certbot را هم در کانتینر اجرا کنید یا از ابزارهایی استفاده کنید که این کار را خودکار انجام می‌دهند.

root@server: ~
$ docker ps... PORTS                       NAMES... 127.0.0.1:3000->3000/tcp    api... 127.0.0.1:8081->80/tcp      dashboard$ curl -sI https://api.example.com | head -n 1HTTP/2 200$ 
شکل ۳ کانتینرها فقط روی localhost منتشر شده‌اند و از بیرون تنها از طریق Nginx و HTTPS در دسترس‌اند.

امنیت و محدودیت دسترسی در لایهٔ Nginx

یکی از مزیت‌های بزرگ Reverse Proxy این است که می‌توانید محافظت‌های امنیتی را یک بار و برای همهٔ اپلیکیشن‌ها پیاده کنید، بدون اینکه به کد هیچ‌کدام دست بزنید. برای پنل‌های مدیریتی که فقط خودتان استفاده می‌کنید، می‌توانید دسترسی را به آی‌پی‌های مشخص محدود کنید یا یک لایه رمز عبور پایه با auth_basic روی آن بگذارید. این کار حتی اگر خود اپلیکیشن ضعف امنیتی داشته باشد، سطح حمله را بسیار کوچک می‌کند.

برای مسیرهای حساس مثل صفحهٔ ورود، محدودیت نرخ با limit_req جلوی حدس زدن رمز به‌صورت انبوه را می‌گیرد. هدرهای امنیتی مثل X-Content-Type-Options و Strict-Transport-Security را هم می‌توانید در همین لایه اضافه کنید. همچنین چون همهٔ درخواست‌ها از Nginx عبور می‌کنند، لاگ دسترسی آن تصویر کاملی از ترافیک همهٔ اپلیکیشن‌ها می‌دهد و ابزاری مثل Fail2ban می‌تواند با خواندن همین لاگ، آی‌پی‌های مزاحم را مسدود کند.

نکتهٔ دیگر به‌روزرسانی است: وقتی نسخهٔ جدید یک کانتینر را اجرا می‌کنید، تا زمانی که پورت داخلی تغییر نکند به پیکربندی Nginx نیازی نیست. پس از به‌روزرسانی، با یک curl ساده از سلامت سرویس مطمئن شوید.

عیب‌یابی

  • 502 Bad Gateway: کانتینر اجرا نیست یا روی پورت دیگری است؛ با docker ps و curl به 127.0.0.1 بررسی کنید. روند کامل در رفع خطای 502.
  • صفحه باز می‌شود ولی لینک‌ها به http یا پورت داخلی اشاره می‌کنند: هدرهای Host و X-Forwarded-Proto را بررسی کنید و تنظیم آدرس عمومی اپلیکیشن را اصلاح کنید.
  • کانتینر هنوز از بیرون با پورتش در دسترس است: پورت را بدون 127.0.0.1 منتشر کرده‌اید؛ داکر قوانین UFW را دور می‌زند. توضیح در آموزش فایروال UFW.

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

سؤالات متداول

چرا پورت کانتینر را روی 127.0.0.1 منتشر کنم؟

پورت‌هایی که داکر روی همهٔ آدرس‌ها منتشر می‌کند ممکن است قوانین UFW را دور بزنند و از اینترنت در دسترس باشند. انتشار روی 127.0.0.1 تضمین می‌کند فقط Nginx روی همان سرور به کانتینر دسترسی دارد.

Nginx را روی سرور نصب کنم یا در کانتینر؟

برای یک سرور، نصب روی خود سرور ساده‌تر است و Certbot مستقیم با آن کار می‌کند. Nginx در کانتینر قابل‌حمل‌تر است و برای کسانی که همه‌چیز را در Compose می‌خواهند مناسب است؛ هر دو روش درست‌اند.

چرا WebSocket پشت Nginx کار نمی‌کند؟

هدرهای Upgrade و Connection را تنظیم نکرده‌اید یا proxy_http_version روی 1.1 نیست. این سه خط را اضافه کنید و برای اتصال‌های طولانی proxy_read_timeout را افزایش دهید.

خطای 413 Request Entity Too Large از کجاست؟

حجم درخواست، معمولاً فایل آپلودی، از حد client_max_body_size در Nginx بیشتر است. مقدار آن را در بلوک server یا location افزایش دهید و Nginx را reload کنید.

آماده‌اید؟ سرورتان را بسازید.

از ۱٬۲۰۰ تومان در ساعت · پرداخت ریالی

سرورتان را همین حالا بسازید.

ساعتی بپردازید. هر وقت خواستید حذف کنید.