1. Introduction

iptables filters IP packets based on a chain of rules within a table. Deleting iptables rules requires caution because improper deletion might provide access to an unauthorized packet or deny access to an authorized one. Furthermore, programmatically deleting rules in bulk is more convenient than manual deletion.

In this tutorial, we’ll learn about deleting specific rules from iptables based on parameters and rule numbers, taking a backup of the rules, and persisting changes on reboot. Then, we’ll run scripts to delete rules based on a text search and explore comments as tags for deletion. Lastly, we’ll study how to flush chains, delete user-defined chains, and reset iptables.

We tested all scripts in Bash version 5.2.15 on Debian 12 (Bookworm) OS installed with iptables version 1.8.9. We’ll run the commands as a root user because running iptables requires root privileges, but running the commands with sudo will also work.

2. Deleting Specific Rules From iptables

We delete a particular rule from a chain within a table using the –delete (-D) operation. iptables provides two syntaxes for this operation:

iptables [-t table] -D chain rule_specification
iptables [-t table] -D chain rule_number

Specifying table is mandatory for any value other than filter, the default table. However, we’ll explicitly specify this parameter for clarity.

2.1. Backup and Restore

It’s better to create a backup of iptables rules as a checkpoint of the instant when things work as expected. Restoring the backup enables us to return to that state whenever needed.

Let’s back up all tables, chains, and rules through the iptables-save command, which prints the rules to stdout. This text can be stored in any file and used for restoring later. Let’s proceed with the filename iptables.rules.v4.backup:

# iptables-save > iptables.rules.v4.backup

Now, let’s inspect the contents of the backup file:

# cat iptables.rules.v4.backup
# Generated by iptables-save v1.8.9 (nf_tables) on Tue Mar  5 18:06:16 2024
*filter
:INPUT ACCEPT [143:18111]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [92:10313]
:MYCHAIN - [0:0]
-A INPUT -p tcp -m tcp --dport 8070 -j MYCHAIN
COMMIT
# Completed on Tue Mar  5 18:06:16 2024

Here, the output shows the active tables, their chains, and the rules within each table. The iptables-restore command takes this output as its input.

When we run iptables-restore without the –noflush (-n) option, the command flushes all existing rules. Then, it restores only the rules passed to it.

Let’s run the command with our backup file as input:

# iptables-restore < iptables.rules.v4.backup

Thus, we replaced all previous rules with the backup file’s rules.

2.2. Persisting Rules on Reboot

By default, any changes made to iptables rules are lost on reboot. On a Debian-based OS, the package iptables-persistent mediates this persistence. Let’s install this package via apt and respond with an additional when the installer asks for confirmation:

# apt install iptables-persistent
[truncated]
Setting up iptables-persistent (1.0.20) ...
Processing triggers for man-db (2.11.2-2) ...

Let’s run the netfilter-persistent save command to persist current settings on reboot:

# netfilter-persistent save
run-parts: executing /usr/share/netfilter-persistent/plugins.d/15-ip4tables save
run-parts: executing /usr/share/netfilter-persistent/plugins.d/25-ip6tables save

The above operation stored the tables in /etc/iptables/rules.v4 and /etc/iptables/rules.v6 for IPv4 and IPv6 respectively. Running the above command after modifying iptables rules is necessary to keep the latest changes.

Similarly, a Fedora-based OS configured with iptables rules provides a command, service iptables save, for storing the rules in /etc/sysconfig/iptables. Replacing iptables with ip6tables performs the same for IPv6.

3. Deleting by Parameters

When we provide a rule_specification to delete a rule, iptables seeks and deletes the first rule that entirely matches the given specification within the chain. If multiple rules match the specification, iptables deletes the rule’s first occurrence having the lowest rule number in the chain.

Let’s add three similar rules with a common source IP address using the –append (-A) operation with the –source (-s) parameter:

# iptables -t filter -A INPUT -p tcp -s 30.100.10.55 --dport 8080 -j DROP
# iptables -t filter -A INPUT -p tcp -s 30.100.10.55 --dport 9090 -j DROP
# iptables -t filter -A INPUT -p tcp -s 30.100.10.55 --dport 9091 -j DROP

Having added these rules, we’ll attempt to delete a rule based on the source IP address specified through the -s parameter:

# iptables -t filter -D INPUT -s 30.100.10.55
iptables: Bad rule (does a matching rule exist in that chain?).

The provided rule specification didn’t match any rule. Let’s provide the exact rule specification that we used to create the rule:

# iptables -t filter -D INPUT -s 30.100.10.55 -p tcp --dport 8080 -j DROP

The command ran successfully. Now, let’s examine the current rules using the –list (-L) operation with the –line-numbers option to display the rule numbering within the chain:

# iptables -t filter -L INPUT --line-numbers
Chain INPUT (policy ACCEPT)
num  target     prot opt source               destination
1    DROP       tcp  --  30.100.10.55         anywhere             tcp dpt:9090
2    DROP       tcp  --  30.100.10.55         anywhere             tcp dpt:9091

As shown in the output, we deleted the rule for port 8080. This method is ideal for deleting a single rule but becomes cumbersome for multiple rules. Moreover, it’s necessary to perform this delete operation as many times as the frequency of a matching rule.

4. Deleting by Number

iptables* rules within a chain are numbered starting from *1. Let’s delete the rule number 1 from the INPUT chain:

# iptables -t filter -D INPUT 1
# iptables -t filter -L INPUT --line-numbers
Chain INPUT (policy ACCEPT)
num  target     prot opt source               destination
1    DROP       tcp  --  30.100.10.55         anywhere             tcp dpt:9091

As shown above, we removed the first rule from the INPUT chain. iptables decremented the rule number of all rules listed after this rule. This method is also cumbersome for deleting multiple rules at a time.

5. Using a Text Search Script

A text search script for deleting iptables rules is based on two methods:

  1. searching for matching lines in the output of the –list-rules (-S) operation and substituting -A with iptables -D for these lines
  2. searching for non-matching lines in the output of iptables-save using grep and passing the result to iptables-restore

Both methods support deleting rules within a specific table. The first method also supports deleting within a chain. On the contrary, the second supports deleting within all tables. Hence, both methods are suitable for deleting rules in bulk.

5.1. Using iptables -S

Let’s add a few rules containing the same IP address and check the output of the -S operation:

# iptables -t filter -A INPUT -s 30.100.10.55 -p tcp --dport 8085 -j DROP
# iptables -t filter -A OUTPUT -d 30.100.10.55 -p tcp --sport 8086 -j DROP
# iptables -t filter -A INPUT -p tcp -s 30.100.10.55 --dport 8087 -j ACCEPT

# iptables -t filter -S
-P INPUT ACCEPT
-P FORWARD ACCEPT
-P OUTPUT ACCEPT
-N MYCHAIN
-A INPUT -s 30.100.10.55/32 -p tcp -m tcp --dport 9091 -j DROP
-A INPUT -s 30.100.10.55/32 -p tcp -m tcp --dport 8085 -j DROP
-A INPUT -s 30.100.10.55/32 -p tcp -m tcp --dport 8087 -j ACCEPT
-A OUTPUT -d 30.100.10.55/32 -p tcp -m tcp --sport 8086 -j DROP

Here, we listed the rules in all chains within the filter table. Let’s define our search text as a variable and search within the INPUT chain of this table. We’ll employ the –word-regexp (-w) match option of grep to match the search text as a whole word with word boundaries on both sides:

# SEARCH_TEXT=30.100.10.55/32
# iptables -t filter -S INPUT | grep -w "$SEARCH_TEXT"
-A INPUT -s 30.100.10.55/32 -p tcp -m tcp --dport 9091 -j DROP
-A INPUT -s 30.100.10.55/32 -p tcp -m tcp --dport 8085 -j DROP
-A INPUT -s 30.100.10.55/32 -p tcp -m tcp --dport 8087 -j ACCEPT

Thus, we found three matches. Let’s make the substitution of iptables -D in place of -A using sed with the /e flag to run the substituted text as a command:

# iptables -t filter -S INPUT | grep -w "$SEARCH_TEXT" | sed 's/^-A/iptables -D/e'

# iptables -t filter -S INPUT
-P INPUT ACCEPT

Here, we deleted the matching rules within this chain. Let’s skip mentioning the chain to run the command for all chains within the filter table:

# iptables -t filter -S | grep -w "$SEARCH_TEXT" | sed 's/^-A/iptables -D/e'

# iptables -t filter -S
-P INPUT ACCEPT
-P FORWARD ACCEPT
-P OUTPUT ACCEPT
-N MYCHAIN

In this way, we deleted the OUTPUT chain rule for port 8086.

5.2. Using iptables-save

Let’s repeat adding rules with the same IP address for our second method. We’ll fetch the output of iptables-save for the filter table using the –table (-t) option:

# iptables-save -t filter
# Generated by iptables-save v1.8.9 (nf_tables) on Tue Mar  5 18:43:04 2024
*filter
:INPUT ACCEPT [269:24056]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [148:15068]
:MYCHAIN - [0:0]
-A INPUT -s 30.100.10.55/32 -p tcp -m tcp --dport 8085 -j DROP
-A INPUT -s 30.100.10.55/32 -p tcp -m tcp --dport 8087 -j ACCEPT
-A OUTPUT -d 30.100.10.55/32 -p tcp -m tcp --sport 8086 -j DROP
COMMIT
# Completed on Tue Mar  5 18:43:04 2024

Let’s delete the matching rules from the filter table using our second method. grep* provides an –invert-match (-v) option to return non-matching lines which we’ll pipe to *iptables-restore:

# iptables-save -t filter | grep -vw "$SEARCH_TEXT" | iptables-restore -T filter
# iptables-save -t filter
# Generated by iptables-save v1.8.9 (nf_tables) on Tue Mar  5 18:43:59 2024
*filter
:INPUT ACCEPT [7:404]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [7:716]
:MYCHAIN - [0:0]
COMMIT
# Completed on Tue Mar  5 18:43:59 2024

Thus, using parameters to specify the table and an inverted grep search, we removed those rules that matched our search text. Excluding the table parameters will perform deletion of the matching rules from all tables.

Let’s repeat adding more rules in other tables as well:

# iptables-save
# Generated by iptables-save v1.8.9 (nf_tables) on Tue Mar  5 18:50:03 2024
*security
:INPUT ACCEPT [42:2384]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [33:5880]
-A INPUT -s 30.100.10.55/32 -p tcp -m tcp --dport 8085 -j DROP
-A INPUT -s 30.100.10.55/32 -p tcp -m tcp --dport 8087 -j ACCEPT
-A OUTPUT -d 30.100.10.55/32 -p tcp -m tcp --sport 8086 -j DROP
COMMIT
[truncated]
*filter
:INPUT ACCEPT [42:2384]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [33:5880]
:MYCHAIN - [0:0]
-A INPUT -s 30.100.10.55/32 -p tcp -m tcp --dport 8085 -j DROP
-A INPUT -s 30.100.10.55/32 -p tcp -m tcp --dport 8087 -j ACCEPT
-A OUTPUT -d 30.100.10.55/32 -p tcp -m tcp --sport 8086 -j DROP
COMMIT
# Completed on Tue Mar  5 18:50:03 2024
[truncated]

The output shows matching rules in security and filter tables.

Let’s search in all tables and delete matching rules using our second method:

# iptables-save | grep -vw "$SEARCH_TEXT" | iptables-restore
# iptables-save
# Generated by iptables-save v1.8.9 (nf_tables) on Tue Mar  5 18:51:09 2024
*security
:INPUT ACCEPT [11:652]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [6:712]
COMMIT
[truncated]
*filter
:INPUT ACCEPT [11:652]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [6:712]
:MYCHAIN - [0:0]
COMMIT
# Completed on Tue Mar  5 18:51:09 2024
[truncated]

Thus, we deleted rules from all tables based on the search text.

6. Using Comments

iptables supports adding a comment to each rule up to 256 characters.

Let’s use the -m comment –comment extension to create a rule with a comment:

# iptables -t filter -A INPUT -p tcp --dport 8090 -m comment --comment "allow-http" -j ACCEPT

Having added this rule with a comment, let’s list all the rules within the INPUT chain:

# iptables -t filter -S INPUT
-P INPUT ACCEPT
-A INPUT -p tcp -m tcp --dport 8090 -m comment --comment allow-http -j ACCEPT

Now, we’ll leverage our comment as a search text and prefix it with the comment –comment syntax to delete matching rules within the INPUT chain:

# SEARCH_TEXT=allow-http
# iptables -t filter -S INPUT | grep -w "comment --comment $SEARCH_TEXT" | sed 's/^-A/iptables -D/e'

# iptables -t filter -S INPUT
-P INPUT ACCEPT

Thus, we leveraged a comment as a tag to find and delete a rule.

7. Flushing Chains

Flushing a chain means clearing out all the rules it contains. However, it’s crucial to set the default chain policy to ACCEPT before flushing it:

# iptables -t filter -P INPUT ACCEPT

We ensured that the filter table INPUT chain allows all traffic after we flush the chain.

Let’s flush the INPUT chain with the –flush (-F) operation:

# iptables -t filter -F INPUT
# iptables -t filter -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
ACCEPT     tcp  --  anywhere             anywhere             tcp spt:9092

Chain MYCHAIN (0 references)
target     prot opt source               destination

If we exclude the chain from the -F operation, iptables will flush all chains within the specified table. Let’s ensure all built-in chains have the ACCEPT policy and then flush the table:

# iptables -t filter -P OUTPUT ACCEPT
# iptables -t filter -P FORWARD ACCEPT
# iptables -t filter -F
# iptables -t filter -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

Chain MYCHAIN (0 references)
target     prot opt source               destination

Thus, we flushed all chains within the filter table.

8. Deleting a User-Defined Chain

iptables supports creating and deleting user-defined chains within a table. However, such a chain must satisfy the below criteria to be eligible for deletion:

  • no rules refer to this chain as the target, specified as –jump (-j) target
  • contains no rules

Let’s run our text search script to delete rules by searching for -j MYCHAIN:

# iptables -t filter -S | sed '/-j MYCHAIN/s/^-A/iptables -D/e'
-P INPUT ACCEPT
-P FORWARD ACCEPT
-P OUTPUT ACCEPT
-N MYCHAIN

Here, we satisfied the first condition. Let’s flush the chain to satisfy the second:

# iptables -t filter -F MYCHAIN

Having removed all rules in this chain, let’s delete the chain with the –delete-chain (-X) operation:

# iptables -t filter -X MYCHAIN

Thus, we deleted MYCHAIN.

9. Resetting iptables

Let’s reset iptables to the initial state by running an awk script on the output of iptables-save. Here, we retain only the built-in chains of each table and set the chain policy as ACCEPT:

# iptables-save | awk '/^[*]/ { print ; }
    /^:[A-Z]+ [^-]/ { print $1 " ACCEPT" ; }
    /COMMIT/ { print ; }' | iptables-restore

Running this script brought iptables to an initial state of allowing traffic.

10. Conclusion

In this article, we learned different ways of deleting iptables rules. Initially, we understood the basic syntax of deleting a rule and the process of taking a backup using iptables-save and restoring it with iptables-restore. We also examined ways to persist rules on reboot using the iptables-persistent package.

Then, we studied how to delete rules by providing the rule specification or number. We also ran a text search script to bulk delete rules using iptables -S and iptables-save. Then, we inspected how to use comments for tagging and deleting rules. Finally, we saw how to flush rules after setting an ACCEPT chain policy, delete user-defined chains with iptables -X, and reset iptables using an awk script.