SSH: SPA (fwknop) + nftables + WireGuard — Zweiteiler für Server & Client

Erstellt am: 12/10/2025 - Lesezeit: 10 Minuten

Einleitung (kurz)

Portknocks sind wie ein akustisches Signal, was eine Geheimtür öffnet: nur wer die richtige Tonfolge kennt, darf eintreten. SPA (Single Packet Authorization) mit fwknop macht das verschlüsselt und zuverlässig. Für mobile Arbeit (wechselnde IPs) kombinieren wir SPA mit WireGuard als Fallback — oder nutzen SPA-Varianten, die nicht an eine feste Client-IP binden (z. B. --resolve-url oder GPG-SPA). nftables ist die Firewall-Engine, mit der wir die Zugriffsregeln sauber und performant verwalten.

Dieses Tutorial ist in zwei Teilen aufgeteilt:

  • Teil A Server: nftables-Grundgerüst, fwknop Server-Setup (mit nft-Integration), WireGuard Server-Setup, Backup & Recovery, Router-Portforwarding.
  • Teil B Client: SPA (resolve-URL) Variante, GPG-SPA Variante, WireGuard Client-Config, Tipps für mobiles Arbeiten.

Teil A: Server: nftables + fwknop (SPA) + WireGuard (Basis)

Ziel: Server so einrichten, dass SSH standardmäßig versteckt ist (nur per SPA erreichbar) und zusätzlich WireGuard läuft, damit Clients auch bei wechselnder IP zuverlässig verbinden können.

Vor dem Start: Den Notfall-Plan (lesen!)

  • Du brauchst physischen/KVM-Zugang oder eine Admin-IP, die du vorerst freihältst.

  • Lege unbedingt ein Backup von nftables an:

    sudo nft list ruleset > ~/nftables-before-fwknop.conf
    
  • Arbeite am Anfang getestet in einer VM, wenn möglich.


1) Pakete installieren

Da kommen wir leider nicht drumherum, erst einmal die Dinge zu installieren, die wir brauchen:

# Server (Debian/LMDE/Ubuntu)
sudo apt update
sudo apt install fwknop-server fwknop-client nftables wireguard -y
sudo systemctl enable --now nftables

Prüfe:

nft --version
sudo systemctl status fwknop-server

2) nftables Basisaufbau (Table, Set, Chains)

Wir legen ein Set allowed_ssh an — fwknop fügt Client-IPs dort ein (mit Timeout). So bleibt die Firewall sauber.

# Table anlegen (falls noch nicht vorhanden)
sudo nft add table inet filter

# Set für erlaubte SSH-IPs (IPv4) mit timeout-Flag
sudo nft 'add set inet filter allowed_ssh { type ipv4_addr\; flags timeout\; }'

# (Optional) IPv6 Set
sudo nft 'add set inet filter allowed_ssh_v6 { type ipv6_addr\; flags timeout\; }'

# INPUT Chain mit policy drop
sudo nft 'add chain inet filter input { type filter hook input priority 0 \; policy drop\; }'

# Basisregeln
sudo nft 'add rule inet filter input iif "lo" accept'
sudo nft 'add rule inet filter input ct state established,related accept'

# Erlaube SSH für IPs, die im Set sind
sudo nft 'add rule inet filter input ip saddr @allowed_ssh tcp dport 22 accept'

# IPv6 Variante (optional)
sudo nft 'add rule inet filter input ip6 saddr @allowed_ssh_v6 tcp dport 22 accept'

Speichern (Boot-Persistence):

sudo nft list ruleset > /etc/nftables.conf

3) fwknop (Server) konfigurieren — nft integration

  1. Finde den Pfad zu nft auf deinem System:
which nft
# z.B. /usr/sbin/nft
  1. fwknopd Interface einstellen (fwknop sniffet per pcap): Editiere /etc/fwknop/fwknopd.conf (als root) und setze:
PCAP_INTF enp3s0      # ersetze enp3s0 durch dein externes Interface
UseSyslog Y           # optional, damit SPA-Versuche in syslog landen
  1. access.conf (fwknop) anlegen — nft-Befehle als FW_CMD Erstelle die /etc/fwknop/access.conf mit folgendem Template (ersetze die Base64-Schlüssel durch die später vom Client erzeugten):

sudo nano /etc/fwknop/access.conf

Als Inhalt:

SOURCE: ANY;
OPEN_PORTS: tcp/22;
FW_ACCESS_TIMEOUT: 60;
DATA_COLLECT_MODE: PCAP;

KEY_BASE64: <DEIN_RIJNDAEL_KEY_BASE64>;
HMAC_KEY_BASE64: <DEIN_HMAC_KEY_BASE64>;

# nft add element mit timeout (FW_CMD führt das aus)
# %IP% = Platzhalter, den fwknop zur Laufzeit ersetzt
FW_CMD: /usr/sbin/nft add element inet filter allowed_ssh { %IP% timeout 60s }
FW_CMD_DEL: /usr/sbin/nft delete element inet filter allowed_ssh { %IP% }

Zugriffsrechte setzen:

sudo chown root:root /etc/fwknop/access.conf
sudo chmod 600 /etc/fwknop/access.conf
  1. fwknop starten / neu starten:
sudo systemctl restart fwknop-server 2>/dev/null || sudo systemctl restart fwknopd
sudo systemctl enable --now fwknop-server
sudo journalctl -u fwknop-server -f

Hinweis: Wenn deine nft Version timeout für Set-Elemente nicht unterstützt, verwende FW_CMD ohne timeout und FW_CMD_DEL als Cleanup (fwknop kann das FW_CMD_DEL aufrufen), oder nutze ein kleines Timer-Script.

4) WireGuard (Server) — Basis-Setup

WireGuard ist die stabile Lösung für mobile Clients. Wenn WireGuard läuft, kannst du SSH über den Tunnel erreichen, völlig unabhängig von der öffentlichen Client-IP.

4.1 Server Keys & Konfiguration

# Verzeichnisse
sudo mkdir -p /etc/wireguard
sudo chmod 700 /etc/wireguard

# Schlüssel (als root)
wg genkey | sudo tee /etc/wireguard/server_private.key | wg pubkey | sudo tee /etc/wireguard/server_public.key

Erstelle /etc/wireguard/wg0.conf (Beispiel):

[Interface]
Address = 10.10.10.1/24
ListenPort = 51820
PrivateKey = <INHALT VON /etc/wireguard/server_private.key>
# Optional: bring up nftables rules beim Start (PostUp/PostDown) oder verwalte nftables manuell
PostUp = /usr/sbin/nft add element inet filter allowed_ssh { 10.10.10.2 } || true
PostDown = /usr/sbin/nft delete element inet filter allowed_ssh { 10.10.10.2 } || true

Hinweis: In PostUp/PostDown kannst du erlauben, dass bestimmte VPN-Peers direkt SSH dürfen (z. B. 10.10.10.2). Alternativ verwaltest du Zugriff rein über die allowed_ssh Set-Einträge für öffentliche IPs — VPN-Tunneled Clients benötigen das nicht.

Aktiviere nun WireGuard:

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

4.2 nftables + WireGuard

Wenn SSH über den Tunnel laufen soll, sorge dafür, dass die INPUT-Chain VPN-Traffic zulässt (beispielsweise accept für iifname wg0 oder für das VPN-Subnet):

sudo nft 'add rule inet filter input iif "wg0" accept'
# oder gezielt: accept tcp dport 22 iif "wg0"

5) Port-Forwarding am Router (was muss weitergeleitet werden?)

  • WireGuard: UDP Port (Standard 51820) → Server LAN-IP (z. B. 192.168.178.10).
  • SPA / fwknop: SPA-Pakete werden an eine Zielport gesendet (je nach Client-Aufruf). Wenn du einen festen Port für SPA nutzt (z. B. UDP 62201), musst du diesen Port ebenfalls zum Server forwarden. fwknop selbst „lauscht“ per pcap, aber NAT/Router muss Pakete an die Server-IP durchlassen/weiterleiten.

Wir behandeln die Router-Konfiguration unten (Fritz!Box & Speedport).


6) Backup / Rollback (nftables + fwknop)

Backup nftables (wichtig):

sudo nft list ruleset > ~/nftables-before-fwknop.conf

Rollback (falls ausgesperrt):

sudo nft -f ~/nftables-before-fwknop.conf

Rollback-Script (optional):

cat > ~/fwknop-rollback-nft.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
BACKUP="$HOME/nftables-before-fwknop.conf"
if [ ! -f "$BACKUP" ]; then
  echo "Backup $BACKUP fehlt"
  exit 1
fi
echo "Restore nftables from $BACKUP"
sudo nft -f "$BACKUP"
EOF
chmod +x ~/fwknop-rollback-nft.sh

7) Kurz Checks (vor Tests)

  • sudo nft list ruleset — sieht man Table / Set / Rules?
  • which nft — Pfad prüfen und ggf. in access.conf anpassen.
  • ip addr — Interface prüfen (PCAP_INTF).
  • sudo journalctl -u fwknop-server -f — Logs beobachten.

Teil B: Client: SPA-Varianten & WireGuard-Client

Ziel: zwei Wege, wie du vom Laptop/Smartphone zuverlässig Zugriff bekommst:

  • SPA mit resolve-url (praktisch unterwegs), oder
  • GPG-SPA (stärker, IP-unabhängig), oder
  • WireGuard (empfohlen für Vielnutzer / stabile Verbindung).

A) SPA mit --resolve-url (mobil, einfach)

Konzept: Client ermittelt seine öffentliche IP über eine Web-URL (z. B. https://icanhazip.com oder besser: deine eigene kleine Resolver-URL) und packt diese IP ins SPA-Paket. Server öffnet genau diese IP.

Client Befehl (einfach):

# Beispiel: verwende deine Server-Domain im -D Feld
fwknop -A tcp/22 -D server.example.com --use-hmac --resolve-url https://icanhazip.com --verbose

Vorteile: funktioniert bei wechselnder IP; minimaler Konfig-Aufwand. Nachteile: Privatsphäre (du fragst externe Dienste nach deiner IP). Ich empfehle, falls möglich, eine eigene kleine resolver-URL zu hosten (z. B. ein kleines CGI mit echo $_SERVER['REMOTE_ADDR']).

Server: keine Änderung nötig, access.conf wie in Teil A verwenden — fwknop setzt %IP% aus dem SPA.


B) GPG SPA (keine Bindung an Source-IP)

Konzept: Client signiert/verschlüsselt SPA mit privatem GPG-Key. Server nutzt Public Key, um Pakete zu prüfen. Server vertraut dem Paketinhalt, nicht der Quelladresse → mobil und sicher.

Schritte (Kurz):

  1. Client (Erzeuge GPG-Key):
gpg --full-generate-key
# Notiere KeyID (z.B. ABCDEF12)
  1. Server (Importiere Client Public Key): Übertrage gpg --export --armor CLIENT_KEYID und importiere auf dem Server in /root/.gnupg oder in einem dedizierten gnupg-home für fwknop.

  2. access.conf (GPG-Konfig):

GPG_HOME_DIR: /root/.gnupg
GPG_REMOTE_ID: <CLIENT_KEYID>
GPG_DECRYPT_ID: <SERVER_KEYID>    # optional, wenn Entschlüsselung nötig
  1. Client erzeugt SPA mit GPG-Optionen: siehe fwknop manpage (Optionen wie --use-gpg / --gpg-key — die Flags variieren mit Version).

Vorteile: sehr sicher, keine Source-IP-Problematik. Nachteile: GPG-Keymanagement erforderlich (Passphrase, ggf. Smartcard).

1) Empfehlung (mobil & einfach) — resolve-url

Der Client ermittelt seine öffentliche IP über eine Web-URL (z. B. https://icanhazip.com oder eine eigene Resolver-URL) und sendet ein SPA an den Server. Einfach, robust bei wechselnden IPs.

# Auf dem Client ausführen (ersetzen!)
fwknop -A tcp/22 -D server.example.com --use-hmac --resolve-url https://icanhazip.com --verbose

Erläuterung / Platzhalter

  • -A tcp/22 → Zugriff auf SSH (Port 22).
  • -D server.example.com → Hostname oder IP deines Servers (ersetzen!).
  • --use-hmac → nutze HMAC (bei symmetrischen Keys).
  • --resolve-url https://icanhazip.com → Dienst zur Bestimmung der öffentlichen IP; empfehle eigene Resolver-URL für Privatsphäre.
  • --verbose → Ausgabe zum Debuggen.

Schnell-Check (nach Ausführung):

# prüfe, ob ~/.fwknoprc Schlüssel enthält
grep -E '^(KEY_BASE64|HMAC_KEY_BASE64):' ~/.fwknoprc

Wenn Keys vorhanden sind, kannst du daraus /etc/fwknop/access.conf bauen (Server-Seite), z. B. mit dem AWK-Snippet im Tutorial.


2) Empfehlung (IP-unabhängig & kryptographisch) — GPG-SPA

Wenn du von überall ohne IP-Bindung zugreifen willst und GPG benutzt, signiere/verschlüssele das SPA mit deinem GPG-Key. (Flag-Namen können je nach fwknop-Version leicht variieren — prüfe man fwknop falls nötig.)

Beispiel (konzeptionell — ersetze CLIENT_KEYID und server.example.com):

# Beispiel (prüfe deine fwknop-Version / manpage für genaue gpg-Flags)
fwknop -A tcp/22 -D server.example.com --use-gpg --gpg-identity CLIENT_KEYID --verbose

Wichtige Hinweise

  • --use-gpg / --gpg-identity sind exemplarische Flags — die genaue Option kann je nach fwknop-Version leicht heißen –gpg-key / –gpg-id. Prüfe man fwknop oder fwknop –help.
  • Auf dem Server musst du den zugehörigen Public-Key importieren (z. B. in /root/.gnupg) und access.conf so konfigurieren, dass GPG_REMOTE_ID / GPG_HOME_DIR gesetzt sind.

Kurz-Check auf dem Client (GPG):

gpg --list-keys
# notiere die KeyID, die du dann in fwknop verwendest

3) (Optional) Einzeiler, um nach Key-Erzeugung eine access.conf vorzubauen

Wenn du nach dem --key-gen die ~/.fwknoprc hast, kannst du lokal schnell eine access.conf erzeugen (auf dem Client) und dann per scp auf den Server kopieren:

awk '/^KEY_BASE64:/ {k=$2} /^HMAC_KEY_BASE64:/ {h=$2} END { if(!k||!h){print "Fehler: Keys nicht gefunden" > "/dev/stderr"; exit 2} \
printf "SOURCE: ANY;\nOPEN_PORTS: tcp/22;\nFW_ACCESS_TIMEOUT: 60;\nDATA_COLLECT_MODE: PCAP;\n\nKEY_BASE64: \ 
%s\nHMAC_KEY_BASE64: %s\n", k, h }' ~/.fwknoprc > /tmp/fwknop_access.conf

Danach scp + sudo mv wie im Tutorial beschrieben, oder nutze das Einzeiler-Deploy aus dem Artikel.


C) WireGuard Client (empfohlen für dauerhafte Mobil-Nutzung)

  1. Client Key erzeugen:
wg genkey | tee client_private.key | wg pubkey > client_public.key
  1. Client-Config (Beispiel wg0-client.conf):
[Interface]
PrivateKey = <INHALT client_private.key>
Address = 10.10.10.2/24
DNS = 10.10.10.1

[Peer]
PublicKey = <SERVER_PUBLIC_KEY>
Endpoint = your.dyndns.example.com:51820
AllowedIPs = 0.0.0.0/0, ::/0  
PersistentKeepalive = 25
  1. Auf dem Server füge den Peer (öffentliche Schlüssel + AllowedIPs) zur wg0 Konfiguration hinzu (oder verwalte per wg set).

  2. Firewall: erlaube wg0 traffic (siehe Teil A).

Vorteile: stabil, unabhängig von Client-IP, performant. Nachteile: initialer Setupaufwand, Peers verwalten.


Router: Port-Forwarding für Fritz!Box & Speedport (Allgemeine Anleitung)

Hinweis: Menübezeichnungen / Firmwarevarianten weichen leicht ab. Wenn die einzelnen Menüs nicht exakt passen, suche nach Begriffen wie „Internet“,
„Freigaben“, „Portfreigabe“, „NAT“ oder „Portweiterleitung“. Lies im Zweifelsfall das Handbuch deines Modells.

1) Fritz!Box (AVM)

  • Öffne die Weboberfläche: http://fritz.box
  • Melde dich als Admin an.
  • Menü: Internet → Freigaben (oder «Portfreigaben» / «Freigaben»).
  • „Neue Freigabe“ / „Gerät für Freigaben hinzufügen“ — wähle deinen Server (LAN-IP) aus.
  • Lege eine Portfreigabe an:
  • Anwendungsname: WireGuard (oder fwknop-SPA)
  • Protokoll: UDP
  • Port extern: 51820 (WireGuard) bzw. z. B. 62201 (SPA), sofern du SPA über UDP auf einem bestimmten Port senden willst
  • Port intern: gleich wie extern

Speichern. Teste von außen (z. B. Handy über Mobilfunk) mit nc / wg / fwknop Aufruf.

2) Speedport (Deutsche Telekom) — typischer Ablauf

  • Öffne Webinterface: http://192.168.2.1 (oder speedport.ip)
  • Login als Admin.
  • Menü: Internet / Freigaben / Portweiterleitung (genaue Bezeichnung variiert).
  • „Neue Regel anlegen“: fülle aus wie bei FritzBox:
  • Zielgerät / IP: Server LAN-IP
  • Port extern / intern: 51820 UDP (WireGuard) und optional SPA-Port
  • Protokoll: UDP

Speichern und testen.

Test (von außen)

Für WireGuard: versuche mit wg/wg-quick die Verbindung vom Mobilgerät.

Für SPA: teste fwknop Client-Befehl mit --resolve-url vom Mobilnetz.


Zum Abschluss

Zum Abschluss liefere ich noch 3 mögliche Konfigurationsvarianten für die Mobile Verbindung.

Variante A — Resolve-URL (praktisch, mobil-freundlich)

Konzept kurz: Client ermittelt seine öffentliche IP via HTTP(S) (z. B. https://icanhazip.com oder besser: deine eigene kleine resolver-URL) und packt diese IP ins SPA. Server fügt diese IP ins nft-Set ein.

Server — access.conf (nft-Integration)

Füge in /etc/fwknop/access.conf (ersetze Base64-Keys):

SOURCE: ANY;
OPEN_PORTS: tcp/22;
FW_ACCESS_TIMEOUT: 60;
DATA_COLLECT_MODE: PCAP;

KEY_BASE64: <DEIN_RIJNDAEL_KEY_BASE64>;
HMAC_KEY_BASE64: <DEIN_HMAC_KEY_BASE64>;

FW_CMD: /usr/sbin/nft add element inet filter allowed_ssh { %IP% timeout 60s }
FW_CMD_DEL: /usr/sbin/nft delete element inet filter allowed_ssh { %IP% }

Achte auf den korrekten nft-Pfad (which nft) und dass allowed_ssh existiert.

Client Einzeiler (mobil)

fwknop -A tcp/22 -D server.example.com --use-hmac --resolve-url https://icanhazip.com --verbose

(Empfehlung: ersetze https://icanhazip.com durch eine eigene Resolver-URL, wenn dir Privatsphäre wichtig ist.)

Kurzcheck

  • Nach Ausführung: auf dem Server sudo nft list set inet filter allowed_ssh → die IP sollte erscheinen.
  • Dann ssh -i ~/.ssh/id_rsa user@server.example.com.

Vor-/Nachteile

  • Einfach einzurichten, funktioniert bei wechselnden IPs.
  • − Abhängigkeit von einem externen IP-Service (oder Aufwand, selbst einen Mini-Resolver zu hosten).
  • Sicherheit: weiterhin Verschlüsselung durch SPA; kurzlebiger Zugriff (Timeout).

Variante B: GPG-SPA (IP-unabhängig, kryptographisch stark)

Konzept kurz: Client signiert/verschlüsselt das SPA mit seinem privaten GPG-Key; Server überprüft/entschlüsselt mit dem zugehörigen Public Key. Server braucht nicht die Client-Source-IP zu binden (oder kann zusätzlich %IP% verwenden).


Profilbild

Christian Rumpf

Ich bin aktiver Berufskraftfahrer in zweiter Generation mit langjähriger Erfahrung im Transportsektor. Auf diesem Blog teile ich meine persönliche Meinung und Erfahrungen.

 

Du hast Fragen, Anregungen oder Kritik? Schreib mir eine E-Mail: ue.golbsnaitsirhc@ofni