ip6tables is the IPv6 packet filtering framework, analogous to iptables for IPv4. It allows you to define rules to permit, deny, or modify IPv6 packets based on various criteria.
Let’s see ip6tables in action. Imagine you have a server with an IPv6 address and you want to allow SSH access (port 22) from anywhere, but block all other incoming traffic.
First, we need to set the default policies for our chains. The INPUT chain handles packets destined for the local machine. Setting it to DROP means any packet not explicitly allowed will be discarded.
sudo ip6tables -P INPUT DROP
sudo ip6tables -P FORWARD DROP
sudo ip6tables -P OUTPUT ACCEPT
Here, sudo ip6tables -P INPUT DROP sets the default policy for incoming traffic to DROP. This is a security best practice: deny by default, and explicitly allow what’s needed. FORWARD is set to DROP because this server isn’t acting as a router. OUTPUT is set to ACCEPT because we want our server to be able to initiate connections outbound.
Now, let’s allow SSH. SSH typically runs on TCP port 22.
sudo ip6tables -A INPUT -p tcp --dport 22 -j ACCEPT
This command appends (-A) a rule to the INPUT chain. It specifies that if the packet protocol (-p) is tcp, and the destination port (--dport) is 22, then the target action (-j) is ACCEPT.
What if you want to allow established connections to come back in? This is crucial for services that initiate connections themselves (like DNS lookups or outgoing HTTP requests).
sudo ip6tables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
The -m state module matches packets based on their connection state. ESTABLISHED means the packet is part of an existing connection that our server initiated. RELATED means the packet is related to an existing connection, but not part of it directly (e.g., an ICMP error message related to a UDP connection). Allowing these ensures that return traffic for outgoing connections isn’t blocked by our DROP default policy.
To see your current rules, you use:
sudo ip6tables -L -v -n
The -L lists the rules, -v provides more verbose output (like packet and byte counts), and -n shows numeric output (IP addresses and port numbers instead of trying to resolve them).
Let’s say you also want to allow ICMPv6 traffic, which is essential for IPv6 to function correctly (e.g., Neighbor Discovery Protocol).
sudo ip6tables -A INPUT -p icmpv6 -j ACCEPT
This rule allows all ICMPv6 traffic. You might want to be more specific in a production environment, but for general connectivity, this is a good start.
The full set of rules for our example might look like this:
# Flush existing rules (use with caution!)
sudo ip6tables -F INPUT
sudo ip6tables -F FORWARD
sudo ip6tables -F OUTPUT
# Set default policies
sudo ip6tables -P INPUT DROP
sudo ip6tables -P FORWARD DROP
sudo ip6tables -P OUTPUT ACCEPT
# Allow established and related connections
sudo ip6tables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# Allow ICMPv6
sudo ip6tables -A INPUT -p icmpv6 -j ACCEPT
# Allow SSH on port 22
sudo ip6tables -A INPUT -p tcp --dport 22 -j ACCEPT
# Allow ping (optional, but useful for testing)
sudo ip6tables -A INPUT -p icmp --icmpv6-type echo-request -j ACCEPT
Notice the sudo ip6tables -F INPUT and similar commands. These flush (delete) all rules from the specified chain. Be very careful with this on a remote machine, as it can lock you out if you don’t have another way in or if your allow rules aren’t correct. It’s often better to insert rules at specific positions (-I) or to build rulesets incrementally.
The most surprising thing about ip6tables is how the state module can manage the lifecycle of connections for you. Without it, you’d need separate rules to allow outgoing traffic and the corresponding incoming return traffic, which quickly becomes unmanageable. The ESTABLISHED,RELATED state effectively links the inbound return packets back to the outbound connection that initiated them, automatically allowing them.
The exact levers you control are the chains (INPUT, OUTPUT, FORWARD, and custom chains), the matching criteria (protocol, ports, source/destination IPs, state, etc.), and the target actions (ACCEPT, DROP, REJECT, LOG, MASQUERADE, etc.).
When you’re dealing with complex ip6tables configurations, especially with many rules, the order of rules within a chain becomes critical. ip6tables processes rules sequentially from top to bottom. The first rule that matches a packet determines the packet’s fate. If a packet matches a rule, processing for that packet stops for that chain. This means more specific rules should often appear before more general ones, or a general DROP rule should be at the end of a chain after all your ACCEPT rules.
The next concept you’ll likely encounter is how to make these rules persistent across reboots, as ip6tables rules are volatile by default.