Using Debian as a Side Router

Using Debian as a Side Router

Using Debian as a side router offers a more stable and flexible alternative without depending on OpenWrt and LuCI. Configuring Debian directly gives you greater control over the system and avoids the limitations and instability of a GUI. Compared with common side-router setups, this approach makes transparent proxying more reliable and offers another option for those who value performance and efficiency.

Introduction

Most side-router setups use OpenWrt, a separate Linux distribution with its own package system. Most of those also rely on LuCI, OpenWrt’s dedicated web GUI, and tutorials use luci-app-xxx software built specifically for it. These approaches are good, but not quite good enough:

  1. Too much dependence on GUI configuration: LuCI packages generally expose only limited options in the web interface.
  2. LuCI is not stable enough. I mean LuCI, not OpenWrt itself. OpenClash has crashed my LuCI three times—though that might have been my fault.
  3. We can compile OpenWrt ourselves, but most tutorials use prebuilt firmware, some of which may be outdated.
  4. You cannot fully control the system. LuCI has taken the reins!

I spent a year or two tinkering with OpenWrt transparent proxying, both on the main router and with a side router, but eventually gave up because it was not stable enough. For a long time I muddled through with clients such as Surge, Loon, and Clash Verge Rev. About a week ago, Zenless Zone Zero launched. Because of the Chinese release-approval situation, PS5 only got the international version. Even the Asia server barely worked directly, with hopeless speeds and latency. Determined not to hand money to NetEase UU, I thought of transparent proxying again. I happened to have an idle Beelink mini PC with Debian already installed. It was supposed to be a development machine, but laziness had left it gathering dust. After a weekend of tinkering, I finally had Debian working as a side router for transparent proxying.

Here is the final network topology:

topo

The LAN is divided into two subnets, 192.168.6.0/24 and 192.168.7.0/24. The 6.0/24 subnet is the default for devices that do not need censorship circumvention. Devices that need it go on 7.0/24, with all their traffic forwarded through the side-router mini PC.

The core is AdGuard Home plus Clash. AdGuard Home handles ad filtering and related features, while Clash handles DNS routing and traffic proxying.

Main Router Configuration

The existing LAN uses 192.168.6.0/24. We need to add 192.168.7.0/24.

My main router runs iKuai, so here is how to add a subnet there. For OpenWrt or other router systems, Google the equivalent.

In iKuai, go to Network Settings → LAN/WAN Settings → lan1. Under Advanced Settings, add an extended IP of 192.168.7.1 with subnet mask 255.255.255.0.

iKuai configuration

Add a DHCP configuration for 192.168.7.0/24 in DHCP Settings.

DHCP configuration

Set the gateway to 192.168.7.2, the side router address we will configure later. Set both preferred and alternate DNS to 192.168.7.2 too, since the side router handles all DNS for this subnet.

Debian Configuration

Unless stated otherwise, perform the following steps on the side router.

Configure the IP Address

Run sudo nano /etc/network/interfaces to edit Debian’s network configuration, replace it with the following, then save and exit:

text
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

source /etc/network/interfaces.d/*

# The loopback network interface
auto lo
iface lo inet loopback

# The primary network interface
allow-hotplug enp1s0
iface enp1s0 inet static
address 192.168.7.2
netmask 255.255.255.0
gateway 192.168.7.1
dns-nameservers 127.0.0.1

In this configuration:

  • enp1s0 is my network interface name. Replace it with yours; ip a shows the available interfaces.
  • IPv4 uses static configuration, inet static, fixing the address at 192.168.7.2/24 and pointing the gateway to the main router at 192.168.7.1. Until setup is complete, use a working DNS server so you retain Internet access. Change DNS to 127.0.0.1 once AdGuard Home is configured locally.

After saving, restart networking with:

shell
sudo systemctl restart networking.service

Your SSH session may disconnect because the IP address has changed. Reconnect over SSH to 192.168.7.2.

Check the result with ip a:

shell
ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host noprefixroute
       valid_lft forever preferred_lft forever
2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 70:70:fc:00:e3:36 brd ff:ff:ff:ff:ff:ff
    inet 192.168.7.2/24 brd 192.168.7.255 scope global enp1s0
       valid_lft forever preferred_lft forever
    inet6 ■■■:■■■■:■■■■:■■■:■■■■:■■■■/64 scope global dynamic mngtmpaddr
       valid_lft 1741sec preferred_lft 1741sec
    inet6 fe80::7270:fcff:fe00:e336/64 scope link
       valid_lft forever preferred_lft forever

The local LAN address is now 192.168.7.2/24.

Enable Forwarding

A machine must be able to forward traffic to act as a router and gateway:

shell
sudo echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
sudo sysctl -p

AdGuard Home Configuration

Here is the DNS design:

DNS path

When a client resolves a domain, AdGuard Home on port 53 forwards the query to its upstream, Clash. Clash then routes the query according to its settings: mainland Chinese domains use public DNS servers within China, while other domains use overseas public DNS servers through the proxy.

If Clash fails, AdGuard Home queries public DNS servers within China directly. In practice this is not very useful, since traffic still has to pass through Clash even if DNS succeeds.

Install AdGuard Home

Run these commands as root.

shell
## 检查最新稳定版的版本号,如果获取不到请检查网络
remote_ver=$(curl -sS https://api.github.com/repos/AdguardTeam/AdGuardHome/releases/latest | jq -r .tag_name | sed 's|v||' | grep -v "null"); echo $remote_ver

## 下载最新稳定版(前一句有输出这一句才能正常执行)
cd /tmp
wget -q --progress=bar:dot --show-progress -O "AdGuardHome_linux_amd64.tar.gz" "https://github.com/AdguardTeam/AdGuardHome/releases/download/v${remote_ver}/AdGuardHome_linux_amd64.tar.gz"

## 解压
tar --no-same-owner -xf "AdGuardHome_linux_amd64.tar.gz" --strip-components 2 --directory=.

## 安装
install -ps AdGuardHome /usr/local/bin/adguardhome

Create a Service

Create the working directory /var/lib/adguardhome.

shell
mkdir -p /var/lib/adguardhome

Create /etc/systemd/system/adguardhome.service with the following contents. The configuration file will be /var/lib/adguardhome/AdGuardHome.yaml.

ini
[Unit]
Description = Network-wide ads & trackers blocking DNS server.
Wants       = network-online.target mosdns.service
After       = network-online.target mosdns.service

[Service]
Type               = simple
Restart            = always
StartLimitInterval = 5
StartLimitBurst    = 10
ExecStart          = /usr/local/bin/adguardhome -w /var/lib/adguardhome
RestartSec         = 10

[Install]
WantedBy = multi-user.target

Save it, then run systemctl enable --now adguardhome.service to start it immediately and enable it at boot. To view logs later, use Debian’s built-in tools:

shell
journalctl -efu adguardhome.service

To restart it:

shell
systemctl restart adguardhome.service

Initial Setup

Open http://192.168.7.2:3000 for initial setup. Keep the web administration port at 3000 and set the DNS server port to 53.

Under Settings → DNS Settings, set upstream DNS to Clash at 127.0.0.1:1053, which we have not configured yet. Add a few DNS servers within China as fallback servers, for example:

text
223.5.5.5
119.29.29.29

Remember to click Apply.

Then set DNS Server Configuration → Rate Limit to 0.

For ad blocking, add lists under Filters → DNS Blocklists. These two rule sets work well in mainland China:

text
easylist:  https://anti-ad.net/easylist.txt
half-life: https://adguard.yojigen.tech/HalfLifeList.txt

Clash Configuration

Clash handles DNS routing between China and overseas, along with its usual job of getting through the wall. The original Clash repository was deleted and its maintainers disappeared, so Mihomo inherited the project. (Damn you, miHoYo.)

Install Clash

Run the following as root:

shell
## 检查最新稳定版的版本号,如果获取不到请检查网络
remote_ver=$(curl -sS https://api.github.com/repos/MetaCubeX/mihomo/releases/latest | jq -r .tag_name | sed 's|v||' | grep -v "null"); echo $remote_ver

## 下载最新稳定版(前一句有输出这一句才能正常执行)
cd /tmp
wget -q --progress=bar:dot --show-progress -O "mihomo-linux-amd64-v${remote_ver}.gz" "https://github.com/MetaCubeX/mihomo/releases/download/v${remote_ver}/mihomo-linux-amd64-v${remote_ver}.gz"

## 解压
gzip -d "mihomo-linux-amd64-v${remote_ver}.gz"

## 安装
install -ps mihomo-linux-amd64-v${remote_ver} /usr/local/bin/clash

Create a Service

Create the working directory /var/lib/clash.

shell
mkdir -p /var/lib/clash

Create the clash user.

shell
useradd -M -s /usr/sbin/nologin clash

Create /etc/systemd/system/clash.service with the following contents. Clash’s configuration file will be /var/lib/clash/config.yaml.

ini
[Unit]
Description = Clash-Meta tproxy daemon.
Wants       = network-online.target
After       = network-online.target

[Service]
Environment   = PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/b>
Type          = simple
User          = clash
Group         = clash

CapabilityBoundingSet = CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW
AmbientCapabilities   = CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW

Restart       = always
ExecStartPre  = +/usr/bin/bash /var/lib/clash/clean.sh
ExecStart     = clash -d /var/lib/clash
ExecStartPost = +/usr/bin/bash /var/lib/clash/iptables.sh

ExecStopPost  = +/usr/bin/bash /var/lib/clash/clean.sh

As the unit file shows, the Clash binary runs as clash:clash. This makes it easy to distinguish traffic generated by Clash itself from traffic forwarded by Clash.

Notice that ExecStartPost and ExecStopPost run iptables.sh and clean.sh to set up and clear the routing rules.

Here are iptables.sh and clean.sh:

sh
#!/usr/bin/env bash

set -ex

ip rule del fwmark 666 table 666 || true
ip route del local 0.0.0.0/0 dev lo table 666 || true

iptables -t nat -F
iptables -t nat -X
iptables -t mangle -F
iptables -t mangle -X clash || true
iptables -t mangle -X clash_local || true

Every line has detailed comments. Ask ChatGPT if you want to dig further.

Clash Configuration File

Your proxy subscription provider can supply a Clash configuration in YAML format. Save it to /var/lib/clash/config.yaml and adjust the following sections:

A closer look at DNS: there are two groups of DNS servers.

  • nameserver contains public DNS servers within China.
  • fallback contains public DNS servers overseas.

fallback-filter determines when a domain uses the fallback group’s result.

  • geoip-code is an inverse condition: if the IP returned by nameserver does not match geoip-code, use the fallback result.
  • geosite is a positive condition: domains matching geosite use fallback.
  • ipcidr is a positive condition: if nameserver returns one of these poisoned IPs, use fallback.
  • domain is a positive condition: matching domains use fallback directly.

That completes DNS routing.

Other Clash Files

Clash needs a few supporting files. Download them before starting it.

shell
cd /var/lib/clash
wget -q --progress=bar:dot --show-progress -O country.mmdb https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/country.mmdb
wget -q --progress=bar:dot --show-progress -O geosite.dat  https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat
wget -q --progress=bar:dot --show-progress -O GeoIP.dat    https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.dat

mkdir -p ui
cd ui
wget -q --progress=bar:dot --show-progress -O xd.zip https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip
unzip -oqq xd.zip
mv metacubexd-gh-pages xd

The final /var/lib/clash directory should look like this:

shell
/var/lib/clash
├── clean.sh
├── config.yaml
├── country.mmdb
├── GeoIP.dat
├── geosite.dat
├── iptables.sh
└── ui

Since Clash runs as the clash user, change ownership:

shell
chown -R clash:clash /var/lib/clash

Make iptables.sh and clean.sh executable too.

shell
chmod +x iptables.sh
chmod +x clean.sh

Start the Service

Once configuration is complete, enable /etc/systemd/system/clash.service at boot and start it immediately.

shell
systemctl enable --now clash.service

To check logs later, use Debian’s built-in tools:

shell
journalctl -efu clash.service

Open the web UI: http://192.168.7.2:9090/ui/xd.

The web UI configuration should be familiar. If everything works, change this machine’s DNS to 127.0.0.1 as mentioned earlier, and point both the gateway and DNS of LAN devices to 192.168.7.2.

Port Forwarding

If the main router has port forwarding configured and the target machine uses the side router as its gateway, that forwarding probably no longer works. See Fixing Port Forwarding with a Side Router for the solution.