WireGuard — bezpieczny dostęp administracyjny do homelabu

VPN do domu na WireGuard: konfiguracja serwera w kontenerze LXC, klienci z kodem QR, DDNS przez Cloudflare oraz jak sprawdzić i ominąć CGNAT.

Cloudflare Tunnel świetnie nadaje się do aplikacji webowych, ale administracja to coś innego: SSH, panel Proxmoxa, iDRAC, udziały SMB. Potrzebujesz do niej pełnego dostępu do sieci LAN, tak jak z domowej sieci — i nikt poza Tobą nie powinien nawet wiedzieć, że taki dostęp istnieje.

WireGuard ma tu przewagę nad innymi VPN-ami: nie odpowiada na pakiety bez poprawnego klucza. Skaner portów nie odróżni go od zamkniętego portu. Do tego jest szybki i mieści się w kilku tysiącach linii kodu w jądrze Linuksa.

CGNAT — jak sprawdzić, czy masz publiczny IP

WireGuard wymaga, żeby urządzenie z zewnątrz mogło dotrzeć do Twojego routera na porcie UDP. Sprawdź to, zanim zaczniesz:

Okno terminala
# Adres, pod którym widzi Cię internet
curl -4s https://ifconfig.me

Porównaj wynik z adresem WAN w panelu routera:

  • Adresy identyczne — masz publiczny IP. Możesz kontynuować.
  • Adres WAN z zakresu 100.64.0.0/10 (np. 100.72.14.3) albo prywatny (10.x, 192.168.x) — jesteś za CGNAT-em operatora. Przekierowanie portu nic nie da. Rozwiązania opisuję na końcu artykułu.

Kontener LXC na WireGuard

Moduł WireGuard jest wbudowany w jądro Proxmoxa, więc serwer może działać w lekkim, nieuprzywilejowanym kontenerze LXC — 1 rdzeń i 256 MB RAM-u to więcej, niż potrzeba.

Na hoście Proxmox: kontener 120
# Najnowszy szablon Debiana 13
pveam update
TEMPLATE=$(pveam available --section system | awk '/debian-13-standard/ {print $2}' | sort -V | tail -1)
pveam download local "$TEMPLATE"
pct create 120 "local:vztmpl/$TEMPLATE" \
--hostname wireguard \
--cores 1 \
--memory 256 \
--swap 256 \
--rootfs tank-vm:4 \
--net0 name=eth0,bridge=vmbr0,ip=192.168.1.30/24,gw=192.168.1.1 \
--nameserver 192.168.1.1 \
--unprivileged 1 \
--features nesting=1 \
--ssh-public-keys /root/admin.pub \
--onboot 1 \
--start 1
pct enter 120

W kontenerze:

W kontenerze 120
apt update && apt full-upgrade -y
apt install -y wireguard-tools iptables qrencode
# Przekazywanie pakietów między tunelem a LAN
cat > /etc/sysctl.d/99-wireguard.conf <<'EOF'
net.ipv4.ip_forward = 1
EOF
sysctl --system

Konfiguracja serwera WireGuard

Poniższy skrypt generuje klucze i plik wg0.conf. Uzupełnij trzy zmienne na górze — wartości w skrypcie odpowiadają sieci z tej serii:

/root/wg-setup.sh
#!/usr/bin/env bash
set -euo pipefail
VPN_SUBNET="10.8.0" # sieć tunelu: 10.8.0.0/24, serwer = .1
LISTEN_PORT=51820
LAN_IFACE="eth0" # interfejs kontenera w sieci LAN
umask 077
mkdir -p /etc/wireguard/clients
cd /etc/wireguard
[[ -f server.key ]] || wg genkey > server.key
wg pubkey < server.key > server.pub
cat > wg0.conf <<EOF
[Interface]
Address = ${VPN_SUBNET}.1/24
ListenPort = ${LISTEN_PORT}
PrivateKey = $(cat server.key)
# NAT: urządzenia w LAN widzą ruch z VPN jako ruch z kontenera — bez tras statycznych na routerze
PostUp = iptables -t nat -A POSTROUTING -s ${VPN_SUBNET}.0/24 -o ${LAN_IFACE} -j MASQUERADE
PostDown = iptables -t nat -D POSTROUTING -s ${VPN_SUBNET}.0/24 -o ${LAN_IFACE} -j MASQUERADE
EOF
systemctl enable --now wg-quick@wg0
wg show
Okno terminala
bash /root/wg-setup.sh

Dodawanie urządzeń z kodem QR

Każde urządzenie (telefon, laptop) dostaje własną parę kluczy i adres w tunelu. Skrypt dopisuje je do serwera bez restartu tunelu i wyświetla kod QR do zeskanowania aplikacją WireGuard:

/usr/local/sbin/wg-add-peer
#!/usr/bin/env bash
# Użycie: wg-add-peer <nazwa> <numer 2-254>
# Przykład: wg-add-peer telefon 2
set -euo pipefail
NAME="${1:?Podaj nazwę urządzenia}"
NUM="${2:?Podaj numer urządzenia (2-254)}"
VPN_SUBNET="10.8.0"
LAN_SUBNET="192.168.1.0/24"
LAN_DNS="192.168.1.1"
ENDPOINT="vpn.prywatnylab.pl:51820" # domena z DDNS (niżej) lub stały publiczny IP
cd /etc/wireguard
umask 077
if grep -q "AllowedIPs = ${VPN_SUBNET}.${NUM}/32" wg0.conf; then
echo "Adres ${VPN_SUBNET}.${NUM} jest już zajęty" >&2
exit 1
fi
KEY=$(wg genkey)
PUB=$(echo "$KEY" | wg pubkey)
PSK=$(wg genpsk)
cat >> wg0.conf <<EOF
[Peer]
# ${NAME}
PublicKey = ${PUB}
PresharedKey = ${PSK}
AllowedIPs = ${VPN_SUBNET}.${NUM}/32
EOF
cat > "clients/${NAME}.conf" <<EOF
[Interface]
PrivateKey = ${KEY}
Address = ${VPN_SUBNET}.${NUM}/32
DNS = ${LAN_DNS}
[Peer]
PublicKey = $(cat server.pub)
PresharedKey = ${PSK}
Endpoint = ${ENDPOINT}
# Split tunnel: przez VPN idzie tylko ruch do labu, reszta internetu bezpośrednio
AllowedIPs = ${VPN_SUBNET}.0/24, ${LAN_SUBNET}
PersistentKeepalive = 25
EOF
# Przeładowanie konfiguracji bez zrywania istniejących połączeń
wg syncconf wg0 <(wg-quick strip wg0)
qrencode -t ansiutf8 < "clients/${NAME}.conf"
echo "Konfiguracja zapisana w /etc/wireguard/clients/${NAME}.conf"
Okno terminala
chmod 700 /usr/local/sbin/wg-add-peer
wg-add-peer telefon 2
wg-add-peer laptop 3

Odebranie dostępu (zgubiony telefon): usuń sekcję [Peer] danego urządzenia z wg0.conf i wykonaj wg syncconf wg0 <(wg-quick strip wg0).

Router: przekierowanie portu

W routerze przekieruj UDP 51820 na 192.168.1.30 (kontener WireGuard). To jedyny port, jaki otwierasz w całej serii — i jedyny, który nie ujawnia niczego, dopóki klient nie przedstawi poprawnego klucza.

Zmienny adres IP: DDNS przez Cloudflare

Większość domowych łączy dostaje co jakiś czas nowy adres. Kontener cloudflare-ddns pilnuje, żeby rekord vpn.prywatnylab.pl zawsze wskazywał aktualny IP. Dodaj go w maszynie docker01:

/opt/stacks/ddns/compose.yaml
services:
cloudflare-ddns:
image: favonia/cloudflare-ddns:latest
container_name: cloudflare-ddns
restart: unless-stopped
network_mode: host
read_only: true
cap_drop: [all]
security_opt: [no-new-privileges:true]
user: '1000:1000'
environment:
CLOUDFLARE_API_TOKEN: ${CLOUDFLARE_API_TOKEN}
DOMAINS: vpn.prywatnylab.pl
PROXIED: 'false'
IP6_PROVIDER: none

Token API utworzysz w panelu Cloudflare: My Profile → API Tokens → Create Token → szablon „Edit zone DNS”, z uprawnieniami ograniczonymi do jednej strefy. Zapisz go w /opt/stacks/ddns/.env jako CLOUDFLARE_API_TOKEN=... i ustaw chmod 600 .env.

PROXIED: 'false' jest tu konieczne. Cloudflare proxy obsługuje tylko HTTP(S), więc rekord VPN musi wskazywać bezpośrednio na Twój IP.

Test

Wyłącz Wi-Fi w telefonie (żeby wyjść przez sieć komórkową), włącz tunel w aplikacji WireGuard i otwórz https://192.168.1.10:8006. Na serwerze:

Okno terminala
wg show

Przy urządzeniu powinna pojawić się linia latest handshake: … seconds ago i rosnące liczniki transfer.

Jak ominąć CGNAT

Bez publicznego IP połączenie przychodzące do domu jest niemożliwe, ale wychodzące — jak najbardziej. Masz trzy wyjścia:

  1. Poproś operatora o publiczny adres IPv4. Wielu dostawców światłowodu przydziela go bezpłatnie albo za kilka złotych miesięcznie. To najprostsze rozwiązanie.
  2. IPv6. Jeśli operator daje natywne IPv6, każde urządzenie ma publiczny adres. Otwórz UDP 51820 w firewallu routera dla adresu IPv6 kontenera i ustaw IP6_PROVIDER w DDNS. Warunek: sieć, z której się łączysz, też musi mieć IPv6.
  3. Serwer pośredniczący w chmurze. Darmowa maszyna w Oracle Cloud z publicznym IP służy jako punkt spotkań. Dom łączy się z nią tunelem wychodzącym (PersistentKeepalive), a Ty łączysz się z nią z zewnątrz. Tę samą maszynę wykorzystasz w serii o niezależnym monitoringu — konfigurację VPS-a opisuję w części o Oracle Cloud.

Na tym kończy się seria o infrastrukturze. Masz serwer z Proxmoxem, pulę ZFS ze snapshotami, Dockera w maszynie wirtualnej, aplikacje dostępne przez Cloudflare Tunnel i prywatny kanał administracyjny. Pozostaje jedno pytanie: skąd dowiesz się, że coś przestało działać? Odpowiedź w serii Niezależny monitoring domowej sieci.