راه اندازی WireGuard با رابط کاربری wg-easy در Docker
با استفاده از Docker Compose و تنظیمات دقیق NET_ADMIN و sysctl، ابزار wg-easy را اجرا کنید. این راهنما نحوه رفع خطای ماژول هسته و اتصال سریع موبایل با QR را شرح میدهد.
آنچه میسازید
wg-easy در واقع WireGuard به همراه یک رابط کاربری وب است که در قالب یک Docker container اجرا میشود. این ابزار رابط 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) را پین کنید؛ زیرا ارتقای نسخه اصلی، فرمت پیکربندی روی دیسک را تغییر میدهد و بهراحتی به نسخه قبل بازنمیگردد.
فایل 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::/64etc_wireguard یک volume نامگذاریشده است که کلید سرور و تمام کلاینتهایی که ایجاد میکنید را در خود نگه میدارد. از این volume نسخه پشتیبان تهیه کنید، در غیر این صورت با rebuild کردن، تمام peerهای خود را از دست خواهید داد. اگر ترجیح میدهید این فایلها را در فایلسیستم میزبان ببینید، آن را با یک bind mount جایگزین کنید. پیش از انجام این کار، تفاوت بین bind mountها و named volumeها را مطالعه کنید، زیرا نحوه مدیریت مجوزها (permissions) در آنها متفاوت است.
چرا به NET_ADMIN، SYS_MODULE و sysctlها نیاز است
بهصورت پیشفرض، یک کانتینر اجازه دسترسی به پشته شبکه (network stack) را ندارد و هر یک از این خطوط، یکی از محدودیتهای خاص را برطرف میکند.
NET_ADMIN به کانتینر اجازه میدهد تا رابط wg0 را ایجاد کند، به آن آدرس اختصاص دهد و مسیرها (routes) را بنویسد. بدون این قابلیت، کانتینر شروع به کار میکند اما هنگام بالا آوردن رابط شبکه متوقف میشود، زیرا ip link add wg0 type wireguard مقدار Operation not permitted را برمیگرداند.
SYS_MODULE به همراه mount کردن /lib/modules بهصورت فقطخواندنی (read-only)، به کانتینر اجازه میدهد تا ماژول هسته WireGuard را بارگذاری کند، در صورتی که میزبان (host) هنوز آن را بارگذاری نکرده باشد. این ماژول در هسته میزبان قرار دارد، نه داخل ایمیج؛ به همین دلیل است که دایرکتوری میزبان باید قابل مشاهده باشد. در هستههای مدرن، این ماژول معمولاً بهصورت داخلی (built-in) وجود دارد و میتوانید آن را با دستور sudo modprobe wireguard && echo ok روی میزبان تأیید کنید.
net.ipv4.ip_forward=1 باعث میشود هسته، بستههایی را که مقصد آنها خودِ دستگاه نیست، فوروارد کند. بدون این تنظیم، کلاینت متصل میشود، handshake با موفقیت انجام میگیرد، اما تمام بستههای ارسالی به اینترنت حذف (drop) میشوند؛ در نتیجه ping 1.1.1.1 دچار وقفه (timeout) میشود، در حالی که به نظر میرسد VPN متصل است.
net.ipv4.conf.all.src_valid_mark=1 موردی است که باعث تعجب کاربران میشود. WireGuard بستههای خروجی خود را علامتگذاری میکند تا دوباره به داخل تونل مسیریابی نشوند. فیلترینگ سختگیرانه مسیر معکوس (Strict reverse path filtering)، بستهای را که آدرس مبدأ آن با مسیر مورد انتظار مطابقت ندارد، حذف میکند. این sysctl به هسته دستور میدهد که بستههای علامتگذاریشده را بپذیرد؛ این همان چیزی است که مانع از اختلال تونل کامل در عملکرد خودش میشود.
راهاندازی سرویس و ایجاد حساب کاربری مدیر
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 روی کانتینری که با تنظیمات متفاوت ایجاد شده است، شبکه را در وضعیتی ناپایدار قرار میدهد. اگر میخواهید پس از راهاندازی مجدد سیستم (reboot)، استک دوباره بالا بیاید، restart: unless-stopped این کار را پوشش میدهد و رفتار راهاندازی سرویسهای compose توضیح میدهد که این سیاست چه تضمینهایی دارد و چه تضمینهایی ندارد.
رابط کاربری وب روی پورت TCP 51821 گوش میدهد. در اولین بازدید، صفحهای برای تنظیمات اولیه نمایش داده میشود که در آن باید حساب کاربری مدیر را ایجاد کرده و آدرس میزبان (host address) که کلاینتها برای اتصال به سرور استفاده میکنند را تأیید کنید. این آدرس میزبان در خط Endpoint تمام فایلهای پیکربندی کلاینت قرار میگیرد، بنابراین باید IP عمومی یا نام دامنه 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 tunnel به آن دسترسی پیدا کنید:
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 است.
بررسی کنید که آیا container واقعاً در حال گوش دادن روی این پورت است یا خیر:
sudo ss -ulnp | grep 51820شما باید یک سوکت UDP در وضعیت listening مشاهده کنید. اگر در آن خط چیزی نمایش داده نشد، به این معنی است که container هرگز رابط شبکه را بالا نیاورده است و 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 وارد کنید. کلید خصوصی موجود در آن فایل یک بار تولید و یک بار نمایش داده میشود. با این فایل همانطور رفتار کنید که با یک کلید خصوصی SSH رفتار میکنید.
چه زمانی از رابط کاربری فراتر میرویم
ابزار wg-easy زمانی بهترین گزینه است که همتایان شما افراد و گوشیهای موبایل باشند. رابط کاربری آن از ویرایش دستی فایلهای پیکربندی سریعتر است و ابطال دسترسی یک گوشی گمشده تنها با یک کلیک انجام میشود.
زمانی که به قابلیتی نیاز داشته باشید که در رابط کاربری تعریف نشده است، به محدودیتهای آن برخورد خواهید کرد. مسیریابی site-to-site، که در آن AllowedIPs یک همتا کل یک زیرشبکه (subnet) راه دور را پوشش میدهد و نه فقط یک آدرس واحد، معمولاً اولین مانع است. تونلهای split با قوانین مسیریابی اختصاصی برای هر همتا، یا پیکربندیهایی که توسط ابزارهای provisioning تولید میشوند، گامهای بعدی هستند. در آن مرحله، راهاندازی دستی دشوارتر نیست، فقط متفاوت است و راهنمای ساده WireGuard همان تونل را با استفاده از wg0.conf ایجاد میکند. اگر ترجیح میدهید اجرای control plane را بهطور کامل متوقف کنید، مقایسه WireGuard با Tailscale گزینههای مدیریتشده را بررسی میکند. اینکه آیا این یک معامله منصفانه است یا خیر، به این بستگی دارد که coordination server واقعاً به چه چیزی دسترسی دارد و مطالعه مدل اعتماد Tailscale پیش از سپردن شبکه به آن، ضروری است. هزینه معمولاً پرسش بعدی است و آنچه طرح رایگان Tailscale پوشش میدهد برای یک خانواده یا تیم کوچک کافی است تا هزینهای پرداخت نکنند. پس از آن، صورتحساب بر اساس تعداد کاربران محاسبه میشود نه دستگاهها، که ساختار متفاوتی نسبت به هزینههای VPS دارد؛ بنابراین هزینههای Tailscale پس از عبور از طرح رایگان عددی است که باید پیش از مهاجرت تیم بررسی کنید. تونل کاملی که بهتازگی ساختید، معادل مستقیمی در آنجا دارد، زیرا معرفی VPS به عنوان یک exit node در Tailscale همان مسیر خروجی را از طریق سرور برای شما فراهم میکند، با این تفاوت که بهجای ویرایش فایل پیکربندی هر کلاینت، در کنسول مدیریت تأیید میشود. مانع زیرشبکه نیز معادل دارد، زیرا معرفی یک شبکه خصوصی کامل از طریق VPS آن شبکه را در اختیار تمام دستگاههای موجود در tailnet قرار میدهد، بدون اینکه نیاز باشد AllowedIPs را برای هر همتا ویرایش کنید؛ همان کاری که باعث شد از رابط کاربری فاصله بگیرید. اگر به آن داشبورد و مسیریابی خودکار mesh نیاز دارید اما نمیخواهید از coordination server شخص ثالث استفاده کنید، اجرای سرور اختصاصی NetBird روی VPS کنترل 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 و در فایل wg0.json قرار دارد. رابط کاربری نیز یک دکمه پشتیبانگیری دارد که همان دادهها را خروجی میدهد. پیش از هر ارتقا، آن فایل را به مکانی خارج از سرور کپی کنید. بازیابی اطلاعات در مرحله راهاندازی کانتینر جدید، از طریق بارگذاری (upload) همان فایل انجام میشود.
آیا میتوانم wg-easy را پشت یک reverse proxy اجرا کنم؟
بله. پروکسی را جلوی پورت TCP 51821 قرار دهید، TLS را در همانجا terminate کنید و متغیر INSECURE=true را روی کانتینر تنظیم کنید تا درخواستهای HTTP ساده (plain) از سمت پروکسی را بپذیرد. پورت UDP 51820 را همچنان مستقیماً منتشر (publish) کنید، زیرا ترافیک VPN از نوع UDP است و از طریق پروکسی HTTP عبور نمیکند.