guest@6admin.io:~$

Portknocking and SSH tunneling: the magic tricks before WireGuard

In 2008, this blog published Portknocking: the art of knocking on the door, then automatic combination generation , with SSH tunneling on the neighboring shelf. Fun, pedagogical and genuinely deployed: I saw it in production until 2018 at a hosting provider. In 2016, Jason Donenfeld publishes WireGuard and the question changes nature: not “how to hide the door”, but “why is there a door?”.

The 2009 setup, in fond memory

For latecomers, what the era’s deluxe portknocking involved:

  • a port combination computed from the system date, the port size doubled until past 1024, capped at 65535. Note: the 2009 published scheme was the school version, day-granularity. The real one, the one that ran, was hour and minute and lived in knock/func on the encfs-encrypted USB key, never committed: you do not print your sesame in the user manual and the gobel1/bash repo proves the hygiene blind, kssh, kscp, kbckp and the encfs smount in it, not one byte of the sequence generation;
  • mandatory NTP synchronization on both sides, since the combination is a function of the window (the NTP article from the same blog, put to use);
  • kssh/kscp/kbckp functions in .bash_functions_mgmt, sourcing the combination from knock/func on the encfs-encrypted USB key (smount mounts the .key over .sdata), on demand;
  • iptables on the server opening port 22 on the sequence and closing it behind you.

The real tooling, exhumed from the gobel1/bash repo (2012), shows the complete setup. Encfs first, the exact pair from the October 2008 EncFS post:

## Encfs, the pair from the October 2008 EncFS post (survives in gobel1/bash)
function smount() {
	encfs /home/logout/.key /home/logout/sdata; }
function sumount() {
	fusermount -u /home/logout/sdata; }

## Knocking ssh
function kssh(){
    host=`echo $* | egrep -o '([0-9]{1,3}\.)([0-9]{1,3}\.)([0-9]{1,3}\.)([0-9]{1,3})'`;
    fqdn=`echo $* | egrep -o '([a-zA-Z0-9.-]+\.[a-zA-Z]{2,4})'`;
    if [ -d /home/user/.sdata/coding/bash/ ];then
        . /home/user/.sdata/coding/bash/knock/func;
    else
        smount && . /home/user/.sdata/coding/bash/knock/func;
    fi
    if [[ $host ]];then
        /usr/bin/knock $host $toc && /usr/bin/ssh $*;
    fi
    unset toc host fqdn;
}

## knock rsync bckp
function kbckp(){
    d="$1-`date +%y%m%d`";
    kscp -rp vhalholl.info:www/$1 ~/.bckp/$d \
        && tar -vczf ~/.bckp/$d.tar.gz ~/.bckp/$d && rm -rf ~/.bckp/$d;
}

And the sequence generation, knock/func? Nowhere in the repo, by design: it lived in .sdata/coding/bash/knock/func, on the encrypted key, never committed. Two bonus authenticities: the 2012 version of the functions drifted, smount targets /home/logout/.sdata/ while kssh looks at /home/user/.sdata/ and sumount unmounts /home/user/.sdata/ (three paths for two mounts, the kind of inconsistency that only survives in a personal repo without CI) and the neighboring sshmount points to another post, the March 2009 SSH-FileSystem . As for the October 2008 EncFS article that presented these two functions, here it is: the tooling survives and its documentation with it.

The whole security hung on three prayers: that the sniffer does not see the sequence (he sees it: it is unencrypted network traffic, replayable as long as the window has not changed), that the clock does not drift (it drifts) and that nobody notices the closed port 22 still answers a polite RST. Portknocking never prevented detection: it added a buffer to a problem that asked for none.

Why it was misplaced, without being useless

Portknocking never protected sshd: it mostly reduced scan noise and, honestly, livened up the iptables evenings. In production, at a hosting provider, it stood up because firewalls already delimited zones: the knock was an access ritual between admins, not a defense. Facing a serious attacker, the sequence gets sniffed, replayed and the real control remained… ssh authentication. In other words: the whole machinery protected a layer that was already protecting itself better alone.

WireGuard: the door goes silent

WireGuard (2016) attacks the problem one level lower and that is what makes it misplaced next to the magic tricks:

  • It says nothing. A WireGuard packet to a non-peer is silently ignored: no RST, no SYN/ACK, nothing. A port scan sees a black hole, not a service to crack. Portknocking simulated silence with iptables rules; WireGuard does it in the protocol.
  • Crypto first: the Noise protocol, Curve25519, ChaCha20-Poly1305, BLAKE2s. Identity is a public key, the handshake authenticates before any revelation. No information leaks before authentication, which is the definition of “crypto safe” that portknocking pretended to approach with port numbers.
  • ~4,000 lines of code, auditable in an afternoon, against the knockd + iptables + date scripts + encfs USB key pile.
  • Native roaming: the IP changes, the session holds. SSH tunneling, meanwhile, drops the connection at every network change and TCP-over-TCP takes its revenge in saturated tunnels.

And SSH tunneling? It stays legitimate for what it is: an emergency net, a quick bridge to an internal service, a SOCKS on the fly. What it can no longer be is the access architecture of a fleet: no roaming, no UDP and an exposed sshd attack surface, exactly what WireGuard eliminates while keeping ssh behind it.

The punchy moral

In 2008, we knocked on doors in morse code and we were proud of it. In 2016, we taught the doors to stay quiet: a public key suffices and what is not a peer does not exist. I decommissioned my last production portknocking in 2018; the iptables regained their dignity, NTP stopped being a security component and nobody asked to bring the port combination back. The fun, itself, stays documented: that is the archive’s job.

And that is exactly why services today stay on their standard ports: hiding ssh on an exotic port or knocking before entering only reassures the lazy port scanner and costs fragile plumbing. A service on its standard port, behind strong authentication and a VPN that only talks to peers, is more honest, more auditable and considerably more restful to operate. A 2026 nuance for mischievous minds who would reinvent the hiding place: WireGuard’s silence holds against a port scanner, but the traffic still lets itself be classified by its shape (packet sizes, cadence) by a serious DPI, so silence is not anonymity. The full obituary of the multiplexed 443 is told in the sslh article .

Because of course there is a “but” and it comes smiling: the cat and mouse have recruited engineers. AmneziaWG, a WireGuard fork, adds what the protocol lacks: junk packets of random size before the handshake, customizable message headers (WireGuard’s types 1/2/3/4 replaced with values of your choosing, H1-H4) and padding inside the handshakes (S1/S2), so the shape signature the DPI was hunting no longer exists. The neighboring family does differently the same: udp2raw disguises UDP as TCP with a forged handshake, wstunnel carries WireGuard inside a WebSocket under genuine TLS with a real SNI and Xray REALITY plays the cleverest game by borrowing the TLS handshake of a real site to carry arbitrary traffic. These tools exist, work and have a real job: hostile networks, censorship, corridors where VPNs are forbidden by law rather than by common sense.

The honest limit: it is a race. The DPI answers with active probing (connect and check that the service resembles what it claims to be), statistical detection of padding, classification of junk packets and the disguise engineers answer by copying the real world (REALITY actually forwards the curious visitor to the real site). In an enterprise, though, all this theater is counterproductive: one is not fighting censorship there, one is fighting one’s own auditability and the doorman confusing your junk packets with your builds is your own runner. Camouflage is a survival tool on hostile networks, not a production strategy.

[edit: 2026] The Ansible implementation of all this: the wireguard role .