Konfiguracja WireGuard VPN na własnym serwerze
Od pustego serwera do działającego tunelu WireGuard: pary kluczy, NAT z nftables, wg-quick jako usługa, kod QR na telefon i trzy obrazy błędów, na których naprawdę się to zacina.
Co jest takie samo we wszystkich czterech dystrybucjach, a co nie
WireGuard siedzi na stałe w jądrze Linuksa od wersji 5.6. Na Debianie 13, Debianie 12, Ubuntu 24.04 i Ubuntu 22.04 nie musisz więc budować modułu DKMS, podpinać repozytorium backportów ani importować obcego klucza. Wszystkie cztery dostarczają przy tym ten sam stan narzędzi przestrzeni użytkownika (wersja upstream 1.0.20210914), czyli polecenia wg i wg-quick zachowują się wszędzie identycznie.
Różnice leżą wyłącznie w otoczeniu i właśnie tam wykłada się większość poradników:
- Filtr pakietów:
wireguard-toolspoleca nftables lub iptables. Ponieważ apt bierze pierwszą dostępną alternatywę, na odchudzonej instalacji Debiana ląduje nftables, a nie iptables. Przepisywane wszędzie wierszeiptables -t nat -A POSTROUTINGtrafiają tam w próżnię, dopóki nie doinstalujesz tego pakietu. - Przekazywanie DNS: dla wiersza
DNS =programwg-quickuparcie wywołujeresolvconf. Na Ubuntu pakietsystemd-resolvedprzynosi nakładkę zgodności, a na minimalnej instalacji Debianaresolvconfczęsto w ogóle nie istnieje. Przy doinstalowaniu właściwy pakiet nazywa się na Debianie oraz na Ubuntu 22.04openresolv, a na Ubuntu 24.04 tego pakietu już nie ma. Dotyczy to tylko klientów linuksowych, nie telefonów. - Nakładka na firewalla: obrazy Ubuntu często przynoszą aktywne ufw, Debian z reguły nie. ufw domyślnie blokuje przekazywanie pakietów, nawet jeśli
net.ipv4.ip_forwardjest ustawione na 1.
Sprawdź najpierw, z czym masz do czynienia:
apt-get update
apt-get install -y wireguard wireguard-tools nftables qrencode
wg --version
apt-cache policy wireguard-tools
Jeśli wg --version zgłasza numer wersji, przestrzeń użytkownika jest na miejscu. Czy kernel to obsłuży, zobaczysz dopiero przy pierwszym wg-quick up. Jeśli pojawi się przy tym komunikat RTNETLINK answers: Operation not supported albo Unable to access interface: Protocol not supported, pracuje kernel bez obsługi WireGuard, zwykle bardzo stary albo mocno okrojony kernel kontenerowy.
Generowanie par kluczy bez robienia sobie kłopotów
WireGuard nie zna nazw użytkowników ani certyfikatów. Na każdego uczestnika przypada dokładnie jedna para kluczy, do tego opcjonalnie wspólny klucz preshared jako dodatkowa warstwa symetryczna. Wygeneruj jedno i drugie z ustawioną umask, inaczej klucze prywatne będą leżeć czytelne dla wszystkich:
umask 077
wg genkey | tee /etc/wireguard/server.key | wg pubkey > /etc/wireguard/server.pub
wg genkey | tee /etc/wireguard/telefon.key | wg pubkey > /etc/wireguard/telefon.pub
wg genpsk > /etc/wireguard/telefon.psk
ls -l /etc/wireguard
Katalogu /etc/wireguard nie musisz zakładać samodzielnie, pakiet wireguard-tools przynosi go już z trybem 0700. Ważniejsza jest pewna właściwość umask: wartość obowiązuje tylko w tej sesji shella, w której ją ustawisz. Kto generuje klucze w dwóch turach, na przykład dlatego, że w międzyczasie zerwało połączenie i trzeba było zalogować się od nowa, pracuje potem znowu z maską domyślną, a server.key oraz telefon.psk lądują na dysku z prawami 0644. Na koniec ustaw więc uprawnienia jeszcze raz wyraźnie:
chmod 600 /etc/wireguard/*.key /etc/wireguard/*.psk
Trzy błędy zdarzają się tu w kółko:
- Zamieniony klucz publiczny z prywatnym. Oba są 44-znakowymi ciągami Base64 i wyglądają identycznie. Jeśli w
[Interface] PrivateKeyprzez pomyłkę stoi klucz publiczny, tunel i tak wystartuje, ale handshake nigdy nie dojdzie do skutku. Sprawdzić to można w każdej chwili:wg pubkey < /etc/wireguard/server.keymusi dać dokładnie zawartość plikuserver.pub. - Skopiowany razem znak końca wiersza. Klucze skopiowane myszą z terminala chętnie ciągną za sobą spacje.
wgkwituje to komunikatemKey is not the correct length or format. - Uprawnienia plików. Jeśli zapomnisz o
umask 077,wg-quickostrzeże przy starcie komunikatemWarning: `/etc/wireguard/wg0.conf' is world accessible. To nie jest kosmetyka, w tym pliku klucz prywatny stoi otwartym tekstem.
Konfiguracja serwera
Ustal najpierw nazwę swojego interfejsu internetowego. Na maszynach wirtualnych nazywa się on w zależności od obrazu eth0, ens3 albo enp1s0:
ip route show default
Następnie załóż /etc/wireguard/wg0.conf. Sieć tunelu powinna być taka, na jaką w podróży nie natkniesz się w hotelowym Wi-Fi, czyli raczej nie 192.168.0.0/24 ani 192.168.1.0/24:
[Interface]
Address = 10.8.0.1/24
ListenPort = 51820
PrivateKey = ZAWARTOSC_SERVER_KEY
PostUp = nft add table ip wgnat
PostUp = nft add chain ip wgnat postrouting '{ type nat hook postrouting priority srcnat; policy accept; }'
PostUp = nft add rule ip wgnat postrouting ip saddr 10.8.0.0/24 oifname "eth0" masquerade
PostDown = nft delete table ip wgnat
[Peer]
# telefon
PublicKey = ZAWARTOSC_TELEFON_PUB
PresharedKey = ZAWARTOSC_TELEFON_PSK
AllowedIPs = 10.8.0.2/32
Zastąp eth0 swoim faktycznym interfejsem. Kto woli zostać przy iptables, doinstalowuje iptables i bierze zamiast tego:
PostUp = iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE
PostDown = iptables -t nat -D POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE
Dwa szczegóły, o których rzadko się wspomina. Po pierwsze: każdy wiersz PostUp jest wykonywany przez shell, a jeśli któryś z nich przerwie działanie błędem, wg-quick wycofuje cały start. Literówka w regule nft prowadzi więc nie do połowicznie działającego tunelu, tylko do żadnego. Po drugie: AllowedIPs ma po stronie serwera zupełnie inne znaczenie niż po stronie klienta. Tutaj jest to tablica przyporządkowania, który adres nadawcy należy do którego peera. Jeśli wpiszesz przy dwóch peerach ten sam adres, wygrywa ten wczytany jako ostatni, a drugi milczy. Każdy klient dostaje dokładnie jedną /32.
Ustaw uprawnienia i sprawdź składnię, zanim wystartujesz:
chmod 600 /etc/wireguard/wg0.conf
wg-quick strip wg0
wg-quick strip wypisuje konfigurację bez wierszy należących do samego wg-quick. Jeśli polecenie przejdzie do końca, plik jest formalnie w porządku. Jeśli zgłosi wg-quick: Line unrecognized, najczęściej wpisałeś opcję do niewłaściwej sekcji, na przykład DNS pod [Peer].
Trwałe włączenie przekazywania pakietów IP
Bez przekazywania każdy pakiet kończy bieg w serwerze. Włączasz je od razu i na stałe:
echo 'net.ipv4.ip_forward=1' > /etc/sysctl.d/99-wireguard.conf
sysctl --system
sysctl net.ipv4.ip_forward
Ostatnie polecenie musi wypisać net.ipv4.ip_forward = 1. Jeśli chcesz przepuszczać przez tunel także IPv6, do tego samego pliku należy dopisać jeszcze net.ipv6.conf.all.forwarding=1.
Jeśli działa ufw, to jeszcze nie wystarczy. ufw ustawia własną politykę przekazywania, która obowiązuje niezależnie od przełącznika w kernelu. Otwórz port i zezwól na przejście wyraźnie:
ufw allow 51820/udp
ufw route allow in on wg0 out on eth0
Typowy obraz błędu przy zapomnianym przekazywaniu jest szczególnie zdradliwy: handshake się udaje, ping na 10.8.0.1 się udaje, ale nic za nim nie jest osiągalne. Kto patrzy tylko na handshake, szuka potem godzinami w niewłaściwym miejscu.
Konfiguracja wg-quick jako usługi
Pakiet przynosi jednostkę szablonową, a nazwa po znaku @ to nazwa pliku konfiguracyjnego bez rozszerzenia:
systemctl enable wg-quick@wg0
systemctl start wg-quick@wg0
systemctl status wg-quick@wg0
journalctl -u wg-quick@wg0 -n 50 --no-pager
Pułapka, którą wielu zauważa dopiero po tygodniach: SaveConfig = true. Ta opcja przy zatrzymaniu usługi zapisuje bieżący stan z powrotem do pliku. Giną przy tym wszystkie komentarze, wszystkie wiersze PostUp w ich pierwotnej kolejności oraz każda ręcznie wprowadzona struktura. Przy serwerze, którego konfigurację utrzymujesz ręcznie, tę opcję po prostu pomiń.
Po starcie sprawdzasz stan nie po kodzie wyjścia, tylko po interfejsie:
wg show
ip -brief address show wg0
ss -ulpn
ss -ulpn musi pokazać nasłuch na porcie UDP 51820. wg show wypisuje peery, w tym momencie jeszcze bez handshake'u.
Konfiguracja klienta i kod QR na telefon
Plik klienta najlepiej wygenerować na serwerze, bo i tak leżą tam wszystkie klucze. Uwaga: po stronie klienta AllowedIPs oznacza co innego, mianowicie które cele mają iść przez tunel. 0.0.0.0/0, ::/0 znaczy: wszystko.
mkdir -p /etc/wireguard/clients
Zawartość pliku /etc/wireguard/clients/telefon.conf:
[Interface]
PrivateKey = ZAWARTOSC_TELEFON_KEY
Address = 10.8.0.2/32
DNS = 9.9.9.9, 149.112.112.112
[Peer]
PublicKey = ZAWARTOSC_SERVER_PUB
PresharedKey = ZAWARTOSC_TELEFON_PSK
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = adres.twojego.serwera:51820
PersistentKeepalive = 25
PersistentKeepalive = 25 nie jest przy telefonach luksusem. NAT w sieci komórkowej zapomina przyporządkowania UDP często po 30 do 60 sekund, a bez keepalive serwer nie może już sam z siebie odezwać się przez istniejące połączenie.
Kod QR generujesz bezpośrednio w terminalu:
qrencode -t ansiutf8 < /etc/wireguard/clients/telefon.conf
W aplikacji WireGuard stukasz w plus, wybierasz import przez kod QR i kierujesz kamerę na terminal. Dwie praktyczne wskazówki: zmniejsz czcionkę w terminalu, zanim wygenerujesz kod, inaczej nie zmieści się w kadrze. I nie kasuj pliku od razu po imporcie, przyda ci się przy zmianie urządzenia. Kto go jednak skasuje, musi wygenerować nową parę kluczy, bo klucza prywatnego nie da się wyliczyć z publicznego.
Po czym poznasz, że naprawdę działa
To, że systemctl start wraca bez żadnego komunikatu, znaczy tylko tyle, że interfejs istnieje. Właściwy test składa się z trzech stopni i warto przejść je w tej kolejności:
wg show wg0 latest-handshakes
wg show wg0 transfer
Stopień pierwszy, handshake. latest-handshakes wypisuje uniksowy znacznik czasu dla każdego peera. Jeśli stoi tam 0, żadne połączenie jeszcze nigdy nie doszło do skutku. W szczegółowym widoku wg show przeczytasz zamiast tego latest handshake: 42 seconds ago.
Stopień drugi, przepływ danych. Przy transfer stoją bajty odebrane i wysłane. Same bajty wysłane bez odebranych oznaczają: twoje pakiety wychodzą, ale nic nie wraca. To prawie zawsze firewall albo błędny endpoint, nigdy problem z kluczem.
Stopień trzeci, faktyczna droga. Na kliencie sprawdzasz, czy ruch w ogóle trafia do tunelu:
ip route get 1.1.1.1
Jeśli stoi tam dev wg0, routing się zgadza. Dopiero potem ma sens rzut oka na stronę pokazującą twój publiczny adres IP. Jeśli wyświetla ona adres twojego serwera, wszystko jest gotowe.
Diagnostyka: brak handshake'u
Najczęstszy obraz błędu w ogóle. Włącz najpierw logowanie modułu jądra, odpowiedź dostajesz z niego zwykle w dziesięć sekund:
echo 'module wireguard +p' > /sys/kernel/debug/dynamic_debug/control
dmesg -w
Teraz uruchamiasz na kliencie próbę połączenia i czytasz na bieżąco. Trzy komunikaty, o które chodzi:
wireguard: wg0: Handshake for peer 1 (...) did not complete after 5 seconds, retrying (try 2)i nic poza tym. Do serwera nie dociera ani jeden pakiet. Sprawdź to kontrolnie przeztcpdump -n -i eth0 udp port 51820. Jeśli nic tam nie widzisz, winien jest firewall przed serwerem, port albo to, że klient siedzi w sieci filtrującej wychodzący ruch UDP. Test z sieci komórkowej zamiast z firmowego Wi-Fi rozdziela te przypadki czysto.wireguard: wg0: Invalid handshake initiation from .... Pakiety docierają, ale nie pasują. Prawie zawsze nie zgadza się klucz publiczny serwera w pliku klienta albo klucz preshared jest wpisany tylko po jednej stronie. Klucz preshared musi być identyczny po obu stronach albo nie występować po żadnej z nich.- Żadnego komunikatu, choć
tcpdumppokazuje pakiety. Wtedy WireGuard nasłuchuje na innym porcie albo na innym adresie. Kontrola przezss -ulpn.
Rzadko opisywanym przypadkiem szczególnym jest zegar. WireGuard wkłada do pierwszej wiadomości handshake'u znacznik czasu, a serwer zapamiętuje dla każdego peera najwyższą kiedykolwiek widzianą wartość. Starsze znaczniki odrzuca i to jest właśnie ochrona przed powtórzeniem pakietów. Jeśli urządzenie miało zegar ustawiony daleko w przyszłość i raz się połączyło, po skorygowaniu zegara już się nie przebije. Serwer trzyma ten stan w pamięci, więc pomaga: ustawić zegar poprawnie, a potem na serwerze wg-quick down wg0 i wg-quick up wg0. Po ponownym zestawieniu blokada znika.
Wyłącz potem logowanie z powrotem, bo jest bardzo gadatliwe:
echo 'module wireguard -p' > /sys/kernel/debug/dynamic_debug/control
Diagnostyka: DNS nie działa
Objaw: tunel stoi, ping 1.1.1.1 działa, ale żadna nazwa się nie rozwiązuje. Przyczyny są dokładnie trzy.
Po pierwsze: resolver jest nieosiągalny. Jeśli wpiszesz DNS = 10.8.0.1, na serwerze musi faktycznie nasłuchiwać serwer nazw pod tym adresem. Goły Debian albo Ubuntu żadnego nie ma. Albo wpisujesz publiczny resolver, do którego dociera się przez tunel, albo stawiasz własny. W tym drugim wariancie wystarczy dnsmasq z małym plikiem pod /etc/dnsmasq.d/wireguard.conf:
interface=wg0
bind-dynamic
no-resolv
server=9.9.9.9
server=1.1.1.1
cache-size=1000
bind-dynamic jest ważne, bo wg0 przy starcie dnsmasq może jeszcze nie istnieć. no-resolv jest na Ubuntu obowiązkowe: bez tego wiersza dnsmasq czyta /etc/resolv.conf, znajduje tam adres stub 127.0.0.53 należący do systemd-resolved i buduje pętlę. I punkt, który na Ubuntu boli najczęściej: dnsmasq zajmuje port 53. Na systemie z aktywnym systemd-resolved port ten jest już zajęty, obie usługi kolidują ze sobą, a dnsmasq nie startuje. Wiersze interface=wg0 i bind-dynamic nie są więc kosmetyką, tylko zapobiegają temu, żeby dnsmasq podpinał się do wszystkich adresów. Sprawdź następnie przez ss -ulpn, że dnsmasq i systemd-resolved nie kłócą się o port 53.
Po drugie: resolver leży poza AllowedIPs. Kto zamiast 0.0.0.0/0 przepuszcza przez tunel tylko wybrane sieci, a jako serwer DNS wpisuje adres, którego na tej liście nie ma, wysyła zapytania obok tunelu, prosto do sieci lokalnej.
Po trzecie, tylko na klientach linuksowych: brakuje resolvconf. Wiersz DNS = jest przez wg-quick przekazywany do programu o nazwie resolvconf. Jeśli go nie ma, start przerywa się komunikatem /usr/bin/wg-quick: line 32: resolvconf: command not found. Sprawdź najpierw, czy ten program w ogóle jest obecny:
command -v resolvconf
Przy doinstalowaniu dystrybucje się rozchodzą i dokładnie na tym wykładają się przepisane poradniki na Ubuntu 24.04. Pakiet openresolv nie istnieje tam ani w main, ani w universe, a wywołanie kończy się komunikatem E: Package 'openresolv' has no installation candidate:
| System | Właściwy pakiet |
|---|---|
| Debian 11, Debian 12, Debian 13 | apt-get install -y openresolv |
| Ubuntu 22.04 | apt-get install -y openresolv |
| Ubuntu 24.04 | apt-get install -y resolvconf |
Na Ubuntu 24.04 resolvconf jest pakietem wirtualnym, który jednoznacznie rozwiązuje się do systemd-resolved i zakłada przy tym /usr/sbin/resolvconf, czyli dokładnie ten plik binarny, który wg-quick wywołuje dla wiersza DNS. Równie dobrze możesz tam zainstalować od razu systemd-resolved. Kto chce jednego wiersza działającego na wszystkich wymienionych systemach, bierze ten:
apt-get install -y openresolv || apt-get install -y resolvconf
Na Ubuntu 24.04 istnieje wariant tego problemu, który pojawia się po aktualizacji z 22.04: Failed to resolve interface "tun.wg0": No such device. Przyczyną jest pozostały stary pakiet resolvconf z 22.04 wraz z plikiem /etc/resolvconf/interface-order, a więc nie pakiet wirtualny o tej samej nazwie z 24.04. Na podstawie tego pliku wg-quick poprzedza nazwę interfejsu członem tun., z czym nakładka zgodności od systemd-resolved nie potrafi nic zrobić. Rozwiązaniem jest usunięcie starego pakietu, tak żeby została już tylko nakładka od systemd-resolved.
Diagnostyka: problemy z MTU
Najbardziej nieprzyjemny obraz błędu, bo wszystko wygląda na działające. Handshake stoi, ping działa, SSH działa, ale strony ładują się w połowie i zawisają, duże pobrania się urywają, a dotknięte jest akurat HTTPS. Powód: małe pakiety przechodzą, duże nie.
WireGuard opakowuje każdy pakiet dodatkowymi 60 bajtami, gdy tunel biegnie przez IPv4 (20 bajtów IP, 8 bajtów UDP, 32 bajty WireGuard), a 80 bajtami przez IPv6. Dlatego wg-quick odejmuje ryczałtem 80 bajtów od ustalonej MTU ścieżki i na normalnej trasie 1500 ląduje na 1420. Jest to świadomie zachowawcze i w większości przypadków się zgadza.
Nie zgadza się wtedy, gdy droga jest węższa niż 1500, na przykład przy DSL z PPPoE (1492), za kolejnym tunelem albo w części sieci komórkowych. Zmierz faktyczną MTU ścieżki z klienta do publicznego adresu serwera, z ustawionym bitem Don't Fragment i bez tunelu:
ping -M do -s 1472 -c 3 ADRES_DOCELOWY
1472 plus 28 bajtów nagłówków daje 1500. Jeśli wraca ping: local error: message too long, mtu=... albo Frag needed and DF set, obniżaj wartość stopniowo, aż przejdzie: 1464, 1444, 1414, 1372. Do znalezionej wartości dodajesz 28 i odejmujesz 80. Przy 1464 daje to więc 1492 dla MTU ścieżki i 1412 dla MTU tunelu.
Wpisuje się to w sekcji [Interface], po tej stronie, która ma problem:
MTU = 1412
Szybki test kontrolny, zanim zaczniesz długo liczyć: ustaw na próbę MTU = 1280. To najmniejsza MTU gwarantowana przez IPv6 i działa praktycznie wszędzie. Jeśli strony ładują się przy tym czysto, winna była MTU i możesz spokojnie dobierać optymalną wartość. Jeśli problem zostaje, leży gdzie indziej.
Na samym serwerze błędna MTU zdarza się rzadziej, ale jest możliwa: jeśli stoi tam wartość wyższa, niż pozwala trasa, skutek zobaczysz tylko przy określonych celach. Rzut oka na ip -brief address show wg0 oraz ip link show wg0 pokazuje aktualnie ustawioną wartość.
Zmiana peerów w trakcie pracy bez wyrzucania wszystkich
Odruch, żeby po każdej zmianie wystukać systemctl restart wg-quick@wg0, zrywa wszystkie istniejące połączenia i buduje reguły NAT od nowa. Przy serwerze z kilkoma użytkownikami jest to niepotrzebnie brutalne. WireGuard potrafi uzgodnić konfigurację w trakcie pracy:
wg syncconf wg0 <(wg-quick strip wg0)
Polecenie porównuje plik z bieżącym stanem i zmienia tylko różnice. Istniejące peery zachowują swoją sesję. Zwróć uwagę, że podstawienie procesu przez <(...) wymaga powłoki bash albo zsh, w czystym sh zawodzi. Pojedynczego peera dodajesz też bezpośrednio:
wg set wg0 peer KLUCZ_PUBLICZNY allowed-ips 10.8.0.3/32
Ta zmiana żyje tylko w pamięci. Zapisz ją dodatkowo do pliku konfiguracyjnego, inaczej po następnym restarcie peer zniknie. To zresztą najczęstsza przyczyna zdania „wczoraj jeszcze działało”.
Jeśli chcesz prowadzić punkt końcowy WireGuard na stałe i pod stabilnym adresem, własny serwer jest naturalną podstawą. W KernelHost serwery root KVM i serwery dedykowane pracują w centrum danych maincubes we Frankfurcie nad Menem (TÜV TIER3+), w naszej własnej sieci, w modelu prepaid i bez minimalnego okresu umowy. Dalej w temacie warto zajrzeć do naszych wpisów o zabezpieczeniu SSH oraz o konfiguracji ufw.
Najczęstsze pytania
Która wersja WireGuard jest w Debianie 13, Debianie 12, Ubuntu 24.04 i Ubuntu 22.04?
Dlaczego wiersze iptables z wielu poradników nie działają na Debianie?
Handshake się udaje, ale nie mam internetu. Z czego to wynika?
Po czym rozpoznam problem z MTU?
Czy muszę prowadzić własny serwer DNS, żeby DNS działał w tunelu?
Dlaczego urządzenie po ustawieniu zegara przestaje się łączyć?
2026 KernelHost GmbH. Wszelkie prawa zastrzeżone. Ten poradnik jest chroniony prawem autorskim. Publikowanie go w innych serwisach, w całości, we fragmentach lub w zmienionej formie, wymaga naszej pisemnej zgody. Cytaty z podaniem źródła i z linkiem są jak najbardziej mile widziane.

