Manage Packet Filtering with iptables

LinuxBeginner
Practice Now

Introduction

iptables is a command-line interface for managing Linux packet-filtering rules. Although many modern distributions implement these rules through an nftables backend, administrators still encounter tasks and existing systems that use the iptables command syntax.

A useful beginner's model is to picture a firewall as an ordered checklist. A packet enters a chain, each rule asks whether the packet matches conditions such as an interface, protocol, or port, and a target such as ACCEPT or REJECT decides what happens. The INPUT chain handles packets addressed to the local host. Because rules are evaluated from top to bottom, both the contents and the position of a rule matter.

In this lab, you will work with one local web service and make only narrowly scoped changes to its TCP port. You will inspect the current firewall, add a rule that rejects requests, prove the effect with real traffic, replace the rule with an explicit allow rule, and remove the lab-owned rule without flushing or rewriting unrelated firewall state.

Inspect the Firewall and Test Service

In this step, you will identify the installed iptables implementation and establish that the local test service works before you change any firewall rule.

Move to the project directory so every later command starts from the course workspace.

cd /home/labex/project

Use command -v to locate the executable that your shell will run.

command -v iptables

This check is more useful than assuming a command is installed: it shows the resolved executable path, normally /usr/sbin/iptables, and returns a nonzero status if the shell cannot find it.

Display the version and backend information. On this Ubuntu host, the output normally includes nf_tables; this means the familiar iptables syntax is translated to the modern kernel ruleset backend.

sudo iptables --version

You should see output similar to:

iptables v1.8... (nf_tables)

The backend name does not change the commands in this lab. You will still create and inspect rules with iptables; the implementation handles their representation in the kernel.

Before filtering the port, request its health endpoint. A successful response proves the application is ready, so a later failure can be attributed to the firewall rule.

curl --noproxy '*' --fail --silent http://127.0.0.1:18086/health

Here, 127.0.0.1 is the IPv4 loopback address, so the traffic leaves and returns through the local lo interface without reaching an external network. --noproxy '*' prevents proxy settings from redirecting the request, --fail makes HTTP errors produce a failure status, and --silent hides the progress meter so the service response is easy to read.

The service returns:

iptables filter service ready

Inspect the INPUT Chain and Add a Reject Rule

In this step, you will read the current INPUT chain and insert one precise rule that rejects loopback TCP traffic to the test port.

The -S option prints rules in command-like form. Naming INPUT limits the output to packets destined for this host.

sudo iptables -S INPUT

The first line usually shows the chain policy, for example -P INPUT ACCEPT. The policy is the fallback action used only when no earlier rule makes a final decision. Following lines beginning with -A INPUT are the rules currently appended to that chain.

For an operational view, -L lists the chain, -n avoids slow name resolution, and --line-numbers shows each rule position. Rule order matters because the first matching terminating target decides the packet's result.

sudo iptables -L INPUT -n --line-numbers

These two views serve different purposes: -S is convenient when you need reusable rule syntax, while -L is convenient when you need positions, packet counters, and a quick operational summary.

Insert the lab rule at position 1 with -I INPUT 1. It matches the loopback interface lo, TCP, and destination port 18086. The REJECT target with tcp-reset refuses the connection immediately instead of making the client wait for a dropped packet to time out.

sudo iptables -I INPUT 1 -i lo -p tcp --dport 18086 -j REJECT --reject-with tcp-reset

Read the command from left to right: -i lo restricts the incoming interface, -p tcp selects the protocol, --dport 18086 selects the service, and -j REJECT jumps to the rejecting target. -I inserts at a chosen position; by contrast, -A would append the rule at the end, where an earlier matching rule might prevent it from ever being reached. This narrow combination avoids blocking unrelated ports or external interfaces.

REJECT and DROP are not identical. REJECT sends an error response, while DROP silently discards the packet. A TCP reset makes this local test fail quickly and predictably, which is easier to diagnose than waiting for a timeout.

Use -C to check whether that exact rule exists. A successful check exits with status zero. On this host, the nftables-backed implementation also prints the normalized matching rule; other implementations may complete silently.

sudo iptables -C INPUT -i lo -p tcp --dport 18086 -j REJECT --reject-with tcp-reset

The exit status is the portable result of -C, not whether text appears on the screen. This makes the same command useful in shell scripts and verification checks.

List the chain again. The new rule should appear as rule 1 and should mention tcp dpt:18086.

sudo iptables -L INPUT -n --line-numbers

Verify the Blocked Traffic

In this step, you will repeat the original request and inspect rule counters to prove that the firewall, rather than the application, rejected the traffic.

Run the request inside an if statement. Because the request is expected to fail, the else branch converts that expected failure into a clear message while allowing the guided workflow to continue.

if curl --noproxy '*' --fail --silent http://127.0.0.1:18086/health; then
  echo "Unexpected: request succeeded"
else
  echo "Expected: iptables rejected the request"
fi

Shell conditionals make decisions from a command's exit status: zero selects then, while nonzero selects else. The reset sent by the firewall causes curl to return nonzero, so reaching the else branch is the expected result here.

The expected message is:

Expected: iptables rejected the request

Now list the chain with -v for verbose counters. The packet and byte counts on the port 18086 rule should be greater than zero after the request.

sudo iptables -L INPUT -n -v --line-numbers

In the output, the pkts column counts matching packets and the bytes column counts their combined size. Counters are useful evidence: checking with -C proves the rule exists, while a counter increase proves that real traffic reached and matched it. If the counter remains zero, inspect the interface, protocol, port, and rule position before blaming the application.

Replace the Reject Rule with an Allow Rule

In this step, you will delete the exact reject rule, add an explicit allow rule for the same traffic, and verify that access returns.

The -D form below identifies the rule by its complete match and target. This is safer than flushing the chain because it removes only the lab-owned change.

sudo iptables -D INPUT -i lo -p tcp --dport 18086 -j REJECT --reject-with tcp-reset

Rules can also be deleted by line number, but line numbers change whenever rules are inserted or removed. Deleting by the exact specification is clearer in a repeatable procedure and reduces the chance of removing a neighboring rule. Avoid iptables -F here: flushing would remove every rule in the selected chain, including rules unrelated to this lab.

Insert an ACCEPT rule for the same interface, protocol, and port.

sudo iptables -I INPUT 1 -i lo -p tcp --dport 18086 -j ACCEPT

ACCEPT permits a matching packet to continue through the network stack. It is inserted at position 1 so that its result is not hidden by an earlier terminating rule. Notice that the match remains just as narrow as before; only the target changes.

Check the exact allow rule.

sudo iptables -C INPUT -i lo -p tcp --dport 18086 -j ACCEPT

Repeat the original health request.

curl --noproxy '*' --fail --silent http://127.0.0.1:18086/health

The response is available again:

iptables filter service ready

Remove the Lab-Owned Rule

In this step, you will remove the temporary allow rule and confirm that no rule from this exercise remains.

Delete the exact ACCEPT rule. As before, this does not flush the chain or touch rules owned by the platform or another administrator.

sudo iptables -D INPUT -i lo -p tcp --dport 18086 -j ACCEPT

Removing an explicit allow rule does not automatically mean the service will become blocked. Once the rule is gone, the remaining rules and finally the chain policy decide the packet's fate. In this prepared environment, the original firewall permits the local request.

Check for the rule once more. This time failure is expected, so || runs the message on the right only when iptables -C returns a nonzero status.

sudo iptables -C INPUT -i lo -p tcp --dport 18086 -j ACCEPT 2>/dev/null || echo "Lab allow rule removed"

You should see:

Lab allow rule removed

Finally, confirm that removing the explicit rule did not stop the application. The chain's original policy and rules again determine access.

curl --noproxy '*' --fail --silent http://127.0.0.1:18086/health

Summary

You inspected the iptables implementation and the INPUT chain, inserted and checked a narrow reject rule, proved its effect with live traffic and counters, replaced it with an allow rule, and removed only the lab-owned change. These operations provide the command-level packet-filtering practice needed to work safely without flushing unrelated firewall state.