
IP Tables as Firewall
/ 9 min read
Updated:Table of Contents
Overview
In this post, I want to share my IP tables notes based on my recently faced firewall and network connectivity issues on an cloud VM instance that costed me several hours to learn.
Context: After I created the VM instance from a cloud provider and
accessed over SSH, I wanted to change the SSH port. I edited the
sshd_config, added the new port to the cloud providers firewall
settings. Restarted the ssh service. But I couldn’t connect to that
TCP port.
After digging into the IPTables, I learned the basics and toke notes for some details.
📚 IPs and ports in this post is randomized for security purposes.
IPTable basics
-
Check the current iptables chains of your machine.
Terminal window sudo iptables -L -n -v -
If your VM is a fresh Debian machine, the output probably will look like:
Terminal window Chain INPUT (policy ACCEPT 0 packets, 0 bytes)pkts bytes target prot opt in out source destinationChain FORWARD (policy ACCEPT 0 packets, 0 bytes)pkts bytes target prot opt in out source destinationChain OUTPUT (policy ACCEPT 0 packets, 0 bytes)pkts bytes target prot opt in out source destination- These three chains are the main chains you should know.
- In the chains above, there is no rules defined at all.
- The state in between parentheses defines the default rule if nothing matches.
- So the iptables are configured to accept any incoming, forwarding or outgoing requests.
- If I add a rule for SSH and reject all remaining requests with the commands below:
Terminal window sudo iptables -I INPUT 1 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPTTerminal window sudo iptables -I INPUT 2 -p tcp --dport 432 -j ACCEPTTerminal window sudo iptables -A INPUT -j REJECT --reject-with icmp-host-prohibited- The iptables becames:
Terminal window Chain INPUT (policy ACCEPT 0 packets, 0 bytes)pkts bytes target prot opt in out source destination62 4672 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:22199 246K REJECT 0 -- * * 0.0.0.0/0 0.0.0.0/0 reject-with icmp-host-prohibitedChain FORWARD (policy ACCEPT 0 packets, 0 bytes)pkts bytes target prot opt in out source destinationChain OUTPUT (policy ACCEPT 0 packets, 0 bytes)pkts bytes target prot opt in out source destination
Understanding the basics of the iptables output.
- I want to explain the output based on this output:
Chain INPUT (policy ACCEPT 0 packets, 0 bytes) pkts bytes target prot opt in out source destination 25 1820 ACCEPT 0 -- * * 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED 62 4672 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:22 199 246K REJECT 0 -- * * 0.0.0.0/0 0.0.0.0/0 reject-with icmp-host-prohibited
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes) pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes) pkts bytes target prot opt in out source destination-
As you can see, the only INPUT chain contains rules.
pkts: Total package count that matched the rule.bytes: Total bytes that matches the rule.target: Defines the ACCEPT or REJECT rule.prot: Defines the network protocol. Most common protocols:0: Matches all protocols.1: ICMP6: TCP17: UDP
in: Defines the incoming network interface.outDefines the outgoing network interface.source: Defines the incoming IP address range.destination: Defines the outgoing IP address range.no name: The last column has no name but defines extras.
-
The kernel starts matching incoming requests with the rules. The match is sequential. So the order of the rules is important.
-
If we analyze the first rule:
- If you don’t have this rule, your server can talk out, but it will be “deaf” to the replies.
- This accept “responses”.
-
If we analyze the second rule:
Terminal window pkts bytes target prot opt in out source destination62 4672 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:22- This rules accepts incoming connections (
INPUTchain) from0.0.0.0/0(any IP address) to0.0.0.0/0(any IP address) TCP connections (prot 6) to port 22 (dpt:22). - In basic, this rule accepts incoming connections for TCP 22 from any IP address.
- This rules accepts incoming connections (
-
If we analyze the third rule:
Terminal window pkts bytes target prot opt in out source destination199 246K REJECT 0 -- * * 0.0.0.0/0 0.0.0.0/0 reject-with icmp-host-prohibited- This rules REJECT incoming connections (
INPUTchain) from0.0.0.0/0(any IP address) to0.0.0.0/0(any IP address). - In basic, this rule rejects incoming connections for all connections.
- Thanks to sequential matching, if a request doesn’t match to any rules before this rule, it is rejected.
- So if I run a web application on TCP port 80, I won’t be able to connect to it with this iptables rules.
- This rules REJECT incoming connections (
Docker Modifies the IPTables
-
In 2026, you will most likely install Docker into the VM. After installing the Docker, the iptables will be like the following:
- The below outputs are from a fresh Debian generic cloud image.
Terminal window Chain INPUT (policy ACCEPT 0 packets, 0 bytes)pkts bytes target prot opt in out source destination37601 103M ACCEPT 0 -- * * 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED268 17944 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:22577 278K REJECT 0 -- * * 0.0.0.0/0 0.0.0.0/0 reject-with icmp-host-prohibitedChain FORWARD (policy DROP 0 packets, 0 bytes)pkts bytes target prot opt in out source destination0 0 DOCKER-USER 0 -- * * 0.0.0.0/0 0.0.0.0/00 0 DOCKER-FORWARD 0 -- * * 0.0.0.0/0 0.0.0.0/0Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)pkts bytes target prot opt in out source destinationChain DOCKER (1 references)pkts bytes target prot opt in out source destination0 0 DROP 0 -- !docker0 docker0 0.0.0.0/0 0.0.0.0/0Chain DOCKER-BRIDGE (1 references)pkts bytes target prot opt in out source destination0 0 DOCKER 0 -- * docker0 0.0.0.0/0 0.0.0.0/0Chain DOCKER-CT (1 references)pkts bytes target prot opt in out source destination0 0 ACCEPT 0 -- * docker0 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHEDChain DOCKER-FORWARD (1 references)pkts bytes target prot opt in out source destination0 0 DOCKER-CT 0 -- * * 0.0.0.0/0 0.0.0.0/00 0 DOCKER-INTERNAL 0 -- * * 0.0.0.0/0 0.0.0.0/00 0 DOCKER-BRIDGE 0 -- * * 0.0.0.0/0 0.0.0.0/00 0 ACCEPT 0 -- docker0 * 0.0.0.0/0 0.0.0.0/0Chain DOCKER-INTERNAL (1 references)pkts bytes target prot opt in out source destinationChain DOCKER-USER (1 references)pkts bytes target prot opt in out source destination- 📚 Docker creates custom chains in the iptables for port forwarding and bridge networks.
- ⚠️ These custom rules mostly overwrites the custom rules so you should be careful.
-
If you run a Nginx container with port forwarding, you will something different.
Terminal window sudo docker run -d --rm -p 80:80 nginx -
Check the running container:
Terminal window berk@iptables-test:~$ sudo docker psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES918983a0abf8 nginx "/docker-entrypoint.…" 13 seconds ago Up 12 seconds 0.0.0.0:80->80/tcp, [::]:80->80/tcp vigorous_johnson -
Check the iptables again:
Terminal window berk@iptables-test:~$ sudo iptables -L -n -vChain INPUT (policy ACCEPT 0 packets, 0 bytes)pkts bytes target prot opt in out source destination43300 169M ACCEPT 0 -- * * 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED268 17944 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:22590 279K REJECT 0 -- * * 0.0.0.0/0 0.0.0.0/0 reject-with icmp-host-prohibitedChain FORWARD (policy DROP 0 packets, 0 bytes)pkts bytes target prot opt in out source destination0 0 DOCKER-USER 0 -- * * 0.0.0.0/0 0.0.0.0/00 0 DOCKER-FORWARD 0 -- * * 0.0.0.0/0 0.0.0.0/0Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)pkts bytes target prot opt in out source destinationChain DOCKER (1 references)pkts bytes target prot opt in out source destination0 0 ACCEPT 6 -- !docker0 docker0 0.0.0.0/0 172.17.0.2 tcp dpt:800 0 DROP 0 -- !docker0 docker0 0.0.0.0/0 0.0.0.0/0Chain DOCKER-BRIDGE (1 references)pkts bytes target prot opt in out source destination0 0 DOCKER 0 -- * docker0 0.0.0.0/0 0.0.0.0/0Chain DOCKER-CT (1 references)pkts bytes target prot opt in out source destination0 0 ACCEPT 0 -- * docker0 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHEDChain DOCKER-FORWARD (1 references)pkts bytes target prot opt in out source destination0 0 DOCKER-CT 0 -- * * 0.0.0.0/0 0.0.0.0/00 0 DOCKER-INTERNAL 0 -- * * 0.0.0.0/0 0.0.0.0/00 0 DOCKER-BRIDGE 0 -- * * 0.0.0.0/0 0.0.0.0/00 0 ACCEPT 0 -- docker0 * 0.0.0.0/0 0.0.0.0/0Chain DOCKER-INTERNAL (1 references)pkts bytes target prot opt in out source destinationChain DOCKER-USER (1 references)pkts bytes target prot opt in out source destination -
In the INPUT chain, there is still no rule for accepting TCP 80. But however, the Nginx container is accepting and responding to the requests
Terminal window curl -v telnet://192.168.1.115:80* Trying 192.168.1.115:80...* Connected to 192.168.1.115 (192.168.1.115) port 80 (#0)
And here we are digging some details.
- Before a request is matching a rule, the kernel first checks the PREROUTING chain. Docker creates a NAT PREROUTING rule for container requests.
- To check the NAT PREROUTING rules, run:
sudo iptables -t nat -L PREROUTING -n -v --line-numbers- The output will look like:
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)num pkts bytes target prot opt in out source destination1 33 988 DOCKER 0 -- * * 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL-
This makes sure that all incoming requests are forwarded to the Docker NAT PREROUTING chain before the default INPUT chain.
-
If you want to check the DOCKER NAT chain, run:
Terminal window sudo iptables -t nat -L DOCKER -n -v --line-numbers -
The output will be like:
Terminal window Chain DOCKER (2 references)num pkts bytes target prot opt in out source destination1 2 120 DNAT 6 -- !docker0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 to:172.17.0.2:80- This rule makes the port forwarding magic.
- If the request is not coming from
docker0interface and the port isTCP 80, it forwards the NATs the requests for172.17.0.2:80. - The
172.17.0.2:80IP address is the internal Docker IP address of the Nginx container. - If no request matches this Docker NAT rules, the request goes goes back to the default INPUT chain.
- So even if there is no ACCEPT rule for TCP port 80 for the NGINX container, Docker creates a backdoor in the iptables to control the requests.
Quick Commands
- Summary information:
- Even if there is no ACCEPT rules for the containers you run, Docker creates a backdoor in the iptables to control and allow the requests.
- If you create or update a system service (like SSH) use a port that is not listed in the INPUT chain, the iptables will reject the connection if the latest rule is reject.
- Check the PREROUTING table:
sudo iptables -t nat -L PREROUTING -n -v --line-numbers- Check the NAT rules:
sudo iptables -t nat -L DOCKER -n -v --line-numbers- Check all the chains:
sudo iptables -L -n -v- Check all the ACCEPT rules from all chains:
sudo iptables -L -n -v | grep ACCEPT- To allow responses in the INPUT chain:
sudo iptables -I INPUT 1 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT- To allow port 22:
sudo iptables -I INPUT 2 -p tcp --dport 22 -j ACCEPT- To reject all the remaining connections:
sudo iptables -A INPUT -j REJECT --reject-with icmp-host-prohibited-
To delete a firewall rule:
- Check the firewall rules by lines
Terminal window sudo iptables -L INPUT --line-numbers -n- Delete the INPUT
Terminal window sudo iptables -D INPUT 6