SSD Nodes Learn Hosting plans →
Przewodniki Matt ConnorAutor: Matt Connor

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.yml

Zawartość 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::/64

etc_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 -f

Używaj docker compose up oraz docker compose down, a nie start i stop. Polecenie start jedynie restartuje istniejące kontenery, więc wszelkie zmiany w pliku compose, w tym ustawienia sieciowe i sysctl, nie zostaną zastosowane, podczas gdy up odtwarza zmienione elementy. Jeśli chcesz, aby stos powracał po restarcie systemu, restart: unless-stopped już to zapewnia, a zachowanie usług compose po starcie systemu wyjaśnia, co ta polityka gwarantuje, a czego nie.

Interfejs WWW nasłuchuje na porcie TCP 51821. Przy pierwszej wizycie wyświetlana jest strona konfiguracji, na której tworzy się konto administratora i potwierdza adres hosta, którego klienci będą używać do połączenia z serwerem. Ten adres hosta trafia do linii Endpoint w konfiguracji każdego klienta, więc musi to być publiczny adres IP lub nazwa DNS serwera VPS. Jeśli jest błędny, kod QR przekazany na telefon wskazuje nieosiągalny punkt i uzgadnianie połączenia nigdy nie zostanie zakończone.

Jeszcze jedna uwaga dotycząca tego portu: wg-easy nie obsługuje samodzielnie TLS i nie odrzuca zwykłego HTTP. Przez http:// interfejs nadal się ładuje, wyświetlając baner ostrzegawczy. Domyślne ustawienie zmienia jedynie ciasteczko sesji, które posiada flagę Secure, więc przeglądarka łącząca się z interfejsem przez zwykłe HTTP na publicznym adresie odrzuca je, co uniemożliwia zalogowanie. INSECURE=true usuwa tę flagę i nie wykonuje żadnych innych operacji. Umieszczenie przed usługą reverse proxy z terminacją TLS, nawet z certyfikatem samopodpisanym, nie wymaga żadnych zmian w konfiguracji wg-easy.

Nie wystawiaj portu interfejsu WWW do Internetu

Plik compose publikuje port 51821 na wszystkich interfejsach. Jest to strona logowania do systemu, który może trasować ruch sieciowy, więc nie powinna być dostępna dla całego świata. Publikacja portu w Dockerze tworzy reguły w łańcuchu DOCKER, który jest sprawdzany przed ufw, więc reguła deny w ufw go nie zablokuje. To pułapka, którą warto zrozumieć, a dlaczego publikowane porty Dockera ignorują ufw wyjaśnia to zagadnienie w pełni.

Prostym rozwiązaniem jest powiązanie interfejsu z adresem loopback i dostęp do niego przez tunel SSH:

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

Następnie z poziomu laptopa:

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

Otwórz http://127.0.0.1:51821 w przeglądarce na laptopie. Ruch jest szyfrowany przez SSH, a port nie odpowiada nikomu innemu. INSECURE=true jedynie usuwa flagę Secure z ciasteczka sesji, dzięki czemu przeglądarka utrzymuje logowanie na tym adresie HTTP, co jest bezpieczne, ponieważ ruch HTTP 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 51820

Powinieneś 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 oprogramowania, a autorzy przenieśli niemal całą konfigurację do panelu administratora w interfejsie webowym. Kontener nie odczytuje żadnej z tych zmiennych, więc uruchamia się standardowo, 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 polecenie sudo modprobe wireguard kończy się powodzeniem, ta uprawnienie pozostaje niewykorzystane. Usunięcie go jest rozsądnym krokiem w celu zwiększenia bezpieczeństwa, a NET_ADMIN jest wymagane w obu przypadkach.

Klient łączy się, ale brak dostępu do Internetu. Co jest nie tak?

Uścisk dłoni (handshake) bez przesyłu danych prawie zawsze oznacza problem z przekazywaniem pakietów (forwarding). Należy potwierdzić, ż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 przekazywanie jest włączone, należy sprawdzić serwer DNS, który otrzymał klient. Tunel, który kieruje cały ruch przez VPN, ale wskazuje na serwer DNS, do którego nie ma już dostępu, wygląda w przeglądarce 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 wersji 15 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. Należy to wykonać 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 wersji 14, a w wersji 15 służy jedynie jako plik przesyłany w kreatorze konfiguracji podczas migracji starej instalacji.

Czy mogę uruchomić wg-easy za reverse proxy?

Tak. Należy umieścić proxy przed portem interfejsu webowego, dokonać tam terminacji TLS i pozostawić INSECURE nieustawione. Kontener akceptuje zwykły ruch HTTP z proxy, a oficjalne przykłady dla Caddy i Traefik nie ustawiają żadnej zmiennej INSECURE. Ponieważ przeglądarka komunikuje się przez HTTPS z proxy, pożądane jest, aby ciasteczko sesji zachowało flagę Secure, którą usuwa jedynie INSECURE=true. Port UDP 51820 należy publikować bezpośrednio, ponieważ ruch VPN to UDP i nie przechodzi on przez proxy HTTP.

Czy ten artykuł był przydatny?