Fix: Aliyun OSS Internal IP Overlaps with Tailscale CGNAT and Breaks Access (Debian)
Date: 2026/02/18Last Updated: 2026-03-18T16:46:07.000Z
Categories: CS
Tags: aliyun, tailscale
Read Time: 2 minutes
0.1 Contents
1 Fix: Aliyun OSS Internal IP Overlaps with Tailscale CGNAT and Breaks Access (Debian)
1.1 Problem
Tailscale assigns node IPv4 addresses from the CGNAT range 100.64.0.0/10 (RFC 6598).
Aliyun OSS internal endpoints can also resolve to IPs within this same range.
For example, according to
Aliyun official documentation,
the Hong Kong OSS internal endpoint oss-cn-hongkong-internal.aliyuncs.com
may resolve to addresses in:
100.115.61.0/24100.99.103.0/24100.99.104.0/24100.99.106.0/24
I first noticed the issue when OSS access via the internal endpoint failed on an Aliyun ECS instance.
I also saw similar failures when accessing the Aliyun internal Debian package mirror.
Switching Debian mirror traffic to a public mirror is a workable workaround.
However, switching OSS traffic to the public endpoint is not ideal because it can increase costs:
internal OSS traffic is free, while public endpoint traffic is billed.
Packet-level testing showed that traffic to these /24 ranges left via the Aliyun internal interface,
and replies arrived at the interface, but applications still timed out (for example, ping).
1.2 Root cause
From Tailscale documentation:
when netfilter-mode is enabled, Tailscale installs iptables rules to prevent CGNAT spoofing:
Traffic from
100.64.0.0/10that does not arrive throughtailscale0is dropped.
In practice, the ts-input chain includes a rule like:
-A ts-input -s 100.64.0.0/10 ! -i tailscale0 -j DROP
So replies from OSS hosts (for example, 100.115.61.1) that arrive on ens5 (or another non-tailscale0 interface) are dropped before they reach user-space applications—even though packet capture confirms the replies are arriving.
1.3 Solution
Add a narrow allowlist exception for the specific OSS /24 ranges before Tailscale’s CGNAT anti-spoof DROP rule.
The simplest approach is to insert RETURN rules into ts-input:
- match packets with source IPs in the specific OSS
/24ranges - only when they arrive on interfaces other than
tailscale0
This keeps Tailscale’s anti-spoof protection for the rest of 100.64.0.0/10, while allowing known legitimate ranges on your LAN/VPC path.
Disabling netfilter integration entirely also fixes the problem, but weakens protection more broadly.
A targeted allowlist is the safer compromise.
1.4 One-time manual fix (immediate)
Insert exceptions into ts-input before the CGNAT DROP rule:
sudo iptables -I ts-input 3 -s 100.115.61.0/24 ! -i tailscale0 -j RETURN
sudo iptables -I ts-input 3 -s 100.99.103.0/24 ! -i tailscale0 -j RETURN
sudo iptables -I ts-input 3 -s 100.99.104.0/24 ! -i tailscale0 -j RETURN
sudo iptables -I ts-input 3 -s 100.99.106.0/24 ! -i tailscale0 -j RETURN
Verify rule order:
sudo iptables -S ts-input
Verify connectivity:
ping -c 3 100.115.61.1
1.5 Persistence on Debian (recommended): systemd oneshot
Tailscale may recreate or rewrite ts-* iptables chains during restart or upgrade.
For reliability, re-apply the allowlist after tailscaled starts.
1.5.1 Script
Create:
/usr/local/sbin/tailscale-cgnat-allow-corp.sh
#!/bin/sh
set -eu
# OSS/internal CGNAT subnets that must be allowed on non-tailscale interfaces
SUBNETS="
100.115.61.0/24
100.99.103.0/24
100.99.104.0/24
100.99.106.0/24
"
# Wait briefly for tailscaled to install ts-input chain (avoid boot race)
for i in 1 2 3 4 5; do
if iptables -S ts-input >/dev/null 2>&1; then
break
fi
sleep 1
done
# Insert RETURN rules near the top of ts-input, before the CGNAT DROP.
# Use -C first to keep it idempotent (safe to run repeatedly).
for net in $SUBNETS; do
if ! iptables -C ts-input -s "$net" \! -i tailscale0 -j RETURN 2>/dev/null; then
iptables -I ts-input 3 -s "$net" \! -i tailscale0 -j RETURN
fi
done
Make it executable:
sudo chmod 0755 /usr/local/sbin/tailscale-cgnat-allow-corp.sh
1.5.2 systemd unit
Create:
/etc/systemd/system/tailscale-cgnat-allow-corp.service
[Unit]
Description=Allow selected CGNAT subnets outside tailscale0 (Tailscale anti-spoof override)
After=tailscaled.service network-online.target
Wants=tailscaled.service network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/tailscale-cgnat-allow-corp.sh
[Install]
WantedBy=multi-user.target
Enable and run:
sudo systemctl daemon-reload
sudo systemctl enable --now tailscale-cgnat-allow-corp.service
1.5.3 Verify persistence across Tailscale restart
sudo systemctl restart tailscaled
sudo iptables -S ts-input | sed -n '1,30p'
ping -c 3 100.115.61.1