آموزش نصب wg-easy و مدیریت WireGuard با Docker
با استفاده از wg-easy در Docker، رابط کاربری وب برای WireGuard بسازید. این راهنما تنظیمات دقیق NET_ADMIN، پورتهای 51820 و sysctls لازم برای اتصال موبایل را بررسی میکند.
آنچه در حال ساخت آن هستید
ابزار wg-easy در واقع همان WireGuard با یک رابط کاربری وب است که در قالب یک کانتینر Docker اجرا میشود. این ابزار رابط WireGuard را برای شما مدیریت کرده و یک رابط کاربری مرورگر برای ایجاد کلاینتها اضافه میکند. برای هر کلاینتی که ایجاد میکنید، یک فایل پیکربندی و یک کد QR تولید میشود تا گوشی موبایل با گرفتن دوربین روی صفحه نمایش به VPN متصل شود.
تونل ایجاد شده، یک WireGuard معمولی است. ماژول هسته (kernel module) بستهها را جابهجا میکند، بنابراین نرخ انتقال داده (throughput) مشابه یک تنظیم دستی است. آنچه به دست میآورید، مدیریت چرخه حیات کلاینت است: افزودن، غیرفعال کردن و حذف همتاها (peers) بدون نیاز به ویرایش فایل پیکربندی از طریق SSH. آنچه از دست میدهید، کنترل مستقیم بر آن فایل پیکربندی است که موضوع راهاندازی دستی WireGuard روی یک VPS است.
شما به یک میزبان لینوکسی نیاز دارید که بتوانید آن را مدیریت کنید، مانند یک KVM VPS که دارای یک آدرس IP عمومی یا یک نام دامنه متصل به آن باشد، یک پردازنده x86_64 یا arm64، موتور Docker به همراه افزونه 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::/64etc_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 هر چیزی را که تغییر کرده باشد، دوباره میسازد. اگر میخواهید پس از reboot سرویسها دوباره بالا بیایند، restart: unless-stopped این موضوع را پوشش میدهد و رفتار بوت سرویسهای compose توضیح میدهد که این سیاست چه کاری انجام میدهد و چه وعدهای نمیدهد.
رابط کاربری وب روی پورت TCP 51821 گوش میدهد. در اولین بازدید، صفحهای برای تنظیمات نمایش داده میشود که در آن حساب کاربری مدیر را ایجاد کرده و آدرس host که کلاینتها برای دسترسی به سرور استفاده میکنند را تأیید میکنید. آن آدرس host در خط Endpoint تنظیمات هر کلاینت قرار میگیرد، بنابراین باید IP عمومی یا نام DNS سرور مجازی (VPS) باشد. اگر اشتباه باشد، کد QR که به گوشی میدهید به جایی اشاره میکند که در دسترس نیست و handshake هرگز کامل نمیشود.
یک نکته دیگر درباره آن پورت: wg-easy خودش TLS را ارائه نمیدهد و HTTP ساده را هم رد نمیکند. روی http:// رابط کاربری همچنان بارگذاری میشود و یک بنر هشدار نشان میدهد. چیزی که تنظیمات پیشفرض تغییر میدهد، کوکی نشست (session cookie) است که پرچم Secure را حمل میکند؛ بنابراین مرورگری که از طریق HTTP ساده و روی یک آدرس عمومی به رابط کاربری میرسد، آن را رد میکند و ورود به سیستم پایدار نمیماند. INSECURE=true آن پرچم را حذف میکند و کار دیگری انجام نمیدهد. قرار دادن یک reverse proxy در مقابل آن که TLS را خاتمه دهد، حتی با یک گواهی خودامضا (self-signed)، نیازی به هیچ تغییری در wg-easy ندارد.
پورت رابط کاربری را در اینترنت منتشر نکنید
فایل 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 فقط پرچم Secure را از کوکی نشست حذف میکند تا مرورگر ورود شما را روی این آدرس HTTP ساده حفظ کند، و این کار در اینجا امن است زیرا مسیر 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 با قوانین مسیریابی برای هر کلاینت، یا پیکربندیهایی که توسط ابزارهای خودکارسازی شما تولید میشوند، گامهای بعدی هستند. در آن مرحله، تنظیمات دستی سختتر نیست، بلکه متفاوت است و راهنمای ساده WireGuard همان تونل را با استفاده از wg0.conf میسازد. اگر ترجیح میدهید اصلاً درگیر اجرای صفحه کنترل (control plane) نباشید، مقایسه WireGuard با Tailscale گزینههای مدیریتشده را بررسی میکند. اینکه آیا این یک معامله منصفانه است یا خیر، به این بستگی دارد که سرور هماهنگکننده واقعاً به چه چیزی دسترسی دارد و خواندن مدل اعتماد Tailscale پیش از سپردن شبکه خود به آن، ارزشمند است. هزینه معمولاً سوال بعدی است و آنچه طرح رایگان Tailscale پوشش میدهد شامل حداکثر شش کاربر بدون هزینه است، که فقط برای استفاده غیرتجاری کاربرد دارد؛ بنابراین یک خانواده هزینهای نمیپردازد اما یک تیم تجاری کوچک، فارغ از تعداد کم اعضا، واجد شرایط آن نیست. پس از آن نقطه، صورتحساب بر اساس تعداد کاربران محاسبه میشود نه دستگاهها، که الگوی متفاوتی از هزینهای است که برای VPS فعلی خود میپردازید؛ بنابراین هزینههای Tailscale پس از خروج از طرح رایگان عددی است که باید پیش از مهاجرت تیم بررسی کنید. تونل کاملی که ساختید در آنجا معادل مستقیمی دارد، زیرا معرفی VPS به عنوان exit node در Tailscale همان مسیر خروجی را از طریق سرور برای شما فراهم میکند، با این تفاوت که به جای نوشتن در پیکربندی هر کلاینت، در کنسول مدیریت تأیید میشود. مانع زیرشبکه نیز معادل دارد، زیرا معرفی یک شبکه خصوصی کامل از طریق VPS آن شبکه را در اختیار تمام دستگاههای موجود در tailnet قرار میدهد، بدون اینکه نیاز به ویرایش AllowedIPs برای هر کلاینت باشد که شما را از رابط کاربری wg-easy دور کرده بود. اگر آن داشبورد و مسیریابی خودکار مش (mesh) را میخواهید اما نمیخواهید از سرور هماهنگکننده شخص دیگری استفاده کنید، اجرای سرور NetBird شخصی روی VPS صفحه کنترل را روی سختافزاری که مالک آن هستید نگه میدارد، به قیمت انجام تنظیمات 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 برای این هستند که کانتینر بتواند ماژول را بارگذاری کند، در صورتی که میزبان (host) آن را نداشته باشد. روی میزبانی که دستور 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 ذکر شده، متعلق به wg-easy 14 است. این همان فایلی است که دکمه پشتیبانگیری نسخه 14 خروجی میدهد و در نسخه 15 تنها کاربرد آن، فایلی است که هنگام مهاجرت از یک نصب قدیمی در setup wizard آپلود میکنید.
آیا میتوانم wg-easy را پشت یک reverse proxy اجرا کنم؟
بله. پروکسی را در مقابل پورت رابط کاربری وب قرار دهید، TLS را در آنجا خاتمه دهید و INSECURE را تنظیمنشده باقی بگذارید. کانتینر مسیر HTTP ساده از پروکسی را همانطور که هست میپذیرد و مثالهای رسمی Caddy و Traefik هیچ متغیر INSECURE را تنظیم نمیکنند. از آنجا که مرورگر با HTTPS با پروکسی صحبت میکند، شما میخواهید کوکی نشست پرچم Secure خود را حفظ کند، که تنها چیزی است که INSECURE=true آن را حذف میکند. پورت UDP 51820 را مستقیماً منتشر نگه دارید، زیرا ترافیک VPN از نوع UDP است و از پروکسی HTTP عبور نمیکند.