SSD Nodes Learn
Przewodniki Matt ConnorAutor: Matt Connor · Zaktualizowano 2026-07-24

Jak postawić własny serwer WireGuard VPN

Instrukcja konfiguracji WireGuard na Linux VPS. Dowiedz się, jak poprawnie ustawić wg0.conf, IP forwarding oraz NAT i uniknąć błędów przy handshake.

Cel projektu

Konfiguracja WireGuard VPN na własnym serwerze zajmuje około czterdziestu linii: jeden para kluczy, jeden plik interfejsu, jedna wartość sysctl, jedna reguła NAT oraz jedno zezwolenie w firewallu. Instalacja jest prosta, dlatego większość tego poradnika dotyczy błędów w zakresie uprawnień kluczy, AllowedIPs, przekierowywania oraz DNS.

WireGuard to tunel warstwy 3 (Layer 3) działający w jądrze systemu. Od wersji Linux 5.6 jest częścią głównej linii jądra, więc Ubuntu 24.04 oraz Debian 13 posiadają go natywnie, bez zewnętrznych modułów. Nie występuje negocjacja szyfrów, brak jest urzędu certyfikacji oraz etapu uwierzytelniania loginem/hasłem: peer to klucz publiczny oraz adresy IP, których ten klucz może używać. Pakiet, który nie przejdzie weryfikacji MAC, jest odrzucany bez odpowiedzi, przez co port nie odpowiada na skanowanie. Skutkiem ubocznym jest brak serwera uwierzytelniającego, więc odebranie dostępu wymaga usunięcia peera z urządzenia.

Najpierw sprawdź wirtualizację

WireGuard wymaga jądra systemu, do którego można załadować moduł. W przypadku VPS typu KVM rozwiązanie działa natywnie. W wirtualizacji kontenerowej, która współdzieli jądro hosta — OpenVZ, LXC — pierwsza komenda zwróci błąd RTNETLINK answers: Operation not supported. W takim przypadku stosowana jest implementacja userspace wireguard-go. Najpierw należy zweryfikować typ wirtualizacji za pomocą sudo modprobe wireguard && echo ok.

Generowanie kluczy bez ryzyka ich wycieku

Plik /etc/wireguard/server.key z uprawnieniami do odczytu dla wszystkich użytkowników jest równoznaczny z brakiem zabezpieczeń VPN. Standardowa metoda polegająca na użyciu umask 077 && wg genkey | sudo tee ... jest niewiarygodna, ponieważ sudo stosuje własny umask do pliku tworzonego przez tee. Należy jawnie ustawić tryb dostępu.

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

Parę kluczy dla klienta należy wygenerować w ten sam sposób. wg genpsk umożliwia dodanie opcjonalnego klucza współdzielonego (pre-shared key), który zajmuje jedną linię w każdym pliku konfiguracyjnym.

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 to ostrzeżenie o starcie systemu; komunikat o ogólnodostępności pliku oznacza pominięcie tego kroku. Address to adres serwera wewnątrz tunelu, obejmujący maskę całej podsieci VPN. Należy wybrać zakres, który nie występuje w sieciach zewnętrznych — 192.168.1.0/24 koliduje z połową routerów domowych, za którymi znajdują się klienci, co powoduje ciche przejęcie ruchu przez trasę lokalną.

AllowedIPs rówieśnika (peer) po stronie serwera to /32, czyli jedyny adres tunelu przypisany do klienta. Przypisanie tego samego adresu IP w sekcji allowed IP dwóm różnym rówieśnikom spowoduje, że ruch będzie kierowany do ostatnio skonfigurowanego elementu, a pierwszy przestanie otrzymywać pakiety bez wyświetlenia błędu. Należy pozostawić SaveConfig nieustawione lub wg-quick down nadpisze ten plik na podstawie bieżącego stanu.

Konfiguracja urządzenia jako routera

Serwer Linux odrzuca pakiety, które nie są adresowane do niego. Domyślnie funkcje forwarding oraz source NAT są wyłączone.

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

Surowy sysctl -w działa tylko do następnego restartu, po którym przestaje funkcjonować. NAT wymaga interfejsu egress — karty sieciowej z dostępem do Internetu, a nie wg0. Nie należy zakładać nazwy eth0; należy sprawdzić własną konfigurację za pomocą ip route show default, ponieważ aktualne obrazy używają nazw takich jak enp1s0 lub ens3.

Firewall: port oraz ścieżka przekazywania

Jeden plik nftables obejmuje filtrowanie oraz NAT. Należy zapisać /etc/nftables.conf — powoduje to wyczyszczenie istniejącego zestawu reguł, dlatego należy pominąć ten krok na systemie 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
  }
}

Zastosować za pomocą sudo systemctl enable --now nftables, utrzymując otwartą drugą sesję SSH: policy drop plus błąd w regule SSH skutkuje utratą dostępu do serwera. Należy zwrócić uwagę na to, czego łańcuch forward nie zezwala — wg0 do wg0. Węzły mają dostęp do internetu, a nie do siebie nawzajem; należy dodać iifname "wg0" oifname "wg0" accept dla sieci VPN typu peer-to-peer. Ten sam łańcuch określa, do czego węzeł może uzyskać dostęp na samym serwerze, co jest istotne, gdy maszyna służy jednocześnie jako zdalna stacja deweloperska uruchamiająca Claude Code w tmux i nie należy wystawiać tej części na publiczny dostęp.

Na systemie 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.

Uruchomienie w systemd

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

wg-quick tworzy interfejs, dodaje adresy oraz instaluje trasy pochodzące z AllowedIPs. enable --now stanowi kluczowy element: ręczne wykonanie wg-quick up wg0 zostaje utracone po ponownym uruchomieniu systemu, a aktualizacje jądra wymagają restartu.

Konfiguracja klienta oraz ustawienie, które jest powszechnie błędnie interpretowane

[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 pełni dwie różne funkcje jednocześnie. Mylenie tych funkcji jest główną przyczyną błędów w konfiguracji WireGuard.

W kierunku wychodzącym (outbound) jest to tabela routingu. Pakiet, którego adres docelowy odpowiada parametrowi AllowedIPs danego peer'a, zostaje zaszyfrowany i wysłany do tego peer'a. 0.0.0.0/0, ::/0 przesyła cały ruch przez tunel — tworzy to tunel pełny (full tunnel), gdzie serwer jest trasą domyślną. Tunel dzielony (split tunnel) to węższa lista: AllowedIPs = 10.8.0.0/24, 10.20.0.0/16 obsługuje ruch VPN oraz jedną sieć prywatną znajdującą się za serwerem, natomiast cały pozostały ruch korzysta z lokalnych tras. Ta ograniczona lista pozwala całkowicie odizolować usługi od publicznego internetu — dzięki niej prywatna instancja Nextcloud na VPS przypisana do adresu tunelu lub maszyny wirtualne w labie nested-virtualisation działające na tym samym urządzeniu, pozostają dostępne dla peer'ów i niewidoczne dla wszystkich innych.

W kierunku przychodzącym (inbound) jest to lista kontroli dostępu (ACL). Pakiet odszyfrowany od peer'a, którego adres źródłowy nie znajduje się w AllowedIPs tego peer'a, zostaje odrzucony. Dlatego serwer zawiera wpis 10.8.0.2/32 dla laptopa: wpis 0.0.0.0/0 spowodowałby, że klient mógłby podszywać się pod dowolny adres w tunelu.

PersistentKeepalive służy klientom znajdującym się za NAT, gdzie router utrzymuje mapowanie UDP tylko podczas przesyłania pakietów. Po wygaśnięciu mapowania serwer nie może połączyć się z klientem. PersistentKeepalive = 25 podtrzymuje mapowanie — należy ustawić tę opcję u klienta, 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 bez linii DNS =, klient zachowuje resolver otrzymany z sieci lokalnej — router w kawiarni pod adresem 192.168.1.1. Ta trasa jest bardziej specyficzna niż trasa domyślna, więc zapytania DNS opuszczają łączę lokalne w postaci jawnej (cleartext), podczas gdy cała reszta ruchu jest tunelowana. Ruch jest prywatny; lista nazw nie jest.

Dostępne są dwa poprawne rozwiązania. Skierowanie DNS na publiczny resolver (DNS = 9.9.9.9) sprawi, że zapytania przejdą przez tunel i wyjdą z serwera, choć resolver nadal będzie je widział. Alternatywnie można uruchomić unbound lub dnsmasq przypisane do 10.8.0.1, ustawić DNS = 10.8.0.1 i dodać udp dport 53 iifname "wg0" accept do łańcucha input — po ustawieniu tej linii resolver przestaje działać i żadna nazwa nie zostanie rozwiązana.

W klientach Linux wg-quick stosuje DNS poprzez resolvconf; w przypadku braku tej konfiguracji występuje resolvconf: command not found. Należy zainstalować openresolv lub ustawić PostUp = resolvectl dns %i 10.8.0.1 w kliencie systemd-resolved.

Dodawanie i usuwanie peerów bez przerywania połączenia tunelu

Restart interfejsu w celu dodania użytkownika powoduje rozłączenie wszystkich połączonych klientów. Należy dopisać blok [Peer] do wg0.conf, a następnie przeładować zestaw peerów w miejscu.

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

Polecenie wg-quick strip wyświetla konfigurację bez kluczy wg-quick-only (Address, DNS, PostUp), a syncconf nakłada zmiany bez przerywania aktywnych sesji. Polecenie to aktualizuje wyłącznie peerów: zmiana Address nadal wymaga pełnego restartu interfejsu (down/up). Aby cofnąć zmiany, należy użyć sudo wg set wg0 peer <public key> remove, a następnie usunąć blok z pliku, w przeciwnym razie zostanie on przywrócony podczas kolejnego przeładowania.

Tryby awarii oraz widoczne komunikaty

Handshake nigdy się nie kończy. wg show wskazuje rówieśnika (peer) bez latest handshake, a klient loguje:

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

Brak przychodzących lub brak akceptowanych danych. Należy sprawdzić kolejno: czy port UDP 51820 jest otwarty na firewallu VPS oraz na firewallu sieciowym dostawcy (osobna kontrola w większości paneli); czy adres i port Endpoint są poprawne; czy klucze nie są zamienione. Klucz w bloku [Peer] klienta musi być publicznym kluczem serwera, i odwrotnie — wklejenie klucza prywatnego lub własnego klucza publicznego klienta powoduje dokładnie ten objaw. sudo tcpdump -ni any udp port 51820 na serwerze pokazuje, czy pakiety w ogóle docierają. Moduł jądra domyślnie nie loguje żadnych komunikatów; komunikaty WireGuard pojawiają się w dmesg tylko po włączeniu dynamicznego debugowania (echo module wireguard +p | sudo tee /sys/kernel/debug/dynamic_debug/control). Po włączeniu tej funkcji niezgodność kluczy objawia się jako odrzucenie pakietu z błędem invalid-MAC.

Handshake działa, brak internetu. ping 10.8.0.1 kończy się sukcesem, ale ping 1.1.1.1 wykazuje timeout: brakuje przekierowania (forwarding) lub NAT. Sprawdź, czy sysctl net.ipv4.ip_forward odczytuje 1, a następnie monitoruj liczniki podczas pingowania przez klienta, używając sudo nft list ruleset lub sudo iptables -t nat -L POSTROUTING -n -v. Zero pakietów w regule masquerade oznacza błędną nazwę interfejsu wyjściowego; rosnący licznik bez odpowiedzi wskazuje na politykę łańcucha forward.

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

Niektóre strony HTTPS zawieszają się. SSH i ping działają poprawnie; duże strony przestają się ładować. Przyczyną jest path MTU: tunel dodaje narzut (overhead), a jeden z pośrednich węzłów odrzuca zbyt duże pakiety bez wysłania komunikatu ICMP. Zmniejsz MTU w [Interface] klienta — spróbuj 1420, następnie 1380, a na końcu 1280.

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

Migracja z Streisand lub OpenVPN

Projekt Streisand nie jest już rozwijany, a jego repozytorium zostało zarchiwizowane. Uruchamianie VPN na porzuconej automatyzacji powoduje narastające problemy z bezpieczeństwem. Nie ma możliwości aktualizacji systemu w miejscu. PKI w OpenVPN nie jest kompatybilne z WireGuard: WireGuard nie wykorzystuje certyfikatów, CA ani dat wygaśnięcia, przez co każdy klient otrzymuje nowy klucz parę.

Należy przeprowadzić migrację równoległą — WireGuard na porcie UDP 51820 może działać jednocześnie z OpenVPN na porcie 1194 na tym samym serwerze. Należy zainstalować wg0, przenosić klientów pojedynczo, a następnie wyłączyć starą usługę. Model nazw użytkowników, haseł oraz unieważniania w OpenVPN nie jest przenoszony; jeśli wymagane są konta użytkowników lub ścieżka audytu, należy zaimplementować te funkcje jako warstwę nad WireGuard.

Backups, upgrades, and what strains at scale

/etc/wireguard jest serwerem. Wykonaj kopię zapasową (sudo tar czf wg-backup.tgz -C /etc wireguard, tryb 600, przechowywana poza maszyną), aby umożliwić odbudowę na nowym VPS w kilka minut. Utrata klucza prywatnego serwera wymaga ponownego wystawienia konfiguracji dla każdego klienta, ponieważ klienci weryfikują klucz publiczny serwera. Aktualizacje to standardowy apt upgrade wymagający restartu przy aktualizacji jądra, a wg-quick@wg0 uruchamia się automatycznie, jeśli została włączona ta funkcja.

Stan poszczególnych rówieśników (peers) jest mały, a kryptografia działa w jądrze. Ograniczeniem jest moc procesora VPS oraz limit przepustowości, a nie parametry konfiguracji. Należy mierzyć wydajność za pomocą iperf3 w tunelu, zamiast polegać na podanych wartościach. Przy dużej skali problemem jest obsługa operacyjna. Każdy peer wymaga unikalnego adresu IP w tunelu. Ręczna edycja sześćdziesięciu bloków [Peer] prowadzi do powstawania duplikatów AllowedIPs; należy generować konfiguracje za pomocą skryptu. Jeden serwer to jeden punkt końcowy UDP i jeden punkt awarii. WireGuard nie posiada mechanizmu klastrowania; redundancja wymaga drugiego serwera z własnymi kluczami. Rotacja kluczy pozostaje procesem manualnym, dlatego należy dokumentować posiadaczy kluczy oraz procedurę ich unieważniania.

Wszystkie powyższe wymagania spełnia maszyna Linux pod pełną kontrolą: publiczny adres IP, jądro umożliwiające załadowanie modułu oraz firewall zarządzany w całości przez użytkownika.

FAQ

Dlaczego handshake WireGuard nigdy się nie kończy?

wg show wskazujący peer bez latest handshake oznacza, że pakiety nie docierają lub nie są akceptowane. Należy sprawdzić UDP 51820 na firewallu VPS oraz na oddzielnym firewallu sieciowym dostawcy, potwierdzić host i port Endpoint, a następnie upewnić się, że klucze nie są zamienione — blok [Peer] klienta musi zawierać klucz public serwera. sudo tcpdump -ni any udp port 51820 na serwerze pokazuje, czy pakiety w ogóle docierają; dmesg raportuje błędy handshake WireGuard dopiero po włączeniu dynamicznego debugowania (echo module wireguard +p | sudo tee /sys/kernel/debug/dynamic_debug/control), a błąd niedopasowania kluczy objawia się jako odrzucenie invalid-MAC.

Tunel jest połączony, ale nie mam internetu. Czego brakuje?

Działanie ping 10.8.0.1 przy jednoczesnym timeoutie ping 1.1.1.1 wskazuje na problemy z forwardingiem lub NAT. Należy potwierdzić, że sysctl net.ipv4.ip_forward odczytuje 1 oraz że jest to ustawione w /etc/sysctl.d/, a nie tylko za pomocą sysctl -w, który znika po restarcie. Następnie należy sprawdzić nazwy reguł masquerade dla rzeczywistego interfejsu wyjściowego z ip route show defaultenp1s0 lub ens3, rzadziej eth0.

Czy muszę dodać linię DNS = w konfiguracji klienta?

Przy pełnym tunelu bez linii DNS = klient zachowuje resolver pobrany z sieci lokalnej. Zapytania te są wysyłane tekstem jawnym przez łącze lokalne, podczas gdy cała reszta ruchu trafia do tunelu. Należy skierować DNS na publiczny resolver lub uruchomić unbound/dnsmasq przypisane do 10.8.0.1 i otworzyć udp dport 53 iifname "wg0" w łańcuchu input.

Co dokładnie kontroluje AllowedIPs?

Funkcja ta pełni dwie role. W ruchu wychodzącym działa jako tabela routingu: ruch pasujący do AllowedIPs danego peer jest szyfrowany i wysyłany do tego peer. W ruchu przychodzącym działa jako lista kontroli dostępu: odszyfrowany pakiet, którego źródło znajduje się poza AllowedIPs danego peer, jest odrzucany. Dlatego strona serwera wymienia /32 na klienta, podczas gdy strona klienta może wymieniać 0.0.0.0/0.

Czy WireGuard zadziała na każdym VPS?

Na VPS typu KVM działa on z modułem 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 userspace wireguard-go. Przed innymi należy uruchomić sudo modprobe wireguard && echo ok.

#wireguard#vpn#linux-networking#nftables#systemd#self-hosting