SSD Nodes Learn Hosting plans →
راهنماها Matt Connorتوسط Matt Connor

راه اندازی wg-easy و WireGuard در Docker

با استفاده از Docker Compose و wg-easy یک رابط کاربری برای WireGuard بسازید. تنظیمات دقیق NET_ADMIN، پورت‌های لازم و رفع خطای عدم بارگذاری ماژول هسته را بیاموزید.

آنچه در حال ساخت آن هستید

wg-easy در واقع WireGuard به همراه یک رابط کاربری وب است که در قالب یک کانتینر Docker اجرا می‌شود. این ابزار رابط WireGuard را برای شما مدیریت کرده و یک رابط کاربری مرورگر برای ایجاد کلاینت‌ها اضافه می‌کند. برای هر کلاینتی که ایجاد می‌کنید، یک فایل پیکربندی و یک کد QR تولید می‌شود؛ بنابراین گوشی موبایل تنها با اسکن کردن کد از روی صفحه نمایش به VPN متصل می‌شود.

تونل ایجاد شده، یک تونل معمولی WireGuard است. ماژول هسته (kernel module) بسته‌ها را جابه‌جا می‌کند، بنابراین نرخ انتقال داده (throughput) مشابه یک تنظیم دستی است. آنچه به دست می‌آورید، مدیریت چرخه حیات کلاینت است: افزودن، غیرفعال کردن و حذف کلاینت‌ها بدون نیاز به ویرایش فایل پیکربندی از طریق SSH. آنچه از دست می‌دهید، کنترل مستقیم بر آن فایل پیکربندی است که موضوع تنظیم دستی WireGuard روی یک VPS است.

شما به یک KVM VPS با یک آدرس IPv4 عمومی، Docker Engine به همراه افزونه Compose و دسترسی root نیاز دارید. مجازی‌سازی کانتینری که هسته میزبان را به اشتراک می‌گذارد، مانند OpenVZ یا LXC، معمولاً نمی‌تواند ماژول WireGuard را بارگذاری کند و کانتینر در بالا آوردن رابط شبکه با خطا مواجه خواهد شد.

نسخه 15 تنظیمات را از محیط (environment) خارج کرد

بیشتر راهنماهایی که پیدا می‌کنید برای wg-easy 14 نوشته شده‌اند؛ جایی که شما WG_HOST را روی آدرس سرور و PASSWORD_HASH را روی هش bcrypt رمز عبور مدیریت تنظیم می‌کردید، که هر دو به عنوان متغیرهای محیطی (environment variables) بودند. نسخه 15 یک بازنویسی کامل است. یادداشت‌های رسمی مهاجرت به‌صراحت بیان می‌کنند که v15 از همان متغیرهای محیطی v14 استفاده نمی‌کند و اکثر آن‌ها به پنل مدیریت در رابط کاربری وب منتقل شده‌اند.

بنابراین WG_HOST و PASSWORD_HASH دیگر هیچ کاری انجام نمی‌دهند. اگر یک فایل compose قدیمی را کپی کنید، کانتینر بالا می‌آید، آن خطوط را نادیده می‌گیرد و سپس از شما می‌خواهد که یک حساب کاربری مدیریت در مرورگر ایجاد کنید. این یک باگ نیست. این جریان راه‌اندازی جدید است.

از اکتبر 2026، تگ اصلی برای پین کردن نسخه، 15 است. به‌جای استفاده از latest، نسخه اصلی (major version) را پین کنید، زیرا یک ارتقای اصلی، فرمت پیکربندی روی دیسک را تغییر می‌دهد و به‌راحتی قابل بازگشت (rollback) نخواهد بود. انتقال به نسخه 15، فایل JSON نسخه v14 را با یک پایگاه داده SQLite جایگزین کرد.

فایل compose

یک دایرکتوری برای این stack ایجاد کنید و فایل compose رسمی را در آن بنویسید. این فایل اصلی (upstream) است که خطوط اختیاری کامنت‌شده از آن حذف شده‌اند.

sudo mkdir -p /etc/docker/containers/wg-easy
sudo curl -o /etc/docker/containers/wg-easy/docker-compose.yml \
  https://raw.githubusercontent.com/wg-easy/wg-easy/master/docker-compose.yml

محتویات فایل به این صورت است:

volumes:
  etc_wireguard:

services:
  wg-easy:
    image: ghcr.io/wg-easy/wg-easy:15
    container_name: wg-easy
    networks:
      wg:
        ipv4_address: 10.42.42.42
        ipv6_address: fdcc:ad94:bacf:61a3::2a
    volumes:
      - etc_wireguard:/etc/wireguard
      - /lib/modules:/lib/modules:ro
    ports:
      - "51820:51820/udp"
      - "51821:51821/tcp"
    restart: unless-stopped
    cap_add:
      - NET_ADMIN
      - SYS_MODULE
    sysctls:
      - net.ipv4.ip_forward=1
      - net.ipv4.conf.all.src_valid_mark=1
      - net.ipv6.conf.all.disable_ipv6=0
      - net.ipv6.conf.all.forwarding=1
      - net.ipv6.conf.default.forwarding=1

networks:
  wg:
    driver: bridge
    enable_ipv6: true
    ipam:
      driver: default
      config:
        - subnet: 10.42.42.0/24
        - subnet: fdcc:ad94:bacf:61a3::/64

etc_wireguard یک volume نام‌گذاری‌شده است که کلید سرور و تمام کلاینت‌هایی که ایجاد می‌کنید را در خود نگه می‌دارد و در دیتابیس SQLite با نام wg-easy.db ذخیره می‌شود. از این volume نسخه پشتیبان تهیه کنید، در غیر این صورت با بازسازی (rebuild) سرویس، تمام peerهای خود را از دست خواهید داد. اگر ترجیح می‌دهید این فایل‌ها را در فایل‌سیستم میزبان ببینید، آن را با یک bind mount جایگزین کنید. پیش از انجام این کار، تفاوت بین bind mountها و volumeهای نام‌گذاری‌شده را مطالعه کنید، زیرا نحوه مدیریت مجوزها (permissions) در آن‌ها متفاوت است.

چرا به NET_ADMIN، SYS_MODULE و sysctlها نیاز است

به‌صورت پیش‌فرض، یک کانتینر اجازه ندارد پشته شبکه را تغییر دهد و هر یک از این موارد، یکی از محدودیت‌های موجود را برطرف می‌کند.

NET_ADMIN به کانتینر اجازه می‌دهد رابط wg0 را ایجاد کند، به آن آدرس اختصاص دهد و مسیرها (routes) را بنویسد. بدون این قابلیت، کانتینر شروع به کار می‌کند اما هنگام بالا آوردن رابط متوقف می‌شود، زیرا ip link add wg0 type wireguard مقدار Operation not permitted را برمی‌گرداند.

SYS_MODULE به همراه mount کردن /lib/modules به‌صورت فقط‌خواندنی، به کانتینر اجازه می‌دهد در صورتی که ماژول هسته WireGuard هنوز توسط میزبان بارگذاری نشده باشد، آن را بارگذاری کند. این ماژول در هسته میزبان قرار دارد، نه داخل image؛ به همین دلیل مسیر میزبان باید قابل مشاهده باشد. در هسته‌های مدرن، این ماژول معمولاً به‌صورت داخلی (built-in) وجود دارد و می‌توانید آن را با دستور sudo modprobe wireguard && echo ok روی میزبان تأیید کنید.

net.ipv4.ip_forward=1 باعث می‌شود هسته بسته‌هایی را که مقصدشان خودِ آن دستگاه نیست، فوروارد کند. بدون این تنظیم، کلاینت متصل می‌شود، handshake با موفقیت انجام می‌گیرد، اما تمام بسته‌های ارسالی به اینترنت حذف می‌شوند؛ در نتیجه ping 1.1.1.1 دچار timeout می‌شود، در حالی که VPN متصل به نظر می‌رسد.

net.ipv4.conf.all.src_valid_mark=1 موردی است که باعث تعجب کاربران می‌شود. WireGuard بسته‌های خروجی خود را علامت‌گذاری می‌کند تا دوباره به داخل تونل هدایت نشوند. فیلترینگ سخت‌گیرانه مسیر معکوس (Strict reverse path filtering)، بسته‌ای را که آدرس مبدأ آن با مسیر مورد انتظار مطابقت ندارد، حذف می‌کند. این sysctl به هسته دستور می‌دهد بسته‌های علامت‌گذاری‌شده را بپذیرد؛ این همان چیزی است که مانع از قطع شدن تونل کامل (full tunnel) توسط خودش می‌شود.

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

cd /etc/docker/containers/wg-easy
sudo docker compose up -d
sudo docker compose logs -f

از docker compose up و docker compose down استفاده کنید، نه start و stop. دستور start فقط کانتینرهایی را که از قبل وجود دارند مجدداً راه‌اندازی می‌کند، بنابراین هر تغییری که در فایل compose ایجاد کرده باشید، از جمله تنظیمات شبکه و sysctl، اعمال نخواهد شد؛ در حالی که up هر آنچه را که تغییر کرده باشد، بازسازی می‌کند. اگر می‌خواهید stack پس از reboot دوباره بالا بیاید، restart: unless-stopped قبلاً این مورد را پوشش داده است و رفتار سرویس‌های compose در هنگام بوت توضیح می‌دهد که این سیاست چه کاری انجام می‌دهد و چه وعده‌ای نمی‌دهد.

رابط کاربری وب روی پورت TCP 51821 گوش می‌دهد. در اولین بازدید، صفحه‌ای برای راه‌اندازی نمایش داده می‌شود که در آن حساب کاربری مدیر را ایجاد می‌کنید و آدرس میزبان (host) را که کلاینت‌ها برای دسترسی به سرور استفاده می‌کنند، تأیید می‌نمایید. آن آدرس میزبان در خط Endpoint تمام پیکربندی‌های کلاینت قرار می‌گیرد، بنابراین باید IP عمومی یا نام DNS سرور مجازی (VPS) باشد. اگر آدرس اشتباه باشد، کد QR که به گوشی می‌دهید به مقصدی غیرقابل‌دسترس اشاره می‌کند و handshake هرگز کامل نمی‌شود.

یک نکته دیگر درباره آن پورت: برنامه wg-easy 15 اتصالات HTTP ساده را نمی‌پذیرد مگر اینکه INSECURE=true را تنظیم کنید. دسترسی به آن از طریق HTTPS با یک گواهی غیرقابل‌اعتماد، یا پایان دادن به TLS در یک reverse proxy که جلوی آن قرار دارد، هر دو مناسب هستند. دسترسی به آن از طریق http:// با تنظیمات پیش‌فرض، امکان‌پذیر نیست.

پورت رابط کاربری را در اینترنت منتشر نکنید

فایل compose پورت 51821 را روی تمام اینترفیس‌ها منتشر می‌کند. این پورت مربوط به صفحه ورود به سیستمی است که می‌تواند ترافیک شما را مسیریابی کند و نباید برای عموم باز باشد. انتشار یک پورت در Docker قوانینی را در زنجیره DOCKER می‌نویسد که پیش از ufw ارزیابی می‌شوند؛ بنابراین یک قانون deny در ufw نمی‌تواند آن را ببندد. درک این تله اهمیت زیادی دارد و مقاله چرا پورت‌های منتشر شده توسط Docker قوانین ufw را نادیده می‌گیرند به طور کامل به آن پرداخته است.

راه حل ساده این است که رابط کاربری را به loopback محدود کرده و از طریق یک تونل SSH به آن دسترسی پیدا کنید:

    ports:
      - "51820:51820/udp"
      - "127.0.0.1:51821:51821/tcp"
    environment:
      - INSECURE=true

سپس از لپ‌تاپ خود دستور زیر را اجرا کنید:

ssh -L 51821:127.0.0.1:51821 youruser@your.server.address

آدرس http://127.0.0.1:51821 را در مرورگر لپ‌تاپ خود باز کنید. ترافیک توسط SSH رمزنگاری می‌شود، پورت به هیچ‌کس دیگری پاسخ نمی‌دهد و INSECURE=true در اینجا امن است، زیرا انتقال HTTP ساده هرگز از اینترفیس loopback خارج نمی‌شود.

باز کردن پورت UDP 51820 و بررسی هر دو فایروال

پروتکل WireGuard برای کارکرد صحیح نیاز دارد که پورت UDP 51820 از طریق اینترنت در دسترس باشد. Docker این پورت را منتشر می‌کند، اما بسیاری از ارائه‌دهندگان سرویس، یک فایروال شبکه مجزا در مقابل VPS قرار می‌دهند که Docker از وجود آن بی‌اطلاع است. پورت را در هر دو لایه باز کنید. اگر مدیریت فایروال میزبان را با ufw انجام می‌دهید، قوانین پایه ufw برای یک VPS مسیر کوتاه‌تری نسبت به نوشتن دستی nftables است.

بررسی کنید که آیا کانتینر واقعاً در حال گوش دادن روی این پورت است یا خیر:

sudo ss -ulnp | grep 51820

شما باید یک سوکت UDP در وضعیت listening مشاهده کنید. اگر در آن خط چیزی نمایش داده نشد، به این معنی است که کانتینر هرگز رابط شبکه را بالا نیاورده است و sudo docker compose logs wg-easy دلیل آن را مشخص خواهد کرد.

ایجاد یک کلاینت و اسکن آن با گوشی

در رابط کاربری، یک کلاینت ایجاد کنید و نامی به آن بدهید که بعداً آن را تشخیص دهید، مانند نام دستگاهی که به آن تعلق دارد. wg-easy آدرس تونل آزاد بعدی را اختصاص داده و جفت‌کلید را برای شما تولید می‌کند. هر ردیف کلاینت یک کد QR و یک فایل قابل دانلود .conf ارائه می‌دهد.

اپلیکیشن رسمی WireGuard را روی گوشی نصب کنید، گزینه افزودن تونل از طریق کد QR را انتخاب کرده و دوربین را به سمت کد روی صفحه بگیرید. تونل با نامی که وارد کردید ظاهر می‌شود. آن را روشن کنید؛ ردیف کلاینت در رابط کاربری شروع به نمایش شمارنده‌های انتقال داده و زمان آخرین handshake می‌کند. هنگامی که گوشی به تونل متصل است، می‌تواند به سرویس‌هایی دسترسی پیدا کند که هرگز در اینترنت منتشر نکرده‌اید؛ این همان روشی است که گوشی می‌تواند از هر کجا به یک سرور عکس self-hosted آپلود انجام دهد، بدون اینکه آن سرور حتی یک پورت باز به سمت دنیای بیرون داشته باشد. همین ترفند برای رسانه‌ها نیز کاربرد دارد و یک کتابخانه Jellyfin که به شکل یک ویدیو کلوپ دهه 90 بازسازی شده، گزینه‌ای جذاب برای مرور از داخل اتاق هتل است، در حالی که همان‌قدر خصوصی باقی می‌ماند که در شبکه محلی (LAN) شما بود. هشدارها در همین تونل به صورت معکوس کار می‌کنند، زیرا یک سرور ntfy self-hosted می‌تواند در لحظه شکست یک عملیات پشتیبان‌گیری، پیامی را به گوشی ارسال کند، بدون اینکه هرگز به درخواستی از اینترنت عمومی پاسخ دهد.

کلاینتی که پس از فعال‌سازی، handshake نشان نمی‌دهد، اصلاً به سرور نمی‌رسد. این موضوع به پورت UDP 51820 اشاره دارد که یا در فایروال ارائه‌دهنده مسدود شده و یا آدرس endpoint درج‌شده در فایل پیکربندی اشتباه است. کلاینتی که handshake دارد اما اینترنت آن کار نمی‌کند، به مشکلات forwarding یا DNS اشاره دارد.

در دسکتاپ، فایل .conf را دانلود کرده و به جای تایپ مجدد، آن را در کلاینت WireGuard وارد (import) کنید. کلید خصوصی موجود در آن فایل یک‌بار تولید و یک‌بار نمایش داده می‌شود. با این فایل همان‌طور رفتار کنید که با یک کلید خصوصی SSH رفتار می‌کنید.

چه زمانی از رابط کاربری فراتر می‌روید

ابزار wg-easy زمانی بهترین گزینه است که همتایان شما انسان‌ها و گوشی‌های موبایل باشند. رابط کاربری آن از ویرایش دستی فایل‌های پیکربندی سریع‌تر است و ابطال دسترسی یک گوشی گم‌شده تنها با یک کلیک انجام می‌شود.

زمانی که به قابلیتی نیاز داشته باشید که در رابط کاربری تعریف نشده است، به محدودیت‌های آن برخورد خواهید کرد. مسیریابی سایت به سایت (Site to site)، که در آن AllowedIPs یک همتا کل یک زیرشبکه (subnet) راه دور را پوشش می‌دهد و نه فقط یک آدرس واحد، معمولاً اولین مانع است. تونل‌های Split با قوانین مسیریابی به ازای هر همتا، یا پیکربندی‌هایی که توسط ابزار provisioning شما تولید می‌شوند، گام‌های بعدی هستند. در آن مرحله، راه‌اندازی دستی دشوارتر نیست، فقط متفاوت است و راهنمای ساده WireGuard همان تونل را با استفاده از wg0.conf ایجاد می‌کند. اگر ترجیح می‌دهید کلاً اجرای control plane را متوقف کنید، مقایسه WireGuard با Tailscale گزینه‌های مدیریت‌شده را بررسی می‌کند. اینکه آیا این یک معامله منصفانه است یا خیر، به این بستگی دارد که سرور هماهنگ‌کننده واقعاً به چه چیزی دسترسی دارد، و مطالعه مدل اعتماد Tailscale پیش از سپردن شبکه به آن توصیه می‌شود. هزینه معمولاً پرسش بعدی است و آنچه طرح رایگان Tailscale واقعاً پوشش می‌دهد برای یک خانواده یا تیم کوچک کافی است تا هزینه‌ای پرداخت نکنند. پس از آن نقطه، صورت‌حساب بر اساس تعداد کاربران محاسبه می‌شود نه دستگاه‌ها، که ساختار متفاوتی نسبت به یک VPS دارد که از قبل برای آن هزینه می‌کنید؛ بنابراین هزینه‌های Tailscale پس از عبور از طرح رایگان عددی است که باید پیش از مهاجرت یک تیم بررسی کنید. تونل کاملی که به‌تازگی ساختید، معادل مستقیمی در آنجا دارد، زیرا معرفی VPS به عنوان یک exit node در Tailscale همان مسیر خروجی را از طریق سرور برای شما فراهم می‌کند، با این تفاوت که به‌جای نوشتن در پیکربندی هر کلاینت، در کنسول مدیریت تأیید می‌شود. مانع زیرشبکه نیز معادل مشابهی دارد، زیرا معرفی یک شبکه خصوصی کامل از طریق VPS آن شبکه را در اختیار تمام دستگاه‌های موجود در tailnet قرار می‌دهد، بدون اینکه نیاز باشد AllowedIPs را به ازای هر همتا ویرایش کنید؛ همان کاری که شما را از رابط کاربری دور کرد. اگر آن داشبورد و مسیریابی خودکار mesh را می‌خواهید اما نمی‌خواهید از سرور هماهنگ‌کننده شخص دیگری استفاده کنید، اجرای سرور NetBird روی VPS شخصی، control plane را روی سخت‌افزاری که مالک آن هستید نگه می‌دارد، البته به قیمت انجام تنظیمات DNS و TLS که wg-easy هرگز از شما نخواسته بود.

اگر بخش ناآشنا برای شما سینتکس compose در بالا بوده است و نه خود WireGuard، مبانی Docker Compose روی VPS فرمت فایل و دستورات روزمره آن را توضیح می‌دهد.

FAQ

چرا wg-easy متغیرهای WG_HOST و PASSWORD_HASH مرا نادیده می‌گیرد؟

این متغیرها متعلق به نسخه 14 از wg-easy هستند. نسخه 15 یک بازنویسی کامل است و توسعه‌دهنده تقریباً تمام تنظیمات را به پنل مدیریت در رابط کاربری وب منتقل کرده است. کانتینر دیگر این متغیرها را نمی‌خواند، بنابراین به‌طور عادی بالا می‌آید و در اولین بازدید از شما می‌خواهد یک حساب کاربری مدیریت ایجاد کنید. آدرس میزبان (host) برای کلاینت‌ها را در همان صفحه تنظیمات اولیه وارد کنید.

اگر هسته سیستم‌عامل من از قبل WireGuard دارد، آیا همچنان به SYS_MODULE نیاز دارم؟

خیر. SYS_MODULE و mount مربوط به /lib/modules برای این هستند که کانتینر بتواند ماژول را در صورتی که میزبان آن را ندارد، بارگذاری کند. روی میزبانی که دستور sudo modprobe wireguard در آن با موفقیت اجرا می‌شود، این قابلیت بلااستفاده است. حذف آن یک گام منطقی برای افزایش امنیت است، اما NET_ADMIN در هر صورت همچنان الزامی است.

کلاینت متصل می‌شود اما اینترنت ندارد. مشکل چیست؟

عدم تبادل ترافیک با وجود برقراری handshake، تقریباً همیشه به معنای مشکل در forwarding است. اطمینان حاصل کنید که net.ipv4.ip_forward=1 و net.ipv4.conf.all.src_valid_mark=1 همچنان در فایل compose وجود دارند، زیرا در کپی‌های ویرایش‌شده دستی، این موارد اغلب حذف می‌شوند. اگر forwarding فعال است، سرور DNS دریافتی کلاینت را بررسی کنید. تونلی که تمام ترافیک را از طریق VPN می‌فرستد اما به سرور DNSای اشاره می‌کند که دیگر در دسترس نیست، در مرورگر دقیقاً مانند یک اتصال قطع‌شده عمل می‌کند.

چگونه از کلاینت‌های خود نسخه پشتیبان تهیه کنم؟

همه چیز در volume نام‌گذاری‌شده etc_wireguard قرار دارد. در نسخه 15، این به معنای یک پایگاه داده SQLite در مسیر /etc/wireguard/wg-easy.db داخل کانتینر است. مستندات نسخه 15 هیچ قابلیت پشتیبان‌گیری یا خروجی گرفتن (export) را ذکر نکرده‌اند، بنابراین از خودِ volume پشتیبان بگیرید: دستور docker compose down را اجرا کنید، محتویات volume را به جایی خارج از سرور کپی کنید و سپس stack را با docker compose up -d دوباره بالا بیاورید. این کار را پیش از هرگونه ارتقا انجام دهید. فایل wg0.json که در راهنماهای قدیمی‌تر ذکر شده، متعلق به نسخه 14 از wg-easy است. این فایلی است که دکمه پشتیبان‌گیری در نسخه 14 خروجی می‌دهد و در نسخه 15، تنها کاربرد آن فایلی است که هنگام مهاجرت از نصب قدیمی، در ویزارد راه‌اندازی آپلود می‌کنید.

آیا می‌توانم wg-easy را پشت یک reverse proxy اجرا کنم؟

بله. پروکسی را جلوی پورت TCP 51821 قرار دهید، TLS را در همان‌جا terminate کنید و متغیر INSECURE=true را روی کانتینر تنظیم کنید تا درخواست‌های HTTP ساده (plain HTTP) را از پروکسی بپذیرد. پورت UDP 51820 را همچنان مستقیماً منتشر (publish) کنید، زیرا ترافیک VPN از نوع UDP است و از پروکسی HTTP عبور نمی‌کند.