We hope you find this tutorial helpful. In addition to guides like this one, we provide simple cloud infrastructure for developers. Learn more →

How To Configure Port Knocking Using Only Iptables on an Ubuntu VPS

Posted Jan 17, 2014 39.2k views Firewall Security Ubuntu


Servers that are connected to the internet are subjected to all manners of attacks and probes by malicious users, scripts, and automated bots. It is sometimes a balancing act to secure your server from attacks without affecting legitimate access to your services and resources.

Certain types of services are meant to be visible and consumable to the public internet. An example of this is a web server. Other types of services are typically used by only the system administrator or a select number of individuals and are not meant to be a public resource.

A concept known as port knocking is a way of shielding processes that fit into the latter description. Port knocking works by covering the ports associated with a process behind a firewall until a specific, predetermined sequence of network activity occurs. At this point, the port knocking service reconfigures the firewall to allow access to the protected application.

In a previous article, we discussed how to enable port knocking through a specially designed port knocking service. In this article, we will discuss an alternative method of configuring port knocking.

This method does not rely on an external application to alter the firewall rules. Instead, the iptables firewall can take advantage of a state-tracking module called "recent" to do all of this within the firewall rules themselves.

We will be configuring this on an Ubuntu 12.04 droplet, but any kind of Linux server should operate in a similar manner.

Note: This tutorial covers IPv4 security. In Linux, IPv6 security is maintained separately from IPv4. For example, "iptables" only maintains firewall rules for IPv4 addresses but it has an IPv6 counterpart called "ip6tables", which can be used to maintain firewall rules for IPv6 network addresses.

If your VPS is configured for IPv6, please remember to secure both your IPv4 and IPv6 network interfaces with the appropriate tools. For more information about IPv6 tools, refer to this guide: How To Configure Tools to Use IPv6 on a Linux VPS

Iptables Port Knocking Overview

Before we get into the actual configuration, we will describe how the recent module works and how it allows us to create a port knocking system.

Iptables can load modules with the -m flag. The recent module can track connection states. We can use this to funnel our port connection attempts through a sequence of chains based on if the connecting user had hit each previously needed port.

For our example, we will be blocking our SSH daemon from the internet, represented by our eth0 interface. We will dynamically reconfigure our firewall to allow SSH connections from our computer temporarily once a sequence of ports have been "knocked on" in sequence.

The sequence we will use for this tutorial is:

  • 1111
  • 2222
  • 3333

These values should not be used in a real configuration.

In order for a user to authenticate correctly and cause iptables to expose the SSH daemon to their IP address, they must hit each of these ports in sequence without sending non-sequence traffic in between. If we implement this, we will have successfully created a port knocking system.

Iptables Configuration Strategy

In order to implement the above design, we will use a few different chains.

First, we will accept all of the traffic that we do not wish to be subjected to the port knocking. This includes any public resources, like a web server, as well as established connections and connection coming from the local interface.

Directly afterwards, we will redirect all incoming traffic not handled by the previous rules into a new chain, where we will put the bulk of our rules. This is always a good idea when implementing a large, self-contained rule set because it provides a really easy way to enable or disable the functionality, simply by funneling traffic into or bypassing the new chain.

We will call this chain KNOCKING.

We will also be using some other chains. The recent module allows us to categorize different types of traffic and then check if a connection matches a category that was previously set.

We will use this strategy to flag IP addresses that sent a packet to the first knock target. We will have a rule that checks for that first flag and checks if the second packet is being sent to the second knock target. If it is, it sets another flag indicating that it's gotten two answers right so far. If the second packet doesn't match the second target, it is dropped and the flag is reset.

The additional chains work using this same strategy of checking the appropriate flag and passing it off if it continues on the right path. In the end, once the final packet has been requested in sequence, the SSH daemon is exposed briefly, only for the IP address that knocked successfully. It is then hidden again automatically.

So basically, if we think of our port knocking rules as different gates to get into our service, we will have three gates for our three knocks. This means that there are four different positions that a requesting IP address could be in:

  • initial state: This is the state that all IP addresses are in until they successfully send a packet to the first knock target. This will not be set by the recent module and is simply a way of referring to a client that has no flags set.

  • auth1 state: Addresses that have successfully knocked on the first knock target are flagged as "auth1". From here, the next packet from this host determines if the address will be placed back in the initial state or moved to the "auth2" state.

  • auth2 state: Addresses flagged with this state have successfully knocked on the first and second targets in sequence. The next packet from this host determines whether the host will be set back to the initial state or set to the "auth3" state.

  • auth3 state: Addresses that have been flagged as "auth3" have successfully knocked on all three ports, in order, in the allotted amount of time. If an address flagged as "auth3" attempts to access the service now (in window that is provided), the connection will be accepted. If any other traffic is received, the address will be thrown back to the initial state.

In addition to these states, which will be flags set by the recent module, each gate, or decision point, will be implemented as a chain. The chains can be summarized as follows:

  • GATE1: Determines whether an address in the initial state should be flagged as "auth1".

  • GATE2: Determines whether an address in the "auth1" state should be processed to "auth2" or reset to the "initial" state.

  • GATE3: Determines whether an address in the "auth2" state should be flagged as "auth3" to allow an SSH connection, or reset to the "initial" state.

  • PASSED: This chain is used to briefly open the port for the SSH daemon for clients that have knocked successfully. Any traffic from clients in this chain that is not destined for the SSH daemon is dropped, causing the state to be reset to "initial" again.

As you can see, we have a lot of decision points here. Every kind of traffic in the KNOCKING chain and its sub-chains should be dropped (except the traffic for the SSH daemon from successful knock clients), regardless of whether it matched the correct port. The internal flagging is the only thing that will keep track of successful attempts and this logic is not exposed to the client.

This is necessary for the port knocking implementation to be valid. A person attempting to connect should receive no feedback as to what stage of the process they are in or even if such a mechanism is in place.

Configure Regular Firewall Framework

We will begin by laying down a basic framework for our connections. The strategy we talked about above will be applied to the INPUT chain, which handles all incoming connections.

We will begin by flushing the existing firewall rules so that we can start with a clean slate. Before flushing rules, it is always a good idea to reassert that the default policies in an empty table are "ACCEPT" in order to maintain your current connection:

sudo iptables -P INPUT ACCEPT
sudo iptables -P FORWARD ACCEPT
sudo iptables -P OUTPUT ACCEPT
sudo iptables -F

This will ensure that we are starting with a completely open firewall, which we can begin to restrict.

Before we begin restricting though, we want to add the additional chains that we will be using:

sudo iptables -N KNOCKING
sudo iptables -N GATE1
sudo iptables -N GATE2
sudo iptables -N GATE3
sudo iptables -N PASSED

At this point, we should have eight different chains! We will use all of these except the OUTPUT and FORWARD chains, which do not concern us in this context.

First, we should add the traffic that we don't want to handle with port knocking to the INPUT chain. We can start by accepting all current connections, which will allow our current SSH connection to remain unaffected:

sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

We should also accept all connections from the local machine, since services often need to communicate with one another:

sudo iptables -A INPUT -i lo -j ACCEPT

If you have services that should remain externally and publicly accessible, like a web server, add a rule to allow this type of connection using this format:

sudo iptables -A INPUT -p protocol --dport port -j ACCEPT

In our example, we'll assume that we have a web server running on the default port 80, and we will allow this traffic:

sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT

Now that we have our basic allowed connections configured, we can transfer all traffic that was not handled in the above rules to our KNOCKING chain to do the actual knocking logic:

sudo iptables -A INPUT -j KNOCKING

Configure First Gate

When we are finished configuring our "gates", we will hook them all into the KNOCKING chain to direct the traffic through our logic tests. Before we do that though, we should develop our individual logic units.

We will begin by defining our initial knocking test. When a client connects, we simply need to see if they are sending a packet to our first target. If they are, we flag the client as passing the first test and drop the connection. If they are not, we simply drop the connection.

We will first set the flag on the correct port attempt:

sudo iptables -A GATE1 -p tcp --dport 1111 -m recent --name AUTH1 --set -j DROP

This line does a number of things. To begin with, it is appending a rule to the GATE1 chain. This rule will match when the protocol being used is "tcp" and when the port it is trying to access is "1111", our first knock target.

If this is true, the recent module (called with -m recent), flags the requesting IP address with the name AUTH1 (with the --name AUTH1 --set rule). This is the flag we will use to see if the second knock matches. The packet is dropped after the flag is set, so that the client does not know if anything happened.

Next, we will just drop all other packets, because any information that is sent to this chain is only looking for the matching first packet at this point:

sudo iptables -A GATE1 -j DROP

We now have our first port covered. The rules either match and flag the address if the correct port is requested, or no action is taken and the packet is simply dropped.

Configure Second Gate

The second gate is configured in much the same way as the first. It is slightly more complex though.

First of all, the logic to send the traffic into this chain will exist in the main KNOCKING chain. This chain does not have to check whether the flag set by the first gate matches, because that will have already happened.

However, it does have to remove any flags before it begins to process. If it did not remove the flag, then an address could be flagged with different names, which could result in the address successfully knocking by just scanning your ports three times. This is definitely not what we want.

We will use the recent module again in a preliminary rule, this time just to clear the name:

sudo iptables -A GATE2 -m recent --name AUTH1 --remove

This is a processing rule, so we don't make any decisions or jumps. We simply strip the current flag (the one that would have allowed the traffic into this chain) and send it to the next rule.

Now that the address is past our first rule in this chain, it is in a clean state with no flags. At this point, we check whether this connection attempt is a correct match for the next port target:

sudo iptables -A GATE2 -p tcp --dport 2222 -m recent --name AUTH2 --set -j DROP

This is handled in much the same way as the first gate. We simply set the AUTH2 flag, indicating that the requesting address passed the second test, if the correct port was knocked on. The flag is set and the packet is again dropped, giving the client no indication of their progress.

The next rule may seem a bit strange at first.

sudo iptables -A GATE2 -j GATE1

You may assume that just dropping the packet at this point is the logical thing to do. However, this would lead to an awkward situation in one specific case.

If we had a "drop all" rule here, and the packet sent at this point matches the first knock target, it would not be registered as a beginning of the knock sequence.

For instance, if our client accidentally hits the first port twice, it won't register as correct because the firewall would see the sequence like this:

  • First port hit. Firewall marks first test passed. Will check for second port next.
  • First port hit. Does not match second port rule. Sequence is reset. Will check for first port next.
  • Second port hit. Does not match first port rule. Sequence is reset. Will check for first port next.
  • Third port hit. Does not match first port rule. Sequence is reset. Will check for first port next.

As you can see, the first, second, third sequence has been completed, but the firewall has gotten confused about what rules it should be checking. In this scenario, the client would have to send a dummy request before beginning the real sequence just to reset the chain if a mistake was made.

To avoid this situation, we will not just drop the packet at this point and be done with it. Instead, we will leverage the GATE1 chain that we already configured. As we demonstrated above, we can simply send the traffic that did not match the second knock target to the first gate again:

sudo iptables -A GATE2 -j GATE1

This will cause the client's position in the sequence to be reset in one of two ways. If the request is for the first port, The sequence is restarted to as a successful first knock. If it is not the first port, it is dropped as usual. This avoids the above situation.

Configure Third Gate

We can use what we learned from the second gate to implement the third gate in the same exact way.

First, we want to clear all flags that have been given to our address so that this run through the chains will set the correct state without stale flags from before:

sudo iptables -A GATE3 -m recent --name AUTH2 --remove

Next, we will test whether the connection attempt matches the third knock target. If it does, we set the AUTH3 flag, which indicates that the client successfully completed all required knocks. As per usual, we drop the packet afterwards.

sudo iptables -A GATE3 -p tcp --dport 3333 -m recent --name AUTH3 --set -j DROP

From here, we once again send the traffic that did not match the third knock target back to the first gate to see if it should count as a successful first knock to restart the sequence:

sudo iptables -A GATE3 -j GATE1

At this point, clients who've completed the correct knocking sequence should be flagged with AUTH3, which will let us easily open the service for them in the PASSED chain.

Configure Passed Chain

This chain is used to open the SSH daemon for 30 seconds to the client that successfully knocked the correct sequence.

We start this in the same way as the others. The SSH daemon will only be available if the next packet sent from the client is requesting it. This forces people who are randomly attempting to get past the knocking to attempt an SSH connection in between each attempt.

First, we do the usual flag resets:

sudo iptables -A PASSED -m recent --name AUTH3 --remove

Next, we accept SSH connections from the users who have made it into this chain:

sudo iptables -A PASSED -p tcp --dport 22 -j ACCEPT

Once again, we send all traffic that does not match back through our first chain to see if it matches the first port knock target:

sudo iptables -A PASSED -j GATE1

Now, we have all of our sub-chains configured but our general KNOCKING chain, which will pass traffic into these individual chains.

Configure the Knocking Chain

Now that we have all of our sub-chains configured, we can hook them into our general KNOCKING chain and create the logic for how to pass off the traffic.

First, we will pass traffic from clients that have successfully completed all the knocks directly into the PASSED chain.

We have some options though. We can implement a time limit to only give the successful client a 30 second window to connect to the daemon. After that, the rule will no longer match successfully.

sudo iptables -A KNOCKING -m recent --rcheck --seconds 30 --name AUTH3 -j PASSED

Next, we will test for each of the other flags from the most restrictive to the least. We can add a 10 second time limit before the previous knock expires also. This will require our clients to complete the next stage of the knock within 10 seconds, and then connect to the SSH daemon in another 30 seconds.

sudo iptables -A KNOCKING -m recent --rcheck --seconds 10 --name AUTH2 -j GATE3
sudo iptables -A KNOCKING -m recent --rcheck --seconds 10 --name AUTH1 -j GATE2

Now, we want to send all traffic that has not matched so far back to GATE1, as usual. This will catch any attempt for the first knock:

sudo iptables -A KNOCKING -j GATE1

This will basically set a default drop policy for our KNOCKING chain by implicitly adding the GATE1 logic to the end of the KNOCKING chain.

At this point, we have all of our knocking chains in place. We have added some additional structure to compartmentalize our logic. The whole KNOCKING chain structure and its sub-chains are hooked up to our regular input chain.

At this point, our port knocking mechanism is configured. It's time to test it out.

Test Our Port Knocking

There are a number of utilities that can be used to generate the TCP packets that we are requiring for our port knocking configuration. We will use the nmap command, because it is present on most systems by default.

Nmap uses TCP packets by default. We need to tell it to drop to host discovery portion of its default behavior though so that those packets do not interfere with our knocking. In addition, we want our connection to time out after only a second, so that we can continue on to the next knock.

With these requirements, a single knock may look like this. We will use our first knock target as an example:

nmap -Pn --host_timeout 201 --max-retries 0 -p 1111 your_server

Therefore, our entire knock sequence could be represented by these commands:

nmap -Pn --host_timeout 201 --max-retries 0 -p 1111 your_server
nmap -Pn --host_timeout 201 --max-retries 0 -p 2222 your_server
nmap -Pn --host_timeout 201 --max-retries 0 -p 3333 your_server

We would then have 30 seconds to connect with our SSH client.

We can take advantage of some really basic bash scripting to automate this a bit. We can use a "for" loop to iterate through our port sequence and then pass it off to the SSH client:

for x in 1111 2222 3333; do nmap -Pn --host_timeout 201 --max-retries 0 -p $x your_server && sleep 1; done && ssh user@your_server

This will knock on the first port, wait for a second, knock on the next, and so on until the sequence is complete. It will then attempt to connect the the server's SSH daemon.

We can put this in a file to clean it up a bit. We'll call it knock_client. Create it in a text editor:

nano knock_client

ports="1111 2222 3333"

for x in $ports
    nmap -Pn --host_timeout 201 --max-retries 0 -p $x $host
    sleep 1
ssh user@${host}

Save and close the file.

Make the file executable with this command:

chmod 755 knock_client

Now, we can connect to our server by typing:


Now that we have verified that our rules work as intended, we can make our firewall rules persistent by downloading a separate package on our server:

sudo apt-get install iptables-persistent

We can have our rules applied at boot by enabling this service:

sudo service iptables-persistent start


By now, you should have a fully operational port knocking system in place using nothing but the functionality included in the iptables firewall. This has a few advantages. First of all, iptables is very commonly used and is audited for security problems often. This means that as long as your rules do what you think they do, it should be pretty secure.

Another advantage this configuration offers over a separate knocking daemon is that there is no opportunity of the knocking service to fail and leave you locked out of your server. If the iptables service fails, you at least will be able to enter your server to repair.

By Justin Ellingwood


Creative Commons License