wg-easy: WireGuard z panelem WWW w Dockerze
Instalacja wg-easy w Docker Compose. Konfiguracja uprawnien NET_ADMIN, wymaganych sysctl oraz portow. Rozwiazanie problemow z wersja 15 i generowanie kodow QR dla klientow.
Co budujesz
wg-easy to WireGuard z interfejsem WWW, działający jako jeden kontener Docker. Zarządza on interfejsem WireGuard i dodaje interfejs przeglądarkowy do tworzenia klientów. Każdy utworzony klient otrzymuje plik konfiguracyjny oraz kod QR, dzięki czemu telefon dołącza do VPN po skierowaniu aparatu na ekran.
Sam tunel to standardowy WireGuard. Moduł jądra przesyła pakiety, więc przepustowość jest taka sama jak w przypadku ręcznej konfiguracji. Zyskujesz zarządzanie cyklem życia klienta: dodawanie, wyłączanie i usuwanie peerów bez edycji plików konfiguracyjnych przez SSH. Rezygnujesz z bezpośredniej kontroli nad tą konfiguracją, co jest tematem ręcznej konfiguracji WireGuard na VPS.
Wymagany jest host z systemem Linux, którym możesz zarządzać, na przykład VPS typu KVM, z publicznym adresem IP lub domeną, procesor x86_64 lub arm64, Docker Engine z wtyczką Compose oraz dostęp root. Wirtualizacja kontenerowa współdzieląca jądro hosta, taka jak OpenVZ lub LXC, zazwyczaj nie pozwala na załadowanie modułu WireGuard, przez co kontener nie uruchomi interfejsu.
Wersja 15 przeniosła ustawienia poza zmienne środowiskowe
Większość dostępnych poradników powstała dla wg-easy 14, gdzie WG_HOST ustawiało się na adres serwera, a PASSWORD_HASH na skrót bcrypt hasła administratora, oba jako zmienne środowiskowe. Wersja 15 to całkowita przebudowa. Oficjalne notatki migracyjne jasno wskazują, że v15 nie korzysta z tych samych zmiennych środowiskowych co v14, a większość z nich przeniesiono do panelu administracyjnego w interfejsie webowym.
Zatem WG_HOST oraz PASSWORD_HASH nie pełnią już żadnej funkcji. W przypadku użycia starego pliku compose, kontener uruchomi się, zignoruje te linie, a następnie poprosi o utworzenie konta administratora w przeglądarce. Nie jest to błąd. To nowy proces konfiguracji.
Według stanu na październik 2026, głównym tagiem do przypięcia jest 15. Należy przypinać wersję główną zamiast używać latest, ponieważ aktualizacja do nowej wersji głównej zmienia format konfiguracji na dysku i nie pozwala na łatwe wycofanie zmian. Przejście na wersję 15 zastąpiło plik JSON z v14 bazą danych SQLite.
Plik compose
Utwórz katalog dla stosu i zapisz w nim oficjalny plik compose. Jest to plik źródłowy, z którego usunięto zakomentowane opcjonalne linie.
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.ymlZawartość pliku wygląda następująco:
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 to nazwany wolumen przechowujący klucz serwera oraz wszystkich utworzonych klientów, zapisanych w bazie danych SQLite wg-easy.db. Wykonuj kopię zapasową tego wolumenu, w przeciwnym razie przebudowa spowoduje utratę wszystkich peerów. Jeśli wolisz przechowywać te pliki w systemie plików hosta, zamień go na bind mount. Przed wykonaniem tej operacji zapoznaj się z różnicami między bind mounts a nazwanymi wolumenami, ponieważ uprawnienia działają w obu przypadkach inaczej.
Dlaczego wymagane są NET_ADMIN, SYS_MODULE oraz sysctl
Domyślnie kontener nie ma uprawnień do modyfikacji stosu sieciowego, a każda z tych dyrektyw znosi konkretne ograniczenie.
NET_ADMIN pozwala kontenerowi utworzyć interfejs wg0, przypisać mu adres oraz zdefiniować trasy. Bez tego kontener uruchamia się, a następnie kończy działanie podczas inicjalizacji interfejsu, ponieważ ip link add wg0 type wireguard zwraca Operation not permitted.
SYS_MODULE wraz z montowaniem /lib/modules w trybie tylko do odczytu umożliwia kontenerowi załadowanie modułu jądra WireGuard, jeśli nie został on wcześniej załadowany przez hosta. Moduł znajduje się w jądrze hosta, a nie wewnątrz obrazu, dlatego katalog hosta musi być widoczny. W nowoczesnych jądrach moduł jest zazwyczaj wbudowany, co można potwierdzić za pomocą sudo modprobe wireguard && echo ok na hoście.
net.ipv4.ip_forward=1 wymusza na jądrze przekazywanie pakietów, które nie są adresowane bezpośrednio do danej maszyny. Bez tego ustawienia klient nawiązuje połączenie, uzgadnianie przebiega pomyślnie, lecz każdy pakiet kierowany do Internetu jest odrzucany, co powoduje przekroczenie czasu oczekiwania ping 1.1.1.1, mimo że VPN wydaje się połączony.
net.ipv4.conf.all.src_valid_mark=1 jest ustawieniem, które często zaskakuje użytkowników. WireGuard oznacza własne pakiety wychodzące, aby nie były one kierowane z powrotem do tunelu. Ścisłe filtrowanie ścieżki zwrotnej (reverse path filtering) wykrywa pakiet, którego adres źródłowy nie pasuje do oczekiwanej trasy, i odrzuca go. Ten sysctl instruuje jądro, aby akceptowało oznaczone pakiety, co zapobiega samoczynnemu przerywaniu pełnego tunelu.
Uruchomienie i utworzenie konta administratora
cd /etc/docker/containers/wg-easy
sudo docker compose up -d
sudo docker compose logs -fNależy używać docker compose up oraz docker compose down, a nie start i stop. Polecenie start restartuje jedynie istniejące kontenery, więc wszelkie zmiany w pliku compose, w tym ustawienia sieci i sysctl, nie zostaną zastosowane. Natomiast up odtwarza zmienione elementy. Jeśli wymagane jest przywrócenie stosu po restarcie systemu, restart: unless-stopped zapewnia taką funkcjonalność, a zachowanie usług compose podczas uruchamiania systemu wyjaśnia zakres działania tej polityki.
Interfejs webowy nasłuchuje na porcie TCP 51821. Przy pierwszej wizycie wyświetlana jest strona konfiguracji, na której tworzy się konto administratora oraz potwierdza adres hosta, z którego będą korzystać klienci w celu połączenia z serwerem. Adres ten trafia do linii Endpoint w każdej konfiguracji klienta, dlatego musi to być publiczny adres IP lub nazwa DNS serwera VPS. W przypadku błędnego adresu kod QR przekazany na urządzenie mobilne będzie wskazywał nieosiągalny punkt, co uniemożliwi nawiązanie połączenia.
Dodatkowa uwaga dotycząca portu: wg-easy 15 odrzuca zwykłe połączenia HTTP, chyba że ustawiono INSECURE=true. Dopuszczalne jest korzystanie z HTTPS przy użyciu niezaufanego certyfikatu lub terminacja TLS na reverse proxy przed usługą. Połączenie przez http:// przy ustawieniach domyślnych nie będzie działać.
Nie udostępniaj portu interfejsu użytkownika w sieci publicznej
Plik compose publikuje port 51821 na wszystkich interfejsach. Jest to strona logowania do urządzenia, które może kierować ruchem sieciowym, dlatego nie powinna być dostępna dla całego świata. Publikacja portu w Dockerze powoduje zapisanie reguł w łańcuchu DOCKER, który jest przetwarzany przed ufw, więc reguła blokująca w ufw nie zamyka tego portu. Warto zrozumieć tę pułapkę, a artykuł dlaczego publikowane porty Dockera ignorują ufw wyjaśnia to zagadnienie w pełni.
Najprostszym rozwiązaniem jest powiązanie interfejsu użytkownika z adresem loopback i uzyskanie do niego dostępu przez tunel SSH:
ports:
- "51820:51820/udp"
- "127.0.0.1:51821:51821/tcp"
environment:
- INSECURE=trueNastępnie z poziomu laptopa:
ssh -L 51821:127.0.0.1:51821 youruser@your.server.addressOtwórz http://127.0.0.1:51821 w przeglądarce na swoim laptopie. Ruch jest szyfrowany przez SSH, port nie odpowiada nikomu innemu, a INSECURE=true jest tutaj bezpieczne, ponieważ niezaszyfrowany ruch HTTP nigdy nie opuszcza interfejsu loopback.
Otwarcie portu UDP 51820 i weryfikacja obu firewalli
WireGuard wymaga, aby port UDP 51820 był dostępny z Internetu. Docker publikuje ten port, jednak wielu dostawców stosuje zewnętrzny firewall sieciowy przed serwerem VPS, o którym Docker nie posiada informacji. Port należy otworzyć w obu miejscach. Jeśli zarządzasz firewallem hosta za pomocą ufw, podstawowe reguły ufw dla VPS są szybszym rozwiązaniem niż ręczne tworzenie reguł nftables.
Sprawdź, czy kontener faktycznie nasłuchuje:
sudo ss -ulnp | grep 51820Powinieneś zobaczyć nasłuchujący gniazdo UDP. Brak danych w tej linii oznacza, że kontener nie uruchomił interfejsu, a sudo docker compose logs wg-easy wskaże przyczynę tego stanu.
Utworzenie klienta i zeskanowanie go telefonem
W interfejsie użytkownika utwórz klienta i nadaj mu nazwę, którą łatwo rozpoznasz później, na przykład nazwę urządzenia, do którego należy. wg-easy przydziela kolejny wolny adres tunelu i generuje parę kluczy. Każdy wiersz klienta oferuje kod QR oraz plik .conf do pobrania.
Zainstaluj oficjalną aplikację WireGuard na telefonie, wybierz opcję dodania tunelu z kodu QR i skieruj aparat na kod wyświetlony na ekranie. Tunel pojawi się pod wpisaną nazwą. Włącz go, a wiersz klienta w interfejsie zacznie pokazywać liczniki transferu oraz czas ostatniego uzgadniania (handshake). Gdy telefon znajduje się w tunelu, może uzyskać dostęp do usług, których nigdy nie wystawiono do Internetu. Dzięki temu telefon może przesyłać zdjęcia do własnego serwera zdjęć z dowolnego miejsca, bez konieczności otwierania choćby jednego portu tego serwera na świat. Ta sama metoda sprawdza się w przypadku multimediów, a biblioteka Jellyfin przypominająca wypożyczalnię kaset wideo z lat 90. jest wygodna w przeglądaniu z pokoju hotelowego, zachowując przy tym taką samą prywatność, jak w sieci lokalnej. Powiadomienia działają w drugą stronę w tym samym tunelu, ponieważ własny serwer ntfy może wysłać wiadomość na telefon w momencie, gdy zadanie kopii zapasowej zakończy się niepowodzeniem, bez konieczności odpowiadania na żądania z publicznego Internetu.
Klient, który po włączeniu nie wykazuje uzgodnienia (handshake), w ogóle nie łączy się z serwerem. Wskazuje to na problem z portem UDP 51820, albo na firewallu dostawcy, albo w adresie punktu końcowego zapisanym w konfiguracji. Klient, który wykazuje uzgodnienie, ale nie ma dostępu do Internetu, wskazuje na problemy z przekierowaniem ruchu lub konfiguracją DNS.
Na komputerze stacjonarnym pobierz plik .conf i zaimportuj go do klienta WireGuard zamiast przepisywać dane. Klucz prywatny w tym pliku jest generowany i wyświetlany tylko raz. Traktuj ten plik tak samo, jak traktujesz klucz prywatny SSH.
Kiedy zrezygnować z interfejsu graficznego
wg-easy to właściwe narzędzie, gdy Twoimi peerami są ludzie i telefony. Interfejs jest szybszy niż edycja plików konfiguracyjnych, a unieważnienie dostępu dla zgubionego telefonu zajmuje jedno kliknięcie.
Ograniczenia napotkasz, gdy będziesz potrzebować funkcji, których interfejs nie obsługuje. Routing typu site-to-site, gdzie AllowedIPs peera obejmuje całą zdalną podsieć zamiast pojedynczego adresu, jest zazwyczaj pierwszą barierą. Kolejnymi są tunele typu split-tunnel z regułami routingu dla poszczególnych peerów lub konfiguracja generowana przez narzędzia do automatyzacji. W tym momencie ręczna konfiguracja nie jest trudniejsza, jest po prostu inna, a przewodnik po standardowym WireGuard pokazuje ten sam tunel zbudowany przy użyciu wg0.conf. Jeśli wolisz całkowicie zrezygnować z utrzymywania płaszczyzny sterowania, porównanie WireGuard z Tailscale omawia opcję zarządzaną. To, czy jest to opłacalna wymiana, zależy od tego, do czego serwer koordynujący ma dostęp, dlatego warto przeczytać model zaufania Tailscale przed powierzeniem mu swojej sieci. Koszt to zazwyczaj kolejne pytanie, a zakres darmowego planu Tailscale obejmuje do sześciu użytkowników bez opłat, wyłącznie do użytku niekomercyjnego, więc gospodarstwo domowe nie płaci nic, podczas gdy mały zespół biznesowy nie kwalifikuje się do tego planu, niezależnie od liczby osób. Powyżej tego punktu rozliczenia dotyczą użytkowników, a nie urządzeń, co zmienia strukturę kosztów w porównaniu z VPS, za który już płacisz, więc koszty Tailscale po wyjściu poza darmowy plan to liczba, którą należy sprawdzić przed migracją zespołu. Pełny tunel, który właśnie zbudowałeś, ma tam swój bezpośredni odpowiednik, ponieważ udostępnienie VPS jako węzła wyjściowego Tailscale zapewnia tę samą trasę wyjściową przez serwer, zatwierdzaną w konsoli administracyjnej zamiast wpisywaną do konfiguracji każdego klienta. Bariera podsieci również ma swój odpowiednik, ponieważ udostępnienie całej sieci prywatnej z VPS przekazuje tę sieć każdemu urządzeniu w tailnet bez edycji AllowedIPs dla każdego peera, co skłoniło Cię do odejścia od interfejsu graficznego. Jeśli chcesz mieć ten pulpit nawigacyjny i automatyczny routing mesh, ale nie chcesz korzystać z cudzego serwera koordynującego, uruchomienie własnego serwera NetBird na VPS pozwala zachować płaszczyznę sterowania na własnym sprzęcie, kosztem konfiguracji DNS i TLS, o które wg-easy nigdy nie pytało.
Jeśli składnia compose powyżej była mniej znana niż część dotycząca WireGuard, podstawy Docker Compose na VPS wyjaśniają format pliku i codzienne polecenia.
FAQ
Dlaczego wg-easy ignoruje moje zmienne WG_HOST oraz PASSWORD_HASH?
Te zmienne należą do wersji 14 wg-easy. Wersja 15 to całkowite przepisanie kodu, a twórcy przenieśli niemal całą konfigurację do panelu administracyjnego w interfejsie webowym. Kontener nie odczytuje tych zmiennych, więc uruchamia się normalnie, a przy pierwszej wizycie prosi o utworzenie konta administratora. Adres hosta widoczny dla klientów należy ustawić na tej stronie konfiguracyjnej.
Czy potrzebuję SYS_MODULE, jeśli mój kernel ma już wbudowany WireGuard?
Nie. SYS_MODULE oraz montowanie /lib/modules istnieją po to, aby kontener mógł załadować moduł, gdy host go nie posiada. Na hoście, gdzie sudo modprobe wireguard kończy się powodzeniem, ta uprawnienie pozostaje niewykorzystane. Usunięcie go jest rozsądnym krokiem w celu zwiększenia bezpieczeństwa, natomiast NET_ADMIN jest wymagane w obu przypadkach.
Klient łączy się, ale brak dostępu do Internetu. Co jest nie tak?
Nawiązany handshake przy braku ruchu prawie zawsze oznacza problem z przekazywaniem pakietów (forwarding). Potwierdź, że net.ipv4.ip_forward=1 oraz net.ipv4.conf.all.src_valid_mark=1 nadal znajdują się w pliku compose, ponieważ ręcznie edytowane kopie często je tracą. Jeśli forwarding jest włączony, sprawdź serwer DNS, który otrzymał klient. Tunel, który kieruje cały ruch przez VPN, ale wskazuje na serwer DNS nieosiągalny przez to połączenie, w przeglądarce wygląda dokładnie jak zerwane połączenie.
Jak wykonać kopię zapasową klientów?
Wszystkie dane znajdują się w wolumenie nazwanym etc_wireguard. W wersji 15 oznacza to bazę danych SQLite w lokalizacji /etc/wireguard/wg-easy.db wewnątrz kontenera. Dokumentacja v15 nie opisuje żadnej funkcji tworzenia kopii zapasowych ani eksportu, dlatego należy zabezpieczyć sam wolumen: uruchom docker compose down, skopiuj zawartość wolumenu poza serwer, a następnie przywróć stos za pomocą docker compose up -d. Wykonaj to przed każdą aktualizacją. Plik wg0.json, o którym wspominają starsze poradniki, należy do wg-easy 14. Jest to plik eksportowany przez przycisk kopii zapasowej w v14, a w v15 jego jedynym zastosowaniem jest plik wgrywany w kreatorze konfiguracji podczas migracji starej instalacji.
Czy mogę uruchomić wg-easy za reverse proxy?
Tak. Umieść proxy przed portem TCP 51821, wykonaj tam terminację TLS i ustaw INSECURE=true na kontenerze, aby akceptował zwykły ruch HTTP z proxy. Port UDP 51820 pozostaw wystawiony bezpośrednio, ponieważ ruch VPN to UDP i nie przechodzi on przez proxy HTTP.