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

Jak skonfigurować własny serwer VPN WireGuard na VPS

Instrukcja konfiguracji WireGuard na Linux VPS. Dowiedz się jak poprawnie wygenerować klucze, ustawić wg0.conf, włączyć IP forwarding oraz uniknąć błędów handshake i problemów z DNS.

Co budujesz

Własny serwer VPN WireGuard wymaga około czterdziestu linii konfiguracji: jednej pary kluczy, jednego pliku interfejsu, jednego wpisu sysctl, jednej reguły NAT oraz otwarcia portu w zaporze sieciowej. Instalacja jest trywialna, dlatego większość tego przewodnika poświęcono kwestiom awarii, uprawnieniom do kluczy, AllowedIPs, przekazywaniu pakietów oraz DNS.

WireGuard to tunel warstwy 3 działający w jądrze systemu, obecny w głównej linii kodu od wersji Linux 5.6, dzięki czemu Ubuntu 24.04 i Debian 13 dostarczają go bez konieczności instalacji zewnętrznych modułów. Brak negocjacji szyfrów, urzędów certyfikacji oraz etapów logowania hasłem: peer jest definiowany przez klucz publiczny oraz adresy IP, z których może korzystać. Pakiet, który nie przejdzie weryfikacji MAC, jest odrzucany bez odpowiedzi, co sprawia, że port nie reaguje na skanowanie. Druga strona medalu: nie istnieje serwer autoryzacji, więc odebranie dostępu wymaga usunięcia peera z serwera.

Sprawdź najpierw wirtualizację

WireGuard wymaga jądra, do którego można załadować moduł; na VPS z KVM działa to bez dodatkowej konfiguracji. W przypadku wirtualizacji kontenerowej współdzielącej jądro hosta, takiej jak OpenVZ czy LXC, pierwsze polecenie kończy się błędem RTNETLINK answers: Operation not supported, a rozwiązaniem zastępczym jest implementacja przestrzeni użytkownika wireguard-go. Sprawdź to najpierw za pomocą sudo modprobe wireguard && echo ok.

Generowanie kluczy bez ryzyka ich ujawnienia

Plik /etc/wireguard/server.key dostępny do odczytu dla wszystkich jest równoznaczny z brakiem VPN. Typowa linia umask 077 && wg genkey | sudo tee ... działa poprawnie: o ile sudoers nie ustawia umask_override, co jest domyślnie wyłączone, sudo łączy umask użytkownika z własną wartością domyślną 0022 i nigdy jej nie obniża, więc plik tworzony przez tee ma uprawnienia 600. Do wycieku kluczy dochodzi w przypadku uruchomienia potoku bez umask 077, przy standardowym umask 022 lub na systemie, w którym sudoers ma włączone umask_override. Ustawienie uprawnień w sposób jawny eliminuje to ryzyko.

sudo apt update && sudo apt install -y wireguard nftables
sudo install -d -m 700 /etc/wireguard
sudo sh -c 'umask 077; wg genkey > /etc/wireguard/server.key'
sudo sh -c 'wg pubkey < /etc/wireguard/server.key > /etc/wireguard/server.pub'
sudo chmod 600 /etc/wireguard/server.key

Wygeneruj parę kluczy klienta w ten sam sposób. wg genpsk dodaje opcjonalny klucz współdzielony (pre-shared key), który należy umieścić w jednej linii w każdej z konfiguracji.

Interfejs serwera: /etc/wireguard/wg0.conf

[Interface]
Address = 10.8.0.1/24
ListenPort = 51820
PrivateKey = <contents of /etc/wireguard/server.key>

[Peer]
PublicKey = <laptop public key>
PresharedKey = <psk, optional>
AllowedIPs = 10.8.0.2/32

chmod 600 go; ostrzeżenie przy starcie informujące, że plik jest dostępny dla wszystkich, oznacza pominięcie tego kroku. Address to adres serwera wewnątrz tunelu, zawierający maskę całej podsieci VPN. Należy wybrać zakres, który nie występuje w użyciu; 192.168.1.0/24 koliduje z połową domowych routerów, za którymi znajdują się klienci, co powoduje cichą utratę łączności tunelowej na rzecz trasy lokalnej.

AllowedIPs peera po stronie serwera to /32, czyli jeden adres tunelu przypisany do danego klienta. Przypisanie dwóm peerom tego samego adresu AllowedIPs spowoduje, że ruch będzie kierowany do tego, który został skonfigurowany jako ostatni, a pierwszy przestanie otrzymywać pakiety bez żadnego komunikatu o błędzie. Pozostaw SaveConfig nieustawione, w przeciwnym razie wg-quick down nadpisze ten plik na podstawie bieżącego stanu.

Przekształcenie serwera w router

Serwer z systemem Linux domyślnie odrzuca pakiety, które nie są do niego zaadresowane. Domyślnie wyłączone jest zarówno przekazywanie pakietów (forwarding), jak i mechanizm source NAT.

printf 'net.ipv4.ip_forward = 1\nnet.ipv6.conf.all.forwarding = 1\n' \
  | sudo tee /etc/sysctl.d/99-wireguard.conf
sudo sysctl --system
sysctl net.ipv4.ip_forward

Samo polecenie sysctl -w działa tylko do następnego restartu, po czym przestaje funkcjonować. Mechanizm NAT wymaga wskazania interfejsu wyjściowego (egress), czyli karty sieciowej zapewniającej dostęp do Internetu, a nie wg0. Nie należy zakładać użycia eth0; nazwę interfejsu należy pobrać z ip route show default, ponieważ współczesne obrazy systemów używają nazw takich jak enp1s0 lub ens3.

Firewall: port i ścieżka przekierowania

Jeden plik nftables obsługuje zarówno filtry, jak i NAT. Zapisanie /etc/nftables.conf powoduje wyczyszczenie istniejącego zestawu reguł, więc pomiń ten krok na serwerze zarządzanym już przez ufw lub Docker.

#!/usr/sbin/nft -f
flush ruleset

table inet filter {
  chain input {
    type filter hook input priority filter; policy drop;
    ct state established,related accept
    iif lo accept
    tcp dport 22 accept
    udp dport 51820 accept
  }
  chain forward {
    type filter hook forward priority filter; policy drop;
    ct state established,related accept
    iifname "wg0" oifname "enp1s0" accept
  }
}

table ip nat {
  chain postrouting {
    type nat hook postrouting priority srcnat; policy accept;
    ip saddr 10.8.0.0/24 oifname "enp1s0" masquerade
  }
}

Zastosuj konfigurację za pomocą sudo systemctl enable --now nftables, utrzymując otwartą drugą sesję SSH: policy drop wraz z literówką w regule SSH spowoduje utratę dostępu do serwera. Zwróć uwagę, czego łańcuch forward nie zezwala, wg0 do wg0. Węzły uzyskują dostęp do Internetu, ale nie do siebie nawzajem; dodaj iifname "wg0" oifname "wg0" accept dla VPN typu peer-to-peer. To, do czego węzeł może uzyskać dostęp na samym serwerze, nie jest zadaniem tego łańcucha: pakiety zaadresowane do serwera przechodzą przez hook input, a nie forward, więc decyduje o tym łańcuch input. W obecnej formie pozwala on węzłowi na dostęp tylko do SSH i niczego więcej, nawet nie do pingowania 10.8.0.1. Dodaj tam iifname "wg0" icmp type echo-request accept oraz jedną regułę iifname "wg0" dla każdej usługi, która ma być dostępna tylko przez tunel. Jest to istotne, gdy serwer pełni jednocześnie rolę zdalnego środowiska programistycznego z uruchomionym Claude Code w tmux, a nie chcesz wystawiać tej części publicznie.

Na serwerze z ufw: ufw allow 51820/udp, DEFAULT_FORWARD_POLICY="ACCEPT" w /etc/default/ufw oraz reguła *nat POSTROUTING MASQUERADE na początku /etc/ufw/before.rules.

Uruchamianie za pomocą systemd

sudo systemctl enable --now wg-quick@wg0
sudo wg show

wg-quick tworzy interfejs, dodaje adresy oraz instaluje trasy wywiedzione z AllowedIPs. enable --now stanowi istotną część: ręcznie uruchomione wg-quick up wg0 znika po kolejnym restarcie, a aktualizacje jądra wymuszają restarty. Jednostka, która nie uruchomi się ponownie po takim restarcie, pozostaje w stanie uśpienia, dopóki ktoś nie spróbuje nawiązać połączenia. Dlatego plik typu drop-in OnFailure= w wg-quick@wg0, wskazujący na własny serwer ntfy, jest najtańszym sposobem na otrzymanie powiadomienia na telefon, zamiast dowiadywać się o awarii od zablokowanego użytkownika.

Konfiguracja klienta i ustawienie, w którym wszyscy popełniają błędy

[Interface]
PrivateKey = <laptop private key>
Address = 10.8.0.2/32
DNS = 10.8.0.1

[Peer]
PublicKey = <server public key>
Endpoint = vpn.example.com:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25

AllowedIPs wykonuje jednocześnie dwa różne zadania, a ich mylenie jest źródłem większości problemów z WireGuard.

W ruchu wychodzącym jest to tablica routingu. Pakiet, którego miejsce docelowe pasuje do AllowedIPs peer-a, jest szyfrowany i wysyłany do tego peer-a. 0.0.0.0/0, ::/0 kieruje wszystko przez tunel, tworząc pełny tunel, w którym serwer staje się domyślną bramą. Tunel dzielony (split tunnel) to węższa lista: AllowedIPs = 10.8.0.0/24, 10.20.0.0/16 przesyła ruch VPN oraz ruch do jednej sieci prywatnej za serwerem, podczas gdy cały pozostały ruch zachowuje swoje lokalne trasy. Ta ograniczona lista pozwala całkowicie odizolować usługi od publicznego Internetu, dzięki czemu prywatna instancja Nextcloud na VPS powiązana z adresem tunelu lub maszyny wirtualne w laboratorium z zagnieżdżoną wirtualizacją działające na tym samym hoście pozostają dostępne dla peer-ów i niewidoczne dla nikogo innego.

W ruchu przychodzącym jest to lista kontroli dostępu (ACL). Odszyfrowany pakiet od peer-a, którego adres źródłowy nie znajduje się w AllowedIPs tego peer-a, jest odrzucany. Dlatego serwer zawiera 10.8.0.2/32 dla laptopa: wpis 0.0.0.0/0 w tym miejscu pozwoliłby klientowi na podszycie się pod dowolny adres wewnątrz tunelu.

PersistentKeepalive służy klientom znajdującym się za NAT, gdzie router utrzymuje mapowanie UDP tylko podczas przepływu pakietów. Po wygaśnięciu mapowania serwer nie może już nawiązać połączenia z klientem. PersistentKeepalive = 25 utrzymuje mapowanie otwarte; należy ustawić tę opcję na kliencie, a nie na serwerze z publicznym adresem IP.

DNS i wyciek, którego nikt nie zauważa

Przy użyciu AllowedIPs = 0.0.0.0/0 i braku linii DNS =, klient zachowuje resolver uzyskany z sieci lokalnej, czyli routera w kawiarni pod adresem 192.168.1.1. Ta trasa jest bardziej szczegółowa niż trasa domyślna, więc zapytania DNS opuszczają urządzenie przez łącze lokalne w postaci otwartego tekstu, podczas gdy cały pozostały ruch jest tunelowany. Ruch jest prywatny, ale lista nazw – nie.

Istnieją dwie uczciwe opcje. Skieruj DNS na publiczny resolver (DNS = 9.9.9.9), a zapytania przejdą przez tunel i wyjdą z serwera, choć resolver nadal będzie je widział. Alternatywnie uruchom unbound lub dnsmasq powiązane z 10.8.0.1, ustaw DNS = 10.8.0.1 i dodaj udp dport 53 iifname "wg0" accept do łańcucha wejściowego (input chain), ustaw tę linię i zapomnij o resolverze – wtedy nic nie zostanie rozwiązane.

Na klientach Linux wg-quick stosuje DNS poprzez resolvconf; jeśli jej brakuje, otrzymasz resolvconf: command not found. Zainstaluj openresolv lub ustaw PostUp = resolvectl dns %i 10.8.0.1 na kliencie korzystającym z systemd-resolved.

Dodawanie i usuwanie partnerów (peers) bez przerywania tunelu

Restartowanie interfejsu w celu dodania użytkownika rozłącza wszystkich aktualnie połączonych. Należy dopisać blok [Peer] do pliku wg0.conf, a następnie przeładować zestaw partnerów w działającym tunelu.

sudo bash -c 'wg syncconf wg0 <(wg-quick strip wg0)'

Polecenie wg-quick strip wyświetla konfigurację bez kluczy specyficznych dla wg-quick (Address, DNS, PostUp), a syncconf nanosi różnice bez przerywania aktywnych sesji. Aktualizowane są wyłącznie dane partnerów: zmiana Address nadal wymaga pełnego cyklu down/up. Aby cofnąć dostęp, należy użyć sudo wg set wg0 peer <public key> remove, a następnie usunąć odpowiedni blok z pliku, w przeciwnym razie powróci on przy następnym przeładowaniu.

Tryby awarii i towarzyszące im komunikaty

Uścisk dłoni (handshake) nigdy nie kończy się. wg show wyświetla partnera bez latest handshake, a klient loguje:

Handshake for peer 1 (10.0.0.10:51820) did not complete after 5 seconds, retrying (try 2)

Dane nie docierają lub nie są akceptowane. W kolejności: czy port UDP 51820 jest otwarty na firewallu VPS oraz na firewallu sieciowym dostawcy (osobne ustawienie w większości paneli); czy adres Endpoint i port są poprawne; czy klucze nie zostały pomylone. Klucz w bloku [Peer] klienta musi być kluczem publicznym serwera, i odwrotnie. Wklejenie klucza prywatnego lub własnego klucza publicznego klienta skutkuje dokładnie tym objawem. sudo tcpdump -ni any udp port 51820 na serwerze pokazuje, czy pakiety w ogóle docierają. Moduł jądra domyślnie nie loguje niczego; komunikaty WireGuard pojawiają się w dmesg dopiero po włączeniu dynamicznego debugowania (echo module wireguard +p | sudo tee /sys/kernel/debug/dynamic_debug/control). Przy włączonym debugowaniu niedopasowanie kluczy objawia się jako odrzucenie z powodu nieprawidłowego MAC.

Uścisk dłoni działa, brak dostępu do Internetu. ping 10.8.0.1 kończy się powodzeniem, ale ping 1.1.1.1 przekracza czas oczekiwania: brakuje przekazywania (forwarding) lub NAT. Sprawdź, czy sysctl net.ipv4.ip_forward ma wartość 1, a następnie obserwuj liczniki podczas pingowania przez klienta za pomocą sudo nft list ruleset lub sudo iptables -t nat -L POSTROUTING -n -v. Zerowe liczniki na regule maskarady oznaczają błędną nazwę interfejsu wyjściowego; rosnący licznik przy braku odpowiedzi wskazuje na politykę łańcucha forward.

Internet działa, nazwy nie są rozwiązywane. ping 1.1.1.1 kończy się powodzeniem, a curl https://example.com zwraca Could not resolve host. Brakuje linii DNS lub wskazuje ona na resolver nieosiągalny z wnętrza tunelu.

Niektóre strony HTTPS zawieszają się. SSH i ping działają poprawnie; duże strony wczytują się w nieskończoność. Przyczyną jest path MTU: tunel dodaje narzut, a któryś z węzłów pośrednich odrzuca zbyt duże pakiety bez wysłania komunikatu ICMP. Ustaw MTU w sekcji [Interface] klienta. Na standardowym łączu 1500 bajtów wg-quick automatycznie wybiera 1420 (1500 minus 80 bajtów narzutu), więc wpisanie 1420 nic nie zmienia: wypróbuj 1380, a następnie 1280. Jeśli obniżenie MTU rozwiązuje problem zawieszania, ale przepustowość nadal jest niezadowalająca, przestań zgadywać zaokrąglone wartości i wykonaj procedurę znalezienia rzeczywistego path MTU metodą bisekcji i ograniczenia TCP MSS, co pozwoli również wykluczyć przyczyny niezwiązane z tunelem.

Interfejs odmawia uruchomienia. Address already in use oznacza, że inny proces zajmuje port UDP 51820. Cannot find device wg0 po nieudanym up zazwyczaj oznacza odrzucenie konfiguracji; sprawdź journalctl -u wg-quick@wg0 -n 50.

Migracja ze Streisand lub OpenVPN

Projekt Streisand nie jest już utrzymywany, a jego repozytorium zostało zarchiwizowane. Uruchamianie VPN przy użyciu porzuconych narzędzi automatyzacji stanowi narastający problem bezpieczeństwa. Nie istnieje ścieżka aktualizacji w miejscu, a infrastruktura klucza publicznego (PKI) OpenVPN nie podlega konwersji: WireGuard nie korzysta z certyfikatów, urzędów certyfikacji (CA) ani dat wygaśnięcia, dlatego każdy klient otrzymuje nową parę kluczy.

Migrację należy przeprowadzić równolegle; WireGuard działający na UDP 51820 może współistnieć na tym samym serwerze z OpenVPN na porcie 1194. Należy uruchomić wg0, przenosić klientów pojedynczo, a następnie zatrzymać starą usługę. Model uwierzytelniania użytkowników i haseł oraz mechanizm unieważniania certyfikatów z OpenVPN nie mają zastosowania w WireGuard; jeśli wymagane są konta lub ścieżka audytu, należy zaimplementować te funkcje w warstwie powyżej WireGuard.

Kopie zapasowe, aktualizacje i ograniczenia skalowalności

/etc/wireguard to cały serwer. Wykonaj kopię zapasową (sudo tar czf wg-backup.tgz -C /etc wireguard, uprawnienia 600, przechowywana poza serwerem), a w razie awarii odbudujesz go na nowym VPS w kilka minut. Utrata klucza prywatnego serwera wymusza ponowne wydanie wszystkich konfiguracji klientów, ponieważ klienci weryfikują klucz publiczny serwera. Aktualizacje to standardowe apt upgrade oraz restart w przypadku aktualizacji jądra, a wg-quick@wg0 uruchomi się ponownie automatycznie, jeśli zostało włączone.

Stan pojedynczego węzła jest niewielki, a kryptografia działa w jądrze, więc ograniczeniem jest wydajność CPU i przepustowość łącza Twojego VPS, a nie sama konfiguracja. Zmierz to za pomocą iperf3 przez tunel, zamiast polegać na deklarowanych wartościach. Problemem przy skalowaniu jest obsługa operacyjna. Każdy węzeł wymaga unikalnego adresu IP w tunelu, a ręczna edycja sześćdziesięciu bloków [Peer] prowadzi do powstawania duplikatów AllowedIPs: generuj konfiguracje za pomocą skryptu. Jeden serwer to jeden punkt końcowy UDP i jeden punkt awarii, a WireGuard nie posiada klastrowania: redundancja oznacza drugi serwer z własnymi kluczami. Rotacja kluczy pozostaje ręczna, więc prowadź ewidencję, kto posiada dany klucz i jak go unieważnić. Gdy ewidencja przestaje mieścić się w pliku tekstowym, rozwiązaniem jest warstwa sterowania (control plane) nad tą samą płaszczyzną danych jądra. Własny serwer NetBird zarządza przydzielaniem adresów, dystrybucją węzłów i kluczami instalacyjnymi, które w przeciwnym razie obsługiwałbyś ręcznie. Jeśli uruchomienie własnej warstwy sterowania to zbyt wiele, Tailscale oferuje hosting, a darmowy plan Personal obejmuje sześciu użytkowników z nieograniczoną liczbą urządzeń i, na start, do 50 otagowanych zasobów, takich jak serwery, co wystarcza dla większości prywatnych wdrożeń. Powyżej tego limitu opłaty zależą od liczby osób, a nie maszyn, więc koszty dla gospodarstwa domowego lub małego zespołu zależą od liczby użytkowników, a nie od liczby węzłów, które musiałbyś ręcznie wpisywać do wg0.conf. W tym modelu ewidencja split-tunnel AllowedIPs staje się ogłaszaniem prywatnych podsieci z poziomu routera, co jest konfigurowane raz na jednym VPS i zatwierdzane centralnie, zamiast wklejania danych do każdego pliku klienta. Decyzja o tym, czy warto, zależy od tego, do czego hostowana warstwa sterowania ma dostęp, a nigdy nie posiada ona kluczy szyfrujących Twój ruch, choć decyduje, które węzły dowiadują się o swoim istnieniu.

Wszystko to wymaga maszyny z systemem Linux pod Twoją kontrolą, publicznego adresu IP, jądra z możliwością ładowania modułów oraz firewalla, którym zarządzasz w całości.

FAQ

Dlaczego uzgadnianie połączenia WireGuard nigdy się nie kończy?

wg show wyświetlenie peera bez latest handshake oznacza, że pakiety nie docierają lub nie są akceptowane. Sprawdź port UDP 51820 na firewallu VPS oraz na zewnętrznym firewallu sieciowym dostawcy, potwierdź Endpoint hosta i portu, a następnie sprawdź, czy klucze nie zostały zamienione – blok [Peer] klienta musi zawierać publiczny klucz serwera. sudo tcpdump -ni any udp port 51820 na serwerze pokazuje, czy pakiety w ogóle docierają; dmesg raportuje błędy uzgadniania WireGuard tylko po włączeniu dynamicznego debugowania (echo module wireguard +p | sudo tee /sys/kernel/debug/dynamic_debug/control), wtedy niedopasowanie kluczy pojawi się jako odrzucenie z powodu invalid-MAC.

Tunel łączy się, ale nie mam dostępu do Internetu. Czego brakuje?

ping 10.8.0.1 działające przy jednoczesnym przekroczeniu czasu oczekiwania ping 1.1.1.1 wskazuje na problemy z forwardingiem lub NAT. Potwierdź, że sysctl net.ipv4.ip_forward zawiera 1 i że ustawienie to jest zapisane w /etc/sysctl.d/, a nie tylko wprowadzone przez sysctl -w, które znika po restarcie. Następnie sprawdź regułę maskarady, wskazując właściwy interfejs wyjściowy z ip route show default, enp1s0 lub ens3, rzadziej eth0.

Czy potrzebuję linii DNS = w konfiguracji klienta?

Przy pełnym tunelu bez linii DNS = klient zachowuje resolver poznany z sieci lokalnej, a zapytania te wychodzą otwartym tekstem przez łącze lokalne, podczas gdy cała reszta ruchu jest tunelowana. Skieruj DNS na publiczny resolver lub uruchom unbound/dnsmasq powiązane z 10.8.0.1 i otwórz udp dport 53 iifname "wg0" w łańcuchu input.

Co tak naprawdę kontroluje AllowedIPs?

Pełni dwie funkcje. W ruchu wychodzącym jest to tablica routingu: ruch pasujący do AllowedIPs peera jest szyfrowany i wysyłany do tego peera. W ruchu przychodzącym jest to lista kontroli dostępu: odszyfrowany pakiet, którego źródło znajduje się poza AllowedIPs danego peera, jest odrzucany. Dlatego po stronie serwera wymienia się /32 dla każdego klienta, podczas gdy po stronie klienta można użyć 0.0.0.0/0.

Czy WireGuard zadziała na każdym VPS?

Na VPS typu KVM działa z modułem wewnątrz jądra bez dodatkowej konfiguracji. W wirtualizacji kontenerowej współdzielącej jądro hosta, takiej jak OpenVZ lub LXC, modprobe wireguard kończy się błędem Operation not supported, a rozwiązaniem zastępczym jest implementacja przestrzeni użytkownika wireguard-go. Uruchom sudo modprobe wireguard && echo ok przed wykonaniem jakichkolwiek innych czynności.