Block One OpenVPN Client From the LAN Without Stopping the Tunnel
Pin the client's tunnel address to its certificate, drop its LAN traffic at the head of FORWARD, then verify IPv6, tracked sessions and reboot survival.
One client on your OpenVPN server has to lose LAN access while every other tunnel stays up. Search for that, and the top answer usually says the daemon cannot do it, which is only half true: OpenVPN has no per-client firewall, but the host that terminates the tunnel already filters packets. The two decisions that matter are the identity you block and the position of the rule. Get those right and the change becomes one line you can test and remove.
Pin the client before you write the rule
OpenVPN leases tunnel addresses from a pool, so 10.8.0.6 is a seat rather than a person. When the session ends the lease returns and can be handed to the next connection, which means a rule written against that address follows the seat, not the user. So decide first what you are actually blocking: an address for a short experiment, or a certificate for someone you want to keep out.
To bind the address to the certificate, create a file in the directory named by client-config-dir and give the file exactly the certificate's common name. Put one directive in it, ifconfig-push 10.8.0.6 255.255.255.0, and that client keeps the same tunnel address across reconnects. Two settings have to exist for this to take effect: client-config-dir must be declared in the server configuration, and the pushed address has to sit in a subnet the server can route. Miss either one and the file is ignored without an error, which makes the next firewall test misleading.
A temporary block can use a pool address, as long as the ticket records which session held it at the time.
Order decides whether the drop runs
On a Linux gateway that forwards between the tunnel and the LAN, packets cross the FORWARD chain. Read the command below as a sentence: insert at the head of FORWARD, match traffic entering from tun0, from that single host, addressed to the LAN subnet, and drop it.
iptables -I FORWARD -i tun0 -s 10.8.0.6/32 -d 192.168.1.0/24 -j DROPThe -I is what makes the rule run. Appended with -A, it lands below any broad accept for the tunnel subnet and never sees a packet. Verify the position with iptables -vnL FORWARD --line-numbers, and read the counters as well: a drop at the top that counts nothing means the traffic is arriving by another path.
The gateway's own LAN address is a different path. Packets sent to the box itself go to INPUT, not FORWARD, so a client can lose a LAN host and still open the router's admin page. Add the matching INPUT rule when that matters.
What the filter cannot see
Filtering happens when a packet is evaluated, so an established TCP session can continue until it closes or is reset. If the block must bite immediately, clear the tracked flows for that source with conntrack -D -s 10.8.0.6 after the rule goes in. If a few seconds of overlap are acceptable, record that choice instead of leaving it implicit. Either way, the next person needs to know whether the rule stops live traffic or only new connections.
Peer traffic never touches the host firewall. With client-to-client enabled, OpenVPN forwards those packets inside the daemon, so neither FORWARD nor INPUT sees them. The only fixes are turning client-to-client off or accepting that peers can reach each other; an iptables rule will not see that traffic.
IPv6, firmware and reboot survival
An IPv4 rule says nothing about IPv6. If the tunnel and the LAN carry both families, the equivalent rule has to exist in ip6tables, or in the nftables ruleset that owns policy on that host. Router firmware often hides netfilter behind its own abstraction, so syntax and table names copied from a forum may not apply.
On OpenWrt, the documented path is a firewall configuration entry rather than a hand-written iptables line, and a hand-written line can disappear when the firewall reloads. Other distributions restore rules through netfilter-persistent, a boot unit or a vendor store, and those mechanisms are not interchangeable. Prove whichever one you chose with a reboot, because a reload exercises a different path.
Verify by path, then roll back cleanly
Test each path separately, because one successful check hides the others. A blocked client should still reach the internet through the tunnel while failing against a LAN host, a second client should reach that same host, and the gateway address and the peer path each need their own observation. Write the results next to the rule so the next change has a baseline. The table below is the set worth keeping in the ticket.
| Path | Expected with the rule | When it fails |
|---|---|---|
| Blocked client to a LAN host | No answer | Rule sits below an accept, or the source does not match |
| Blocked client to the internet | Works through tun0 | Rule is too broad, with no destination match |
| Second client to the same LAN host | Works | Rule matched more than the intended host |
| Blocked client to the gateway LAN address | Blocked only with the matching INPUT rule | INPUT rule missing |
| Blocked client to a peer tunnel address | Still works | client-to-client is enabled and outside firewall scope |
Rollback is a delete by specification, not a flush: iptables -D FORWARD -i tun0 -s 10.8.0.6/32 -d 192.168.1.0/24 -j DROP removes exactly that entry and leaves the rest of the chain intact. Remove the same line from whichever file restores the rules at boot, reload it, and confirm the client reaches the LAN host again. Flushing FORWARD would also undo rules you did not write, which turns a rollback into a second incident.
Before you close the ticket, confirm three things: the pinned address matches the certificate, the rule sits above the accepts with a non-zero counter, and the persistence file contains the same rule you tested. If any of those is missing, the block is a shell-session illusion.