Your tunnel is up, the service is listening, and the office LAN still gets nothing back. Nobody broke the VPN. From the LAN side, the laptop's tunnel address does not exist until something tells the LAN how to reach it, and the reply has to come back the way the request went out. I have watched people rebuild a perfectly good tunnel three times before anyone looked at the reply path.

The tunnel is not what broke

Both ends sit on private ranges, which is the normal case and not a misconfiguration. An address like 10.8.0.6 is real inside the tunnel and nonexistent everywhere else, so the LAN has no reason to send it anything. Before touching routing, confirm the service is reachable inside the tunnel at all: on the client, ss -ltn shows whether the listener binds 127.0.0.1, the LAN interface, or the tunnel address. A daemon bound only to loopback answers nobody but itself, and no route, DNAT or port forward changes that.

A proxy on port 80 or 443 is a different tool with different visibility. It moves requests, not packets, so it is the wrong layer for a raw database connection, and reaching for it is how this problem ends up three tools deep before anyone measures anything.

Route shape: tell the LAN where the pool is

A static route on the LAN gateway is the cleanest answer when the LAN and the tunnel pool do not overlap. Point the pool at the machine that terminates the tunnel:

ip route add 10.8.0.0/24 via 192.168.1.12
route add -p 10.8.0.0 mask 255.255.255.0 192.168.1.12   # Windows, persistent

That fixes one direction only, which is why half-finished versions of this change look like progress. The tunnel side needs the matching half, or the reply leaves the client by its own default route and dies on the way:

push "route 192.168.1.0 255.255.255.0"

For a subnet that sits behind one client rather than behind the server, the server needs both a system route and an iroute entry in that client's file under client-config-dir. Without iroute, OpenVPN knows the prefix exists and has no idea which client owns it: packets arrive and the daemon has nothing to match them to.

Here is the myth worth naming. A route makes an address reachable; it does not make the far side willing to answer. Site-to-site peers add another layer on top, because the remote gateway accepts only sources inside the prefixes listed in its policy, and a laptop arriving from a home pool is not inside them.

NAT shape: DNAT in, and the SNAT line everyone skips

When you cannot touch the LAN gateway, or you want to publish exactly one port, translation on the VPN host works with no route on the LAN at all. Publish the port, forward it, and translate the source on the way into the tunnel:

iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 8080 -j DNAT --to-destination 10.8.0.6:3000
iptables -A FORWARD -i eth0 -o tun0 -p tcp -d 10.8.0.6 --dport 3000 -j ACCEPT
iptables -t nat -A POSTROUTING -o tun0 -d 10.8.0.6 -p tcp --dport 3000 -j SNAT --to-source 10.8.0.1

The third line is the one that goes missing. Skip it and the client sees the original LAN address as the source, answers through its own default route, and that answer heads into the tunnel instead of back toward the LAN. Whether the connection survives then depends on routing you never planned. With SNAT the client sees the server's tunnel address, so the reply follows the tunnel back to the only host that can undo the translation.

Consumer firmware uses its own chain names and does not generate that translation for you. On Tomato, an inbound rule lands in WANPREROUTING and then a filter chain such as wanin, and a rule set that looks complete still needs a manual POSTROUTING line. That is not a firmware bug; it is a rule set written for inbound traffic only.

Why a correct-looking rule still drops the packet

Most of these failures are not in the rule you are staring at. Check four things in this order, and stop at the first one that does not hold:

  • Chain order. iptables -I inserts at the top and -A appends, so an earlier permissive or blocking rule decides first.
  • Wrong chain. Traffic addressed to the router itself crosses INPUT, and forwarded traffic never touches it. Publishing one service can need rules in both.
  • Direction flags. -i tun0 matches traffic arriving from clients and -o tun0 matches traffic leaving toward them. A REJECT with the wrong flag blocks something you did not intend.
  • Source restriction. A forward that accepts one client address works from that client and drops every other source without a word.

A rule with a zero packet counter is not in the path at all, and reading the counters is the fastest way to separate a wrong-chain problem from a wrong-rule problem.

The local forward, when nothing on the LAN can change

An SSH local forward sidesteps the whole question. It needs no route on the LAN, no cooperation from the client's firewall, and no port published anywhere:

ssh -N -f -L 3000:10.8.0.6:3000 user@vpn-host

The service then appears on localhost:3000 of the machine running the tunnel, and nowhere else. That is the boundary: one workstation, one port, and the forward dies with the session unless something restarts it. For an admin checking a staging database it is the right tool. For a camera or a scanner that other people have to reach, it is not, because every user would need their own forward.

Verify both directions before adding another rule

Translation leaves traces, and reading them beats guessing. On the VPN host, conntrack -L shows whether the translation happened at all, iptables -t nat -L -n -v shows whether the counters moved, and tcpdump -ni any port 3000 shows which source address actually arrived. On the client, ss -ltn confirms the listener is on an address the tunnel can reach.

Then pick the shape you are keeping. One published port, a route for the whole pool, and a forward for a single admin are three different commitments, and mixing them is how a working setup becomes one nobody can explain six months later. Write down which address is reachable from where, and the next person to debug this starts at the answer instead of the tunnel.

Artihkkalat mat laktásit