Setting up port knocking for SSH

Page contents

What is port knocking?

The term "port knocking" refers to a technique where a client must first make connection attempts to a specific sequence of closed ports before being able to access the service/port they want to connect to. This works similar to using a secret knocking sequence for your front door, only opening it for people who knocked the correct sequence.

Until a correct knocking sequence is sent, the real target service is not reachable, thus hidden from port scanning or reconnaissance attacks. When the correct knocking sequence is registered, the port is opened only to the knocking client, no other IP address, keeping it invisible to others.

While port knocking can be a powerful technique to hide active services on your servers, be aware that simple network sniffing or man-in-the-middle attacks render it useless. Its primary advantage is to keep automated scanners and bots away from service like SSH, to prevent automated attacks.

Setting up basic port knocking

We assume you are running a debian-like linux distribution for this example; adjust for other operating systems as needed. We also assume you already have an SSH service installed and running on port 22.

Start by installing knockd, a small but powerful port knocking service and client:

sudo apt install knockd

then we create a basic knocking configuration in:

/etc/knockd.conf

[options]
   logfile = /var/log/knockd.log

[openSSH]
   sequence     = 1234,2345,3456
   seq_timeout  = 15
   command      = /usr/sbin/iptables -A INPUT -s %IP% -p tcp --dport 22 -j ACCEPT
   tcpflags     = syn

[closeSSH]
   sequence     = 6543,5432,4321
   seq_timeout  = 15
   command      = /usr/sbin/iptables -D INPUT -s %IP% -p tcp --dport 22 -j ACCEPT
   tcpflags     = syn

The file defines two sequences, one to open the SSH port and a reversed version to close it again. Clients must first knock on ports 1234, 2345 and 3456 in order within 15 seconds, otherwise they cannot connect to the SSH service.

After the configuration file is created, start the knockd service:

sed -i 's/^START_KNOCKD=.*/START_KNOCKD=1/' /etc/default/knockd
sudo systemctl enable --now knockd

As a last step, configure your firewall to reject connection attempts to the ssh service:

sudo iptables -A INPUT -p tcp --dport 22 -j DROP

Connecting via port knocking

On a client machine that wants to connect to the port knocking-enabled server we just set up, you will also need to install knockd:

sudo apt install knockd

But instead of enabled the knocking service, we only use the knock command contained in the package. The client can now knock on the configured ports:

knock your.server.ip 1234 2345 3456

This opens the SSH port for the client machine, who can now SSH normally into the server. When the access is no longer needed, the client can knock the reverse sequence to close the SSH port again:

knock your.server.ip 6543 5432 4321

Now the port is closed for the client's IP again and they will need to perform the knocking sequence again if they want to connect.

Automatically closing idle ports

Requiring a second manual knocking sequence to close the port can be annoying for users and is easily forgotten, potentially weakening the service protection. To fix these issues, we can write a script to check if a specific IP address is still connected over SSH, closing the port for them once they are gone:

/usr/local/bin/close_ssh_if_unused.sh

#!/bin/bash

IP=$1
CHECK_INTERVAL=180 # check every 3 minutes

while true; do 
    sleep $CHECK_INTERVAL

    # Check if there are any established SSH connections from this IP
    if ! ss -o state established '( dport = :ssh )' | grep -q "$IP"; then
        # No active connections, remove the iptables rule and exit
        /usr/sbin/iptables -D INPUT -s "$IP" -p tcp --dport 22 -j ACCEPT
        exit 0
    fi
done

Don't forget to make the script executable:

sudo chmod +x /usr/local/bin/close_ssh_if_unused.sh

Now we can reduce the knockd config to a single knocking sequence:

/etc/knockd.conf

[openSSH]
   sequence     = 1234,2345,3456
   seq_timeout  = 15
   command      = /usr/sbin/iptables -I INPUT -s %IP% -p tcp --dport 22 -j ACCEPT && /usr/local/bin/close_ssh_if_unused.sh %IP% &
   tcpflags     = syn

Be careful to keep the & at the end, making sure the command keeps running in the background instead of blocking indefinitely. Since the script checks every three minutes, you also only have three minutes for your initial SSH connection, or the port will be closed again.

Using this script ensures that you cannot accidentally forget to close the SSH port after connecting to it, while causing almost no system load.

More articles

Running local text to speech using chatterbox

Multi-language text to speech with optional voice cloning without external services

Modern backup management with restic

Reliable backups from storage to recovery

Web security fuzzing with gobuster

Finding accidentally exposed files