Port Knocking
Single Packet Authorization (SPA) compatible with fwknop: a protected port is normally unreachable from outside. The client first sends one encrypted, HMAC-authenticated, replay-protected knock packet; once Gately has verified it, the port is opened for that source address only, for a limited time. A typical use is protecting direct SSH access: scp, IDE remote development and port forwarding, which the Web Terminal cannot cover, can then be used safely from the Internet, while scanners and brute-force attempts never see the port.
Features
- Compatible with fwknop clients: use the official fwknop client (Linux, macOS, WSL) directly. Keys use the same format as the
KEY_BASE64andHMAC_KEY_BASE64generated byfwknop -k, and the rule page can generate the client command and a~/.fwknoprcsection in one click; - Secure: packets are encrypted with AES-256-CBC, and the HMAC (SHA-256 / 384 / 512) is checked before decryption, so a packet with a wrong key never enters the decryption step; each packet can be used only once (replay protection), and a timestamp outside the validity period (120 seconds by default) is rejected; you can restrict the user name and require packets to state the address to open;
- Firewall blocking: protected ports are written into the system firewall (nftables, or iptables + ipset when the kernel has no nf_tables), and only addresses that knocked successfully are let through, which protects services such as the local sshd that do not pass through Gately;
- Gately entries open by knock: rules of Port Forwarding, Protocol Routing and the built-in forwarding of STUN, as well as single routes in Protocol Routing, can be set to "Require knock". Gately decides by itself, no firewall privileges are needed, and it works on Windows, macOS and containers without
NET_ADMIN; - Only one port open: Protocol Routing can recognize knock packets sent over TCP (
fwknop -P tcp), so a single port both receives knocks and opens routes such as SSH according to the knock result; - Works with Intrusion Prevention: rejected knocks publish a "Port knock rejected" event, the built-in "Port knock failures" template of Intrusion Prevention can ban sources by count, and knocks from banned addresses are dropped immediately;
- A list of current grants (remaining time, revocable); successful and rejected knocks are both recorded in the run log and can be subscribed to in the Notification Center.
Quick start: protect local SSH
- Open "Port Knocking" and add a rule: enter
tcp/22as the port, turn on "Firewall blocking", and leave the keys empty to have them generated; - Click "Client" on the rule card, enter the server's domain name or public IP, and copy the command line or the
~/.fwknoprcsection; - Install fwknop on the client:
brew install fwknopon macOS,apt install fwknop-clienton Debian / Ubuntu, and inside WSL on Windows; - Connect over SSH within 30 seconds after knocking:
fwknop -n gately-ssh && ssh user@example.comKnock packets are sent to UDP port 62201 by default. When Gately sits behind a router, forward UDP 62201 together with the protected port to the host running Gately. With "Auto-open firewall" on, the local firewall (OpenWrt fw4, firewalld, ufw and so on) opens the knock port automatically, and also opens the protected port once blocking is active, so only the knock decides whether a connection works.
Avoid locking yourself out
- "LAN knock-exempt" is on by default: private addresses, link-local addresses and the public IPv6 ranges of this machine's interfaces (the interface prefix when it is /64 or shorter; only the machine's own prefix for the /124 or /128 common on cloud servers) need no knock, and you can add a knock-exempt allowlist in the settings. Carrier-grade NAT addresses (100.64.0.0/10) are not included, because other customers of the same ISP use them too; Tailscale users can add 100.64.0.0/10 to the allowlist;
- With firewall blocking on, established connections are unaffected, and they also stay up after a grant expires;
- When Gately stops normally it removes the blocking rules; when it is killed (
kill -9,docker rm -f) the rules stay until Gately starts again. Make sure you can still log in to the machine from the LAN or another route.
Rules
| Field | Description |
|---|---|
| Ports | Ports the client may ask to open, such as tcp/22 and udp/51820, up to 16. Every port in the client's -A argument must be in this list, otherwise the whole knock is rejected |
| Firewall blocking | When on, these ports are blocked in the system firewall and only addresses that knocked successfully are let through; when off the firewall is untouched and the rule is only referenced by "Require knock" entries |
| Encryption key / HMAC key | base64-encoded; leave empty to generate (32 / 64 bytes, same as fwknop -k), or enter the keys of an existing fwknop server. Copying a rule generates new keys; editing, disabling or deleting a rule revokes all grants it produced |
| HMAC algorithm | SHA-256 (fwknop default), SHA-384, SHA-512; must match the client's --hmac-digest-type |
| Access duration | How long new connections may be opened after a successful knock, 30 seconds by default |
| Client max duration | Upper limit when the client asks for a longer duration with -f (300 seconds by default); 0 ignores the duration requested by the client. A request shorter than the access duration is always honored |
| Restrict user name | Accept only knocks whose user name in the packet equals this value (fwknop sends the local login name by default; set it with -U) |
| Require address | Reject packets without an address (fwknop -s); the client must state the address to open with -R or -a |
A rule referenced by Port Forwarding, Protocol Routing or STUN cannot be deleted; the card lists the referencing entries.
Which address is opened
-R(recommended): the client first looks up its own public IPv4 and writes it into the packet, so even an intercepted packet cannot be used for another address;-s: the packet carries no address and the source address of the packet is opened. Someone who intercepts the packet may replay it first from their own address, so a rule can turn on "Require address" to reject such packets;-a address: opens the given address, for example to knock on behalf of another device.
The fwknop 2.6 client can only send knock packets over IPv4 and open IPv4 addresses, so the server must be reachable over IPv4; Gately itself also accepts knock packets sent over IPv6 and opens their source address.
Global settings
- UDP knock port: 62201 by default (the default port of the fwknop client); enter 0 to stop listening on UDP and accept only knocks sent through Protocol Routing. Nothing is listened on while no rule is enabled;
- Auto-open firewall: see above;
- LAN knock-exempt / knock-exempt allowlist: affects firewall blocking only; "Require knock" entries are controlled by their own IP lists;
- Packet validity: the maximum difference between the packet timestamp and this machine's time, 120 seconds by default; the clocks of both client and server must be accurate.
Firewall blocking
| Environment | Firewall blocking | Gately entries open by knock |
|---|---|---|
Linux (root or CAP_NET_ADMIN, with nftables) | Available | Available |
Docker (host network + cap_add: NET_ADMIN) | Available, rules are written to the host | Available |
| Linux without nf_tables, such as Synology DSM | Uses iptables (legacy) + ipset, built into the official Docker image | Available |
| Windows, macOS, FreeBSD, Linux without privileges | Unavailable, the reason is shown at the top of the page | Available |
Gately creates its own inet gately_knock table in nftables and leaves existing system rules untouched. The prerouting chain (priority -160, after connection tracking and before destination NAT) passes packets sent to protected ports to the gate chain, which in order accepts traffic not destined to this machine (connections forwarded through it to other devices are unaffected), the loopback interface, established connections, TCP packets that do not start a connection, and address + protocol + port entries from the knock-exempt networks and the knock-grant set, and drops everything else. Because the decision is made in prerouting, a port mapped to a Docker container is blocked by its port before mapping. Grants are written as set elements with a timeout and removed by the kernel when they expire.
Without nf_tables, the iptables mangle table is used instead: PREROUTING jumps to GATELY_KNOCK, with one ipset (such as gkn4_tcp_22, with timeouts) per protected port. If the table or the jump is cleared by a firewall reload, nft flush ruleset and the like, it is rewritten within 10 seconds together with the grants that are still valid. "View firewall rules" on the page shows the rules currently written.
SSH arriving through port forwarding
Connections forwarded by Gately Port Forwarding to local 127.0.0.1:22 come from the loopback interface and are not blocked by the firewall. In that case choose "Require knock" on the port forwarding rule, or protect the outward-facing forwarded port with firewall blocking.
Notes
- Docker port mapping rewrites the source address: when Gately runs on a bridge network and the knock port is mapped with
-p 62201:62201/udp, and Docker's userland-proxy handles that mapping (for example over IPv6 or the loopback address, or with the iptables integration turned off), Gately sees the Docker gateway as the packet source, so a-spacket grants the gateway address instead of the real client, which may even open the port to every connection forwarded through that gateway. Run Gately on the host network (firewall blocking needs it anyway), or have clients state the address with-R/-aand turn on "Require address"; - The trade-off of blocking only SYN: to avoid cutting connections that were established before blocking was enabled but are not yet tracked by connection tracking, TCP drops only the SYN packets that start a connection and lets packets without SYN through. In theory, a system with SYN cookies enabled (
net.ipv4.tcp_syncookies) accepts a bare ACK that guesses a valid cookie while its SYN queue overflows and sets up a connection; the cookie is a 32-bit initial sequence number computed from a kernel secret and the time, so guessing it is practically impossible; where security requirements are very strict you can disable SYN cookies (sysctl net.ipv4.tcp_syncookies=0).
Gately entries open by knock
In the "IP access control" of Port Forwarding, Protocol Routing and STUN (built-in forwarding) you can pick a knock rule as "Require knock": a source address needs an unexpired grant of that rule to open a new connection (for UDP, when a new session is created: after the grant expires, existing sessions such as WireGuard keep being forwarded, the same as established connections under firewall blocking; allowlists / blocklists are still checked per packet), and other connections are closed at once; when set together with an allowlist / blocklist, both must be satisfied. A knock rule can turn off "Firewall blocking" and serve only these entries.
Protocol Routing can also set "Require knock" on a single route, for example on port 443 the HTTPS route is open to everyone while the SSH route is open only to addresses that knocked successfully.
Only one port open
Add a "Port Knocking" route (protocol spa) to a Protocol Routing rule, and the client sends the knock packet over TCP to the same port:
fwknop -A tcp/443 -P tcp -p 443 -R -D example.com --use-hmac \
--key-base64-rijndael <encryption key> --key-base64-hmac <HMAC key>
ssh -p 443 user@example.com- The knock route hands the packet to the Port Knocking module; it neither forwards nor replies. The port in
-Amust be in the knock rule's port list (for exampletcp/443); - When the whole rule requires a knock, sources that have not knocked can use only the knock route, and after knocking they can use the other routes and the default target;
- Knock packets are recognized only when the rule has a knock route, so protocol detection of other rules is unaffected;
- The UDP port of the knock rule can be set to 0 so that only the Protocol Routing port is open to the outside;
- The Protocol Routing ports that carry a knock route and the UDP knock port cannot be listed in a rule with firewall blocking (checked on save and on enable, since blocking them would stop the knock packets themselves), and they are always left out when the firewall rules are generated.
Works with Intrusion Prevention
Rejected knocks publish a "Port knock rejected" event (its via field is udp or tcp), at most once every 5 seconds per source. The source address of a UDP packet can be forged, so to keep attackers from using Intrusion Prevention to ban arbitrary IPs, UDP packets that fail HMAC authentication are only counted and publish no event; an event is published only when the packet passed HMAC authentication (it came from a client holding the key) but was refused by the rule (decryption failure, replay, expiry, request for an unauthorized port, wrong user name and so on). Packets sent over a Protocol Routing TCP port come from a completed handshake and cannot be spoofed, so an HMAC mismatch there also publishes an event. Create a jail in Intrusion Prevention from the "Port knock failures" template (by default 5 events within 10 minutes ban for 1 day), and sources that keep sending forged packets are banned; knocks from banned addresses are dropped immediately. Data that is not in knock format (port scans, other protocols) is only counted and publishes no event.
Before authentication, packets are rate-limited per source range (at most 10 per second for each IPv4 /24 and each IPv6 /64) and then by a global limit of 200 per second; the rest are dropped, so a flood of forged packets cannot use up the CPU and a flood from one source does not crowd out normal knocks from others. Packets that fail HMAC authentication are not logged one by one; they are summarized once a minute as "M invalid knock packets from N sources".
Limitations
- Only fwknop's Rijndael (AES) CBC mode with HMAC is supported; GnuPG, the legacy IV mode and packets without HMAC are not;
- Command messages (
fwknop -C) are not executed, and NAT access requests (-N) are not supported; - Knock grants are kept in memory, so after Gately restarts you need to knock again (established connections are unaffected). Replay records are also kept only in memory, so after a restart any packet whose timestamp is earlier than the start is refused; if the client clock is behind the server, a knock in the first few seconds after a restart may be refused, and knocking again works.