Why?
During the setup of Automatic WordPress Fail-over & Recovery (Tergum), we shared the LAN subnets of participating servers with each other over Tailscale. This was done to facilitate easy SSH access between the participants. Unfortunately, after enabling subnet routing, we discovered that packets sent to the master server via its local IPv4 interface were being returned via its Tailscale interface. This asymmetric routing had the effect of making the master server’s websites unreachable and caused the server to appear down—not answering pings or HTTP requests.
Diagnosing the Problem
Running tcpdump on the master server while pinging from the local network revealed the issue:
bash
sudo tcpdump -i any icmp and host 10.10.0.207
```
Output showed:
```
16:53:50.178125 enp1s0 In IP abc.blandford.tech > www: ICMP echo request
16:53:50.178192 tailscale0 Out IP www > abc.blandford.tech: ICMP echo replyThe smoking gun: Ping requests arrived on enp1s0 (the physical network interface), but replies departed via tailscale0 (the Tailscale VPN interface). This is a classic asymmetric routing problem.
Root Cause
When we enabled Tailscale subnet sharing, Tailscale added our local subnet 10.10.0.0/24 to its routing table (table 52). We confirmed this with:
bash
ip route show table all | grep tailscale
```
Which showed:
```
10.10.0.0/24 dev tailscale0 table 52Because of Tailscale’s firewall marks and routing rules, return packets were being routed through table 52 instead of the main routing table, sending them back through the VPN tunnel rather than the local interface.
The Solution
Rather than removing Tailscale’s route (which would break VPN connectivity), we added a policy routing rule with higher priority to handle local-to-local traffic:
bash
# Tell the kernel: traffic between 10.10.0.0/24 hosts uses the main routing table
sudo ip rule add from 10.10.0.0/24 to 10.10.0.0/24 lookup main priority 99This surgical fix ensures:
- ✅ Local network traffic (10.10.0.x ↔ 10.10.0.x) uses the physical interface
- ✅ Tailscale traffic from remote networks continues working normally
- ✅ SSH access via Tailscale remains intact
Making It Permanent
To ensure the fix survives reboots, create a network startup script:
bash
sudo nano /etc/network/if-up.d/local-routingAdd this content:
bash
#!/bin/bash
# Fix Tailscale routing for local subnet
ip rule add from 10.10.0.0/24 to 10.10.0.0/24 lookup main priority 99 2>/dev/null || trueMake it executable:
bash
sudo chmod +x /etc/network/if-up.d/local-routingVerify it was created:
bash
ls -la /etc/network/if-up.d/local-routing
cat /etc/network/if-up.d/local-routingVerification
Check that the routing rule is active:
bash
ip rule show
```
You should see:
```
99: from 10.10.0.0/24 to 10.10.0.0/24 lookup mainThat’s it! Local access should now work normally while preserving all Tailscale functionality.
Key Takeaway
When using Tailscale’s subnet routing feature, be aware that it can inadvertently capture local traffic. Policy routing rules allow you to maintain both local connectivity and VPN access without conflicts. Always test connectivity from multiple network paths after enabling subnet sharing.
Feel free to adjust the tone/style to match your blog! Want me to tweak anything?
SB
ok, this is wierd. I rebooted the server and no I cannot ping it or bring up the web pages again…
Oh no! Let’s troubleshoot quickly.
First, can you SSH in via Tailscale?
If yes, once you’re in:
bash
# Check if the routing rule survived
ip rule show
# Check if the network is up
ip addr show enp1s0
# Check routing table
ip route show
# Check if services are running
systemctl status nginx
systemctl status ufwIf you CAN’T SSH via Tailscale either, you’ll need console access (KVM/QEMU console, VNC, or physical access).
Most likely issues:
- The
/etc/network/if-up.d/local-routingscript didn’t run or had an error - Network interface didn’t come up properly
- Tailscale started before the network was ready and grabbed the route again
Once you’re in, check:
bash
# Did the script run?
ls -la /etc/network/if-up.d/local-routing
cat /etc/network/if-up.d/local-routing
# Check system logs for errors
journalctl -b | grep -i "local-routing\|network\|tailscale"Can you get console access? Try SSH via Tailscale first – that should still work since the VPN routes are probably fine.
SB
root@www:/# ip rule show 0: from all lookup local 5210: from all fwmark 0x80000/0xff0000 lookup main 5230: from all fwmark 0x80000/0xff0000 lookup default 5250: from all fwmark 0x80000/0xff0000 unreachable 5270: from all lookup 52 32766: from all lookup main 32767: from all lookup default root@www:/#
There it is! The rule at priority 99 is missing. The script didn’t run.
Quick fix – add it back manually:
bash
sudo ip rule add from 10.10.0.0/24 to 10.10.0.0/24 lookup main priority 99Now let’s figure out why the script didn’t run:
bash
# Check if the script exists and is executable
ls -la /etc/network/if-up.d/local-routing
# Check its contents
cat /etc/network/if-up.d/local-routing
# Check if if-up.d scripts are being executed at all
ls -la /etc/network/if-up.d/
# Check system logs for any errors
journalctl -b | grep -i "if-up\|local-routing"Possible issues:
- Script lost execute permissions
- Script wasn’t saved properly
- The network subsystem doesn’t trigger if-up.d scripts on your system (some modern systems use systemd-networkd or netplan instead)
Let me know what you find – we may need to use a different method to make it permanent (like a systemd service).
SB
root@www:/# ls -la /etc/network/if-up.d/local-routing -rwxr-xr-x 1 root root 143 Dec 7 17:03 /etc/network/if-up.d/local-routing root@www:/# cat /etc/network/if-up.d/local-routing #!/bin/bash # Fix Tailscale routing for local subnet ip rule add from 10.10.0.0/24 to 10.10.0.0/24 lookup main priority 99 2>/dev/null || true root@www:/# root@www:/# ls -la /etc/network/if-up.d/ total 32 drwxr-xr-x 2 root root 4096 Dec 7 22:00 . drwxr-xr-x 6 root root 4096 Aug 19 2021 .. -rwxr-xr-x 1 root root 1053 Aug 19 2020 000resolvconf -rwxr-xr-x 1 root root 923 Aug 20 2018 avahi-autoipd -rwxr-xr-x 1 root root 1689 Jan 30 2024 ethtool -rwxr-xr-x 1 root root 143 Dec 7 17:03 local-routing -rwxr-xr-x 1 root root 385 Jul 19 2021 openvpn -rwxr-xr-x 1 root root 207 Jan 31 2025 postfix lrwxrwxrwx 1 root root 32 Sep 19 07:07 wpasupplicant -> ../../wpa_supplicant/ifupdown.sh root@www:/# root@www:/# journalctl -b | grep -i “if-up\|local-routing” Dec 07 21:59:41 www.blandford.tech sudo[10334]: root : TTY=/dev/pts/1 ; PWD=/ ; USER=root ; COMMAND=/usr/bin/nano /etc/network/if-up.d/local-routing root@www:/#Show more
The script exists, is executable, but never ran during boot. The only log entry is from when you created it with nano.
This suggests your system isn’t using the traditional /etc/network/interfaces system. Modern Ubuntu often uses netplan or systemd-networkd instead, which don’t trigger /etc/network/if-up.d/ scripts.
Let’s use a systemd service instead – this will work regardless:
bash
# Create a systemd service
sudo nano /etc/systemd/system/fix-tailscale-routing.serviceAdd this content:
ini
[Unit]
Description=Fix Tailscale local subnet routing
After=network-online.target tailscaled.service
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/sbin/ip rule add from 10.10.0.0/24 to 10.10.0.0/24 lookup main priority 99
RemainAfterExit=yes
StandardOutput=journal
[Install]
WantedBy=multi-user.targetSave and exit, then:
bash
# Enable and start the service
sudo systemctl daemon-reload
sudo systemctl enable fix-tailscale-routing.service
sudo systemctl start fix-tailscale-routing.service
# Verify it worked
sudo systemctl status fix-tailscale-routing.service
ip rule showThis will run after the network and Tailscale are up, ensuring the rule is always applied. Much more reliable than the if-up.d approach!
