Why a Linux side-gateway instead of router firmware
Many readers already know the OpenWrt plus OpenClash story: the router owns DHCP, DNS, and policy routing, and the whole house inherits one configuration surface. That is excellent when you want a single appliance to be authoritative. It is also a maintenance commitment—kernel packages, flash wear, driver quirks, and upgrade windows that do not always line up with your weekend. A Linux mini PC or fanless industrial box on the same Ethernet segment offers a narrower contract: you are not replacing the ISP CPE or the primary router, you are adding a side-gateway that other devices may choose as their next hop.
The mental model is simple to say and easy to break in practice: LAN clients send off-subnet traffic to the Linux host, the host must forward it, Clash TUN must participate in the same routing story your rules expect, and replies must return along a coherent path. If you want the consumer-router angle for comparison, read OpenWrt OpenClash gateway DNS and bypass patterns first so you know which responsibilities you are intentionally leaving on the primary router.
This article focuses on the Linux-only path: nftables for NAT and basic filter policy, sysctl forwarding toggles, and the Clash Meta knobs people forget when they move from “TUN on one machine” to “TUN as a shared gateway.” For the single-host baseline—install layout, capabilities, and systemd—treat Enable Clash TUN on Ubuntu with systemd as the companion checklist; here we assume the binary already runs reliably and we wire the network around it.
Topology, subnets, and the packet path you are building
The most reproducible home pattern keeps every device on one LAN prefix (for example 192.168.1.0/24) while the primary router remains 192.168.1.1 and the Linux gateway uses a static address such as 192.168.1.2/24. Clients that should use Clash set default gateway to 192.168.1.2 instead of .1. The Linux box still needs a usable upstream route—typically default via 192.168.1.1 dev eth0—because it is not the authoritative path for the whole LAN, only for subscribers who opted in through DHCP or manual settings.
Why mention subnets at all? Because the moment you split VLANs or add a second NIC, you inherit extra decisions about DHCP relay, static routes on the primary router, and reverse-path filtering. Single-LAN side gateways keep those variables smaller. If you later graduate to a routed downstream network, revisit forwarding and nftables with the same discipline—just expect the primary router to need a return route for the downstream prefix.
At the Clash layer, TUN mode is still the feature that makes “I did not configure this app” plausible for arbitrary UDP and TCP flows. If you need the conceptual map of stacks, auto-route, and hijack semantics before you touch forwarding, read Clash TUN mode deep dive so you can tell kernel routing issues apart from policy issues inside rules.
Enable kernel forwarding with sysctl
Linux defaults are conservative: a host is not a router until you say so. For IPv4, set net.ipv4.ip_forward=1. If you operate dual-stack, decide deliberately whether IPv6 forwarding is on; IPv6 neighbor discovery and unexpected RAs can complicate a lab that was meant to be “IPv4 only for now.” Pair IPv6 choices with Fix IPv6 leaks in Clash dual-stack so you do not debug TUN for an hour when the real story is an unexpected global address on the client.
Persist sysctl values in /etc/sysctl.d/ rather than typing them once. After changes, validate with sysctl net.ipv4.ip_forward and then restart Clash so any interface-local assumptions are rebuilt against the new forwarding state. If you script provisioning, treat sysctl, nftables, and the Clash unit as three ordered steps with explicit checks between them.
# /etc/sysctl.d/99-clash-gateway-forward.conf
net.ipv4.ip_forward = 1
# Optional hardening knobs — tune to your NIC names and policy
# net.ipv4.conf.all.rp_filter = 2
# net.ipv4.conf.default.rp_filter = 2
The commented rp_filter lines are intentionally conservative. Relaxed reverse-path modes can unblock hairpinned NAT in some LANs, but they also mask genuine miswiring. Capture your baseline, change one variable at a time, and note which setting fixed which symptom.
nftables: accept forwarding and masquerade upstream
When a phone or laptop uses 192.168.1.2 as its gateway, the forwarded packets still carry the client’s original source IP. The primary router at .1 may not know how to return certain flows to that client without extra static routes, and even when it does, you can hit asymmetric return paths. A practical fix on the side-gateway is source NAT (masquerade) for forwarded traffic that leaves toward the primary router, so upstream devices see the conversation as originating from 192.168.1.2.
You also need explicit filter rules: accept established traffic, accept forwarding from the LAN interface to the upstream interface, and default-drop what you did not intend. The exact table names matter less than the habit of making rules readable: group related accepts, document why a broad accept exists, and keep a literal drawing of interface names in your notes. Replace eth0 below with the interface that faces your home LAN.
#!/usr/sbin/nft -f
flush ruleset
table ip filter {
chain input {
type filter hook input priority 0; policy drop;
ct state established,related accept
iif "lo" accept
iifname "eth0" accept
}
chain forward {
type filter hook forward priority 0; policy drop;
ct state established,related accept
iifname "eth0" oifname "eth0" accept
}
}
table ip nat {
chain postrouting {
type nat hook postrouting priority 100;
oifname "eth0" masquerade
}
}
The snippet is minimal on purpose: it is a teaching scaffold, not a complete hardening profile. Real homes mix mDNS, printer discovery, and guest VLANs; corporate labs add jump hosts and management interfaces. Grow the forward chain deliberately. If you already maintain complex netfilter logic elsewhere, keep Clash’s expectations documented beside your table so the next you does not “fix” NAT by deleting half of the forward policy at 2 a.m.
Clash TUN, allow-lan, bind-address, and DNS listeners
Running TUN on a laptop is mostly about local routes. Running TUN on a gateway adds remote clients that speak HTTP, SOCKS, and DNS to your controller ports. That is where allow-lan and bind-address stop being trivia and become the difference between “works on the box” and “works for the house.” If you have ever chased a mystery refusal on a LAN segment, read Clash LAN sharing firewall and bind address alongside this section—the two guides answer different halves of the same complaint.
DNS is the other half. Clients pointed at your gateway for DHCP option 3 often expect the same host to answer on port 53, or they keep using the router’s resolver and surprise you with split-DNS artifacts. Align Clash’s DNS stanza with the modes you actually run—especially fake-ip—using Clash Meta DNS: nameserver fallback and fake-ip filter as the authoritative walkthrough. If names and routes disagree, users perceive “TUN is flaky” when the forward path is fine but answers point at addresses your policy never matches.
For YAML, treat keys as version-sensitive. The goal is to communicate intent: enable TUN with a stack appropriate to your kernel, expose the controller safely on the LAN, and avoid auto-route surprises when the machine is multi-homed. When in doubt, prefer incrementalism—bring TUN up, confirm locally, then add forwarding and NAT, then repoint a single test client before you repoint the entire VLAN.
Illustrative YAML fragments (adapt to your core version)allow-lan: true
bind-address: "*"
external-controller: 0.0.0.0:9090
secret: "replace-me"
tun:
enable: true
stack: system
auto-route: true
auto-detect-interface: true
strict-route: false
dns-hijack:
- any:53
Tighten external-controller binding in anything beyond a trusted lab. Use a real secret, rotate it when you rotate subscriptions, and keep management traffic off guest SSIDs. If you need inspiration for structuring policy once the plumbing works, Clash rule routing best practices remains the best on-ramp to readable rules and maintainable indirection.
Point clients with DHCP or static routes
Consumer routers vary wildly, but the pattern you want is simple: for DHCP scopes that should use Clash, set option 3 (router) to the Linux gateway address and option 6 (DNS) consistently with the resolver story you configured. For devices that must bypass the gateway—some banking apps, multicast security cameras, or corporate always-on VPNs—carve reservations or separate SSIDs rather than fighting exceptions inside Clash alone.
Static configuration on workstations is a fine lab technique: set IPv4 gateway to the side host, set DNS to the same host or a known upstream pair, and record the exact UI path so you can revert quickly. Windows, macOS, iOS, and Android all surface different toggles; the reproducible part is documenting IP, mask, gateway, and DNS as a single row per device.
Remember that changing the gateway does not magically move existing TCP flows. Restart long-lived apps after the switch, and expect some cached TLS sessions to behave differently when paths change. That is normal, not a sign your nftables table failed.
Verification: ping, curl, tcpdump, and log sanity
Verification should be staged. First, from the Linux gateway itself, confirm Clash starts, the TUN interface exists, and local policy works for both domestic and overseas destinations you care about. Second, from a test client with gateway pointed at the Linux host, confirm default route and DNS answers match your design. Third, add observability: a short tcpdump on the LAN interface while you curl a well-known endpoint tells you whether packets leave with the expected source after NAT.
Use split tests: one target that should hit DIRECT under your rules and one that should hit a remote policy group. If both behave identically wrong, suspect forwarding or NAT before you suspect node quality. If only one class fails, return to rule order and DNS mode with the same skepticism you would apply on a single-user machine.
Logs matter. When TLS or timeouts appear, separate “never left the LAN” from “left but came back weird” using the vocabulary in Clash connection logs: timeout and TLS patterns. Gateway debugging is slower because more devices participate; disciplined logging keeps it tractable.
Failure modes: rp_filter, hairpin NAT, and asymmetric paths
Symptom clusters are repetitive once you have seen them. Partial connectivity—some sites, not others—often traces to DNS or fake-ip alignment rather than TUN itself. Total stall from clients while the gateway still browses fine usually means forwarding or NAT never applied to forwarded traffic. Intermittent stalls on fast Wi-Fi but not Ethernet sometimes mean the client never picked up the new DHCP lease; verify with ip route on Linux test boxes or route print on Windows.
Reverse-path filtering deserves its own line in your runbook. Strict filtering can drop forwarded packets when the return path is asymmetric or when multiple NICs participate. Loosening filters is sometimes correct, sometimes a bandage over a mis-described topology. Draw the packet, label MAC and IP at each hop, and only then change sysctl.
Hairpin NAT scenarios show up when clients try to reach public addresses that resolve to your own WAN, or when IoT vendors assume L2 adjacency tricks. If a device needs exceptions, document the exception rather than widening accept rules “temporarily” that quietly become permanent.
Wrap-up: keep rollback notes next to the gateway
A Linux Clash TUN side-gateway is attractive because it reuses skills you already have from desktop Linux: systemd units, journald, nftables, and plain text configuration. It stays maintainable when you treat the gateway like infrastructure—versioned configs, explicit interface names, sysctl files with comments, and a one-page rollback card taped inside the cabinet or stored next to the serial cable.
Compared with all-in-one router firmware, you trade fewer GUI wizards for more transparency. That transparency pays off the moment you need to know exactly which table accepted a packet, or exactly which resolver answered a CNAME chain. Clash gives you the policy vocabulary; Linux gives you the datapath receipts. Together they are a strong house network—when deployed lawfully, carefully, and with patience during DHCP cutovers.
→ Download Clash for free and experience the difference—pair a polished desktop client for everyday editing with this headless gateway pattern so your profiles stay coherent across machines.