Introduction (Short)
Port knocks are like an acoustic signal that opens a secret door: only those who know the correct sequence of tones may enter. SPA (Single Packet Authorization) with fwknop makes this encrypted and reliable. For mobile work (changing IPs), we combine SPA with WireGuard as a fallback — or use SPA variants that do not bind to a fixed client IP (e.g., --resolve-url or GPG-SPA). nftables is the firewall engine with which we manage access rules cleanly and performantly.
This tutorial is divided into two parts:
- Part A Server: nftables base structure, fwknop server setup (with nft integration), WireGuard server setup, Backup & Recovery, Router port forwarding.
- Part B Client: SPA (resolve-url) variant, GPG-SPA variant, WireGuard client config, tips for mobile work.
Part A: Server: nftables + fwknop (SPA) + WireGuard (Base)
Goal: Set up the server so that SSH is hidden by default (only reachable via SPA) and additionally run WireGuard so clients can reliably connect even with a changing IP.
Before Starting: Read the Emergency Plan!
You need physical/KVM access or an admin IP that you keep free for now.
Definitely create a backup of nftables:
1sudo nft list ruleset > ~/nftables-before-fwknop.confIf possible, work initially in a VM to test.
1) Install Packages
Unfortunately, we have to start by installing the things we need:
| |
Check:
| |
2) nftables Base Setup (Table, Set, Chains)
We create a set allowed_ssh — fwknop adds client IPs there (with timeout). This keeps the firewall clean.
| |
Save (Boot Persistence):
| |
3) Configure fwknop (Server) — nft integration
- Find the path to
nfton your system:
| |
fwknopdSet the interface (fwknop sniffs via pcap): Edit/etc/fwknop/fwknopd.conf(as root) and set:
| |
- Create
access.conf(fwknop) — nft commands as FW_CMD Create the/etc/fwknop/access.confwith the following template (replace the Base64 keys with those generated later by the client):
sudo nano /etc/fwknop/access.conf
Content:
| |
Set access rights:
| |
- Start / restart
fwknop:
| |
Note: If your nft version does not support timeout for set elements, use
FW_CMDwithout timeout andFW_CMD_DELas cleanup (fwknop can callFW_CMD_DEL), or use a small timer script.
4) WireGuard (Server) — Base Setup
WireGuard is the stable solution for mobile clients. If WireGuard is running, you can reach SSH via the tunnel, completely independent of the public client IP.
4.1 Server Keys & Configuration
| |
Create /etc/wireguard/wg0.conf (Example):
| |
Note: In PostUp/PostDown you can allow certain VPN peers to directly use SSH (e.g., 10.10.10.2). Alternatively, manage access purely via
allowed_sshset entries for public IPs — VPN-tunneled clients do not need this.
Now activate WireGuard:
| |
4.2 nftables + WireGuard
If SSH is to run over the tunnel, ensure that the INPUT chain allows VPN traffic (e.g., accept for iifname wg0 or for the VPN subnet):
| |
5) Port Forwarding on Router (what needs to be forwarded?)
- WireGuard: UDP port (default 51820) → Server LAN IP (e.g., 192.168.178.10).
- SPA / fwknop: SPA packets are sent to a target port (depending on the client call). If you use a fixed port for SPA (e.g., UDP 62201), you must also forward this port to the server. fwknop itself “listens” via pcap, but NAT/Router must let packets through/forward them to the server IP.
We treat router configuration below (Fritz!Box & Speedport).
6) Backup / Rollback (nftables + fwknop)
Backup nftables (important):
| |
Rollback (if locked out):
| |
Rollback script (optional):
| |
7) Quick Checks (before tests)
sudo nft list ruleset— can you see Table / Set / Rules?which nft— check path and adjust inaccess.confif necessary.ip addr— check interface (PCAP_INTF).sudo journalctl -u fwknop-server -f— observe logs.
Part B: Client: SPA Variants & WireGuard Client
Goal: two ways to reliably get access from your laptop/smartphone:
- SPA with resolve-url (practical on the go), or
- GPG-SPA (stronger, IP-independent), or
- WireGuard (recommended for many users / stable connection).
A) SPA with --resolve-url (mobile, simple)
Concept: Client determines its public IP via a web URL (e.g., https://icanhazip.com or better: your own small resolver URL) and packs this IP into the SPA packet. Server opens exactly this IP.
Client Command (simple):
| |
Advantages: works with changing IP; minimal configuration effort.
Disadvantages: Privacy (you query external services for your IP). I recommend hosting your own small resolver URL if possible (e.g., a small CGI with echo $_SERVER['REMOTE_ADDR']).
Server: no change needed, use access.conf as in Part A — fwknop sets %IP% from the SPA.
B) GPG SPA (No binding to Source IP)
Concept: Client signs/encrypts SPA with private GPG key. Server uses public key to check packets. Server trusts packet content, not source address → mobile and secure.
Steps (Short):
- Client (Create GPG Key):
| |
Server (Import Client Public Key): Transfer
gpg --export --armor CLIENT_KEYIDand import on the server in/root/.gnupgor in a dedicated gnupg-home for fwknop.access.conf(GPG Config):
| |
- Client creates SPA with GPG options: see
fwknopmanpage (options such as--use-gpg/--gpg-key— the flags vary with version).
Advantages: very secure, no source-IP issues. Disadvantages: GPG key management required (passphrase, optionally smartcard).
1) Recommendation (mobile & simple) — resolve-url
The client determines its public IP via a web URL (e.g., https://icanhazip.com or a custom resolver URL) and sends an SPA to the server. Simple, robust with changing IPs.
| |
Explanation / Placeholder
-A tcp/22→ Access to SSH (port 22).-D server.example.com→ Hostname or IP of your server (replace!).--use-hmac→ Use HMAC (for symmetric keys).--resolve-url https://icanhazip.com→ Service for determining the public IP; recommend a custom resolver URL for privacy.--verbose→ Output for debugging.
Quick check (after execution):
| |
If keys are available, you can build from them /etc/fwknop/access.conf (server-side), e.g., with the AWK snippet in the tutorial.
2) Recommendation (IP-independent & cryptographic) — GPG-SPA
If you want to access from anywhere without IP binding and use GPG, sign/encrypt the SPA with your GPG key. (Flag names may vary slightly depending on fwknop version — check man fwknop if necessary.)
Example (conceptual — replace CLIENT_KEYID and server.example.com):
| |
Important notes
--use-gpg/--gpg-identityare exemplary flags — the exact option may vary slightly by fwknop version, e.g., –gpg-key / –gpg-id. Check man fwknop or fwknop –help.- On the server, you must import the corresponding public key (e.g., into
/root/.gnupg) and configure access.conf so thatGPG_REMOTE_ID/GPG_HOME_DIRis set.
Quick check on client (GPG):
| |
3) (Optional) One-liner to pre-build a access.conf after key generation
If you have the --key-gen and the ~/.fwknoprc , you can quickly create a access.conf locally (on the client) and then copy it to the server via scp:
| |
Then scp + sudo mv as described in the tutorial, or use the one-liner deploy from the article.
C) WireGuard Client (recommended for permanent mobile usage)
- Generate client key:
| |
- Client config (example wg0-client.conf):
| |
On the server add the peer (public key + AllowedIPs) to the wg0 configuration (or manage via wg set).
Firewall: allow wg0 traffic (see Part A).
Advantages: stable, independent of client IP, performant. Disadvantages: initial setup effort, managing peers.
Router: Port Forwarding for Fritz!Box & Speedport (General Guide)
Note: Menu names / firmware variants differ slightly. If individual menus do not match exactly, search for terms like “Internet”,
“Sharing”, “Port Sharing”, “NAT”, or “Port Forwarding”. Read your model’s manual if in doubt.
1) Fritz!Box (AVM)
- Open the web interface: http://fritz.box
- Log in as Admin.
- Menu: Internet → Sharing (or «Portfreigaben» / «Freigaben»).
- “New Sharing” / “Add Device for Sharing” — select your server (LAN IP).
- Create a port forwarding rule:
- Application name: WireGuard (or fwknop-SPA)
- Protocol: UDP
- External port: 51820 (WireGuard) or e.g., 62201 (SPA), if you want to send SPA over UDP on a specific port
- Internal port: same as external
Save. Test from outside (e.g., phone via mobile network) with nc / wg / fwknop call.
2) Speedport (Deutsche Telekom) — typical workflow
- Open web interface: http://192.168.2.1 (or speedport.ip)
- Log in as Admin.
- Menu: Internet / Sharing / Port Forwarding (exact name varies).
- “Create New Rule”: fill out as with FritzBox:
- Target device / IP: server LAN IP
- External / internal port: 51820 UDP (WireGuard) and optionally SPA port
- Protocol: UDP
Save and test.
Test (from outside)
For WireGuard: try connecting from the mobile device using wg/wg-quick.
For SPA: test fwknop client command with --resolve-url from the mobile network.
Conclusion
To conclude, I will provide 3 possible configuration variants for the mobile connection.
Variant A — Resolve URL (practical, mobile-friendly)
Concept briefly: Client determines its public IP via HTTP(S) (e.g., https://icanhazip.com or better: your own small resolver URL) and packs this IP into the SPA. Server adds this IP to the nft set.
Server — access.conf (nft integration)
Add to /etc/fwknop/access.conf (replace Base64 keys):
| |
Pay attention to the correct
nft-path (which nft) and thatallowed_sshexists.
Client One-liner (mobile)
| |
(Recommendation: replace https://icanhazip.com with a custom resolver URL if privacy is important to you.)
Quick check
- After execution: on the server
sudo nft list set inet filter allowed_ssh→ the IP should appear. - Then
ssh -i ~/.ssh/id_rsa user@server.example.com.
Pros/Cons
- Easy to set up, works with changing IPs.
- − Dependence on an external IP service (or effort to host a mini-resolver yourself).
- Security: encryption via SPA continues; short-lived access (timeout).
Variant B: GPG-SPA (IP-independent, cryptographically strong)
Concept briefly: Client signs/encrypts the SPA with its private GPG key; server verifies/decrypts with the corresponding public key. Server does not need to bind the client source IP (or can additionally use %IP%).

