IP Tables as Firewall
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.
sudo iptables -L -n -v -
If your VM is a fresh Debian machine, the output probably will look like:
Chain INPUT (policy ACCEPT 0 packets, 0 bytes) pkts bytes target prot opt in out source destination 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- 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:
sudo iptables -I INPUT 1 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPTsudo iptables -I INPUT 2 -p tcp --dport 432 -j ACCEPTsudo iptables -A INPUT -j REJECT --reject-with icmp-host-prohibited- The iptables becames:
Chain INPUT (policy ACCEPT 0 packets, 0 bytes) pkts bytes target prot opt in out source destination 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
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:
pkts bytes target prot opt in out source destination 62 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:
pkts bytes target prot opt in out source destination 199 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.
Chain INPUT (policy ACCEPT 0 packets, 0 bytes) pkts bytes target prot opt in out source destination 37601 103M ACCEPT 0 -- * * 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED 268 17944 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:22 577 278K REJECT 0 -- * * 0.0.0.0/0 0.0.0.0/0 reject-with icmp-host-prohibited Chain FORWARD (policy DROP 0 packets, 0 bytes) pkts bytes target prot opt in out source destination 0 0 DOCKER-USER 0 -- * * 0.0.0.0/0 0.0.0.0/0 0 0 DOCKER-FORWARD 0 -- * * 0.0.0.0/0 0.0.0.0/0 Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes) pkts bytes target prot opt in out source destination Chain DOCKER (1 references) pkts bytes target prot opt in out source destination 0 0 DROP 0 -- !docker0 docker0 0.0.0.0/0 0.0.0.0/0 Chain DOCKER-BRIDGE (1 references) pkts bytes target prot opt in out source destination 0 0 DOCKER 0 -- * docker0 0.0.0.0/0 0.0.0.0/0 Chain DOCKER-CT (1 references) pkts bytes target prot opt in out source destination 0 0 ACCEPT 0 -- * docker0 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED Chain DOCKER-FORWARD (1 references) pkts bytes target prot opt in out source destination 0 0 DOCKER-CT 0 -- * * 0.0.0.0/0 0.0.0.0/0 0 0 DOCKER-INTERNAL 0 -- * * 0.0.0.0/0 0.0.0.0/0 0 0 DOCKER-BRIDGE 0 -- * * 0.0.0.0/0 0.0.0.0/0 0 0 ACCEPT 0 -- docker0 * 0.0.0.0/0 0.0.0.0/0 Chain DOCKER-INTERNAL (1 references) pkts bytes target prot opt in out source destination Chain 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.
sudo docker run -d --rm -p 80:80 nginx -
Check the running container:
berk@iptables-test:~$ sudo docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 918983a0abf8 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:
berk@iptables-test:~$ sudo iptables -L -n -v Chain INPUT (policy ACCEPT 0 packets, 0 bytes) pkts bytes target prot opt in out source destination 43300 169M ACCEPT 0 -- * * 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED 268 17944 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:22 590 279K REJECT 0 -- * * 0.0.0.0/0 0.0.0.0/0 reject-with icmp-host-prohibited Chain FORWARD (policy DROP 0 packets, 0 bytes) pkts bytes target prot opt in out source destination 0 0 DOCKER-USER 0 -- * * 0.0.0.0/0 0.0.0.0/0 0 0 DOCKER-FORWARD 0 -- * * 0.0.0.0/0 0.0.0.0/0 Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes) pkts bytes target prot opt in out source destination Chain DOCKER (1 references) pkts bytes target prot opt in out source destination 0 0 ACCEPT 6 -- !docker0 docker0 0.0.0.0/0 172.17.0.2 tcp dpt:80 0 0 DROP 0 -- !docker0 docker0 0.0.0.0/0 0.0.0.0/0 Chain DOCKER-BRIDGE (1 references) pkts bytes target prot opt in out source destination 0 0 DOCKER 0 -- * docker0 0.0.0.0/0 0.0.0.0/0 Chain DOCKER-CT (1 references) pkts bytes target prot opt in out source destination 0 0 ACCEPT 0 -- * docker0 0.0.0.0/0 0.0.0.0/0 ctstate RELATED,ESTABLISHED Chain DOCKER-FORWARD (1 references) pkts bytes target prot opt in out source destination 0 0 DOCKER-CT 0 -- * * 0.0.0.0/0 0.0.0.0/0 0 0 DOCKER-INTERNAL 0 -- * * 0.0.0.0/0 0.0.0.0/0 0 0 DOCKER-BRIDGE 0 -- * * 0.0.0.0/0 0.0.0.0/0 0 0 ACCEPT 0 -- docker0 * 0.0.0.0/0 0.0.0.0/0 Chain DOCKER-INTERNAL (1 references) pkts bytes target prot opt in out source destination Chain 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
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 destination
1 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:
sudo iptables -t nat -L DOCKER -n -v --line-numbers - The output will be like:
Chain DOCKER (2 references) num pkts bytes target prot opt in out source destination 1 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
sudo iptables -L INPUT --line-numbers -n- Delete the INPUT
sudo iptables -D INPUT 6