راهاندازی wg-easy و WireGuard در Docker
آموزش اجرای WireGuard با رابط وب wg-easy در Docker Compose؛ شامل پورتها، NET_ADMIN، sysctlهای ضروری، تفاوت نسخه 15 و ساخت QR برای اتصال گوشی.
آنچه میسازید
wg-easy، WireGuard را با یک رابط وب و در قالب یک Docker container اجرا میکند. این ابزار رابط WireGuard را برای شما مدیریت میکند و یک رابط کاربری مرورگری برای ایجاد clientها ارائه میدهد. هر client که ایجاد میکنید، یک فایل پیکربندی و یک کد QR دریافت میکند؛ بنابراین تلفن با قرار دادن دوربین مقابل صفحه، به VPN متصل میشود.
خود تونل، WireGuard معمولی است. ماژول kernel، packetها را جابهجا میکند؛ بنابراین throughput با یک setup دستی یکسان است. مزیت اصلی، مدیریت چرخه عمر client است: میتوانید peerها را بدون ویرایش فایل پیکربندی از طریق SSH اضافه، غیرفعال و حذف کنید. در مقابل، کنترل مستقیم آن فایل پیکربندی را از دست میدهید. این موضوع در setup دستی WireGuard روی یک VPS بررسی میشود.
به یک KVM VPS با آدرس عمومی IPv4، Docker Engine بههمراه Compose plugin و دسترسی root نیاز دارید. Container virtualisation که kernel میزبان را بهاشتراک میگذارد، مانند OpenVZ یا LXC، معمولاً نمیتواند ماژول WireGuard را load کند؛ در نتیجه container نمیتواند رابط را بالا بیاورد.
نسخه 15 تنظیمات را از محیط خارج کرد
بیشتر راهنماهایی که پیدا میکنید برای wg-easy 14 نوشته شدهاند. در آن نسخه، WG_HOST را روی نشانی سرور و PASSWORD_HASH را روی hash مربوط به bcrypt گذرواژه مدیر تنظیم میکردید و هر دو مقدار را بهصورت متغیرهای محیطی قرار میدادید. نسخه 15 بازنویسی شده است. یادداشتهای رسمی مهاجرت بهصراحت اعلام میکنند که v15 از همان متغیرهای محیطی v14 استفاده نمیکند و بیشتر این تنظیمات به پنل مدیریت در رابط وب منتقل شدهاند.
بنابراین WG_HOST و PASSWORD_HASH دیگر هیچ کاری انجام نمیدهند. اگر یک فایل compose قدیمی را کپی کنید، کانتینر شروع میشود، این خطوط را نادیده میگیرد و سپس از شما میخواهد در مرورگر یک حساب مدیر ایجاد کنید. این یک bug نیست. این جریان راهاندازی جدید است.
از July 2026، tag اصلی که باید pin شود 15 است. بهجای استفاده از latest، نسخه اصلی را pin کنید، زیرا ارتقای نسخه اصلی قالب پیکربندی روی دیسک را تغییر میدهد و بازگشت به نسخه قبلی را بهدرستی انجام نمیدهد.
فایل 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 نامگذاریشده است که کلید سرور و همه clientهایی را که ایجاد میکنید نگه میدارد. از این volume نسخه پشتیبان تهیه کنید؛ در غیر این صورت، rebuild همه peerهای شما را حذف میکند. اگر ترجیح میدهید این فایلها را در فایلسیستم host ببینید، آن را با یک bind mount جایگزین کنید و پیش از این کار، تفاوت bind mountها و volumeهای نامگذاریشده را مطالعه کنید، زیرا permissionها به شکل متفاوتی اعمال میشوند.
دلیل نیاز به NET_ADMIN، SYS_MODULE و sysctlها
بهطور پیشفرض، یک کانتینر اجازه دسترسی به پشته شبکه را ندارد و هرکدام از این خطوط یک مانع مشخص را برطرف میکنند.
`NET_ADMIN به کانتینر اجازه میدهد رابط wg0 را ایجاد کند، برای آن آدرس تعیین کند و routeها را بنویسد. بدون آن، کانتینر شروع میشود، اما هنگام بالا آوردن رابط از کار میافتد؛ زیرا 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 باعث میشود کرنل بستههایی را که برای خود سیستم ارسال نشدهاند، forward کند. بدون آن، کلاینت متصل میشود، handshake موفق انجام میشود، اما سپس همه بستههای اینترنت drop میشوند؛ بنابراین ping 1.1.1.1` timeout میشود، درحالیکه VPN متصل به نظر میرسد.
`net.ipv4.conf.all.src_valid_mark=1` همان موردی است که افراد را شگفتزده میکند. WireGuard بستههای خروجی خودش را علامتگذاری میکند تا دوباره به داخل tunnel route نشوند. reverse path filtering سختگیرانه، بستهای را که آدرس مبدأ آن با route مورد انتظار مطابقت ندارد، drop میکند. این 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 روی کانتینری که با تنظیمات متفاوت ایجاد شده است، شبکه را در وضعیت ناسازگار باقی میگذارد. اگر میخواهید پس از راهاندازی مجدد، این پشته دوباره اجرا شود، restart: unless-stopped از قبل این کار را پوشش میدهد و رفتار راهاندازی سرویسهای compose توضیح میدهد که این سیاست چه چیزی را تضمین میکند و چه چیزی را تضمین نمیکند.
رابط وب روی TCP 51821 گوش میدهد. در نخستین بازدید، صفحهای برای راهاندازی نمایش داده میشود. در این صفحه حساب مدیر را ایجاد میکنید و نشانی میزبان را که کلاینتها برای دسترسی به سرور استفاده میکنند، تأیید میکنید. این نشانی میزبان در خط Endpoint هر پیکربندی کلاینت قرار میگیرد؛ بنابراین باید IP عمومی یا نام DNS مربوط به VPS باشد. اگر این نشانی نادرست باشد، کد QR که در اختیار تلفن قرار میدهید به مقصدی غیرقابلدسترسی اشاره میکند و handshake هرگز کامل نمیشود.
یک نکته دیگر درباره این پورت: wg-easy 15، مگر اینکه INSECURE=true را تنظیم کنید، HTTP ساده را رد میکند. دسترسی از طریق 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.addresshttp://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 دلیل آن را مشخص میکند.
ایجاد یک client و اسکن آن با تلفن
در رابط کاربری، یک client ایجاد کنید و نامی برای آن وارد کنید که بعداً بتوانید آن را تشخیص دهید؛ برای مثال، نام دستگاهی که client به آن تعلق دارد. wg-easy آدرس تونل آزاد بعدی را اختصاص میدهد و جفت کلید را برای شما تولید میکند. هر ردیف client یک کد QR و یک فایل قابلدانلود .conf دارد.
اپلیکیشن رسمی WireGuard را روی تلفن نصب کنید، گزینه افزودن تونل از طریق کد QR را انتخاب کنید و دوربین را به سمت کد روی صفحه بگیرید. تونل با همان نامی که وارد کردهاید نمایش داده میشود. آن را فعال کنید. سپس ردیف client در رابط کاربری شروع به نمایش شمارندههای انتقال و زمان آخرین handshake میکند.
client که پس از فعالسازی هیچ handshakeای نشان نمیدهد، اصلاً به server دسترسی پیدا نمیکند. این وضعیت معمولاً به UDP 51820 مربوط است؛ یا در firewall ارائهدهنده یا در آدرس endpoint درجشده در config. client که handshake دارد اما اینترنت آن کار نمیکند، معمولاً به forwarding یا DNS مربوط است.
در رایانه رومیزی، فایل .conf را دانلود کنید و آن را در client WireGuard وارد کنید؛ آن را دوباره بهصورت دستی وارد نکنید. کلید خصوصی موجود در این فایل فقط یک بار تولید و فقط یک بار نمایش داده میشود. با این فایل همانند یک کلید خصوصی SSH رفتار کنید.
چه زمانی از UI عبور کنید
wg-easy تا زمانی ابزار مناسبی است که همتاهای شما افراد و تلفنها باشند. استفاده از UI از ویرایش فایلهای پیکربندی سریعتر است و لغو دسترسی یک تلفن گمشده فقط با یک کلیک انجام میشود.
وقتی به قابلیتی نیاز داشته باشید که UI آن را مدلسازی نمیکند، به محدودیتهای آن میرسید. مسیریابی سایتبهسایت، که در آن AllowedIPs یک همتا کل زیرشبکه راه دور را پوشش میدهد، نه فقط یک نشانی را، معمولاً نخستین مانع است. تونلهای تفکیکشده با قوانین مسیریابی اختصاصی برای هر همتا، یا پیکربندی تولیدشده توسط ابزار تأمین شما، موارد بعدی هستند. در این مرحله، راهاندازی دستی سختتر نیست؛ فقط متفاوت است و راهنمای ساده WireGuard ساخت همان تونل را با استفاده از wg0.conf نشان میدهد. اگر ترجیح میدهید دیگر صفحهکنترل را اجرا نکنید، مقایسه WireGuard با Tailscale گزینه مدیریتشده را توضیح میدهد.
اگر بخش ناآشنای متن بالا نحو compose بوده است، نه بخش WireGuard، مبانی Docker Compose در یک VPS قالب فایل و فرمانهای روزمره را توضیح میدهد.
FAQ
چرا wg-easy متغیرهای WG_HOST و PASSWORD_HASH را نادیده میگیرد؟
این متغیرها به wg-easy 14 تعلق دارند. نسخه 15 بازنویسی شده است و پروژه upstream تقریباً تمام تنظیمات را به پنل مدیریت در رابط وب منتقل کرده است. کانتینر هیچیک از این دو متغیر را نمیخواند؛ بنابراین بهصورت عادی راهاندازی میشود و سپس در نخستین بازدید از شما میخواهد یک حساب مدیر ایجاد کنید. آدرس میزبان قابلدسترسی برای کلاینت را در همان صفحه راهاندازی تنظیم کنید.
اگر kernel من از قبل WireGuard را دارد، آیا به SYS_MODULE نیاز دارم؟
خیر. SYS_MODULE و mount مربوط به /lib/modules وجود دارند تا کانتینر بتواند زمانی که میزبان module را ندارد، آن را بارگذاری کند. در میزبانی که sudo modprobe wireguard از قبل با موفقیت اجرا میشود، این قابلیت استفاده نمیشود. حذف آن یک اقدام منطقی برای سختسازی است و NET_ADMIN در هر صورت همچنان لازم است.
کلاینت متصل میشود، اما اینترنت وجود ندارد. مشکل چیست؟
وجود handshake بدون ترافیک تقریباً همیشه به forwarding مربوط است. بررسی کنید net.ipv4.ip_forward=1 و net.ipv4.conf.all.src_valid_mark=1 همچنان در compose file وجود داشته باشند، زیرا یک کپی که بهصورت دستی ویرایش شده باشد اغلب آنها را از دست میدهد. اگر forwarding فعال است، DNS server دریافتشده توسط کلاینت را بررسی کنید. تونلی که همه ترافیک را از طریق VPN ارسال میکند، اما به DNS serverی اشاره دارد که دیگر نمیتواند به آن دسترسی پیدا کند، در مرورگر دقیقاً مانند یک اتصال قطعشده دیده میشود.
چگونه از کلاینتها نسخه پشتیبان بگیرم؟
همهچیز در named volume با نام etc_wireguard و در فایل wg0.json ذخیره میشود. رابط کاربری نیز دکمهای برای پشتیبانگیری دارد که همین دادهها را صادر میکند. پیش از هر ارتقا، این فایل را در محلی خارج از server کپی کنید. بازیابی در یک کانتینر جدید، با بارگذاری فایل هنگام مرحله راهاندازی انجام میشود.
آیا میتوانم wg-easy را پشت reverse proxy اجرا کنم؟
بله. proxy را جلوی TCP 51821 قرار دهید، TLS را در همانجا خاتمه دهید و INSECURE=true را روی کانتینر تنظیم کنید تا hop ساده HTTP را از proxy بپذیرد. UDP 51820 را مستقیماً منتشر نگه دارید، زیرا ترافیک VPN از نوع UDP است و از HTTP proxy عبور نمیکند.