The Goal and The Struggle
I spent the last two days battling with
nftables and iptables, and it was a painful experience. My goal was simple: I wanted to route all the network traffic from my home router through my home server.
Why? I run a piece of software called
sing-box on the server, and I wanted to use it to filter all the traffic on my network. This would give me a chance to block advertisements and any other unwanted requests for every device in my home.
I was convinced I needed complex firewall rules to get this to work. I was wrong.
The Real Solution: sing-box Configuration
After two days of frustration, the solution turned out to be surprisingly simple: you don't need to modify
or iptables
at all.nftables
The entire routing mechanism can be handled directly by
sing-box. The most critical part is correctly configuring your inbounds. The key was adding a dns inbound to my configuration, which allows sing-box to capture and handle DNS requests, directing traffic as needed.
One other hurdle: if you want to run
sing-box as a systemd service (using systemctl), it needs elevated privileges to bind to low ports and manage network traffic. You must ensure the service runs as the root user and is granted the proper capabilities (like CAP_NET_ADMIN and CAP_NET_BIND_SERVICE).
Bypassing for Local Apps
qbittorrent
I tried adding
process_name, process_path or even process_name_regex to a route rule with outbound set to direct, but it didn't work.
The way I found working is creating a separate service user on my home server. Let's say the name is
qbittorent, then start the service with this user.
Then, the most critical step, get the uid of this user, let's say it's 996.
There is an option called
exclude_uid in tun configuration, can search the name in sing-box documentation. Set its value to 996. You are good to go.
This is telling sing-box to not handle traffic from this uid at all. By this, you don't even need to worry about routing rules. Because the traffic never goes into sing-box.
The same with WeChat. At first I tried using route rule to match traffic to direct outbound.
Again, the magic is never letting sing-box to handle the traffic.
This is a bit different from qbittorent. Instead
exclude_uid, another field called route_exclude_address_set was used.
The value could be remote rule like
geoip-cn or whatever you want to add.
Cloudlfare Tunnel
I have a few service exposing to internet by Cloudflare Tunnel. I found there were lots of log like
lookup quic.cftunnel.com: empty result showing up.
I found this GitHub issue and it's helpful: https://github.com/SagerNet/sing-box/issues/2437.
It's is in Chinese.
Basically, you shouldn't use
sniff_override_destination. I don't exactly understand how disabling this field can fix this issue. But it just works!
Tailscale Exit Node
Another setup I want to do is using Tailscale exit node as a proxy node. Which means if my devices are connecting to my home server, which is a Tailscale exit node. The traffic from my devices should be able to go through sing-box.
I achieve this by adding Tailscale endpoint setting to my sing-box configuration.
Not sure how it works, but again it just works...
I guess because endpoint in sing-box has both inbound and outbound. Perhaps sing-box has internalized the routing logic.
My Network Setup (For Context)
I should mention that my network configuration is a bit specific, which is what makes this setup possible.
My Ubuntu server has multiple network interfaces. I used
Netplan (the default network manager for Ubuntu) to configure the second network interface as a DHCP server.
My main home router then plugs directly into this server interface and acts as a DHCP client.
This physical setup forces all traffic from my home router (and all the devices connected to it) to pass through the Ubuntu server before it can reach the internet. This is what allows
sing-box to intercept and manage everything. If your server only has a single network interface, your setup will likely be different.
Lesson Learned: LLMs vs. The Manual
This experience taught me a valuable lesson. I can't count how many times I asked ChatGPT and Gemini for a solution. Every single time, they gave me complicated answers based on modifying
iptables or nftables.
The real answer had nothing to do with them. It was simply about understanding the
sing-box configuration file.
I think this is because
sing-box is a relatively niche and specialized tool, so there isn't much material for LLMs to train on. The models defaulted to the "standard" Linux way of doing things, which, in this case, was the wrong path.
Next time I'm stuck, I'm going straight to the official documentation first.
Helpful Resource
Finally, I found this GitHub repository full of configuration examples, which was incredibly helpful: