Edit 3: Tested with wg-quick on Arch, same issue re-occurs. So, let’s say we have a peer on 192.168.1.1/24 with internal (wireguard) IP of 10.0.0.1/24, but we also want to route through it to rest of 192.168.1.0/24.
Instead of nice AllowedIPs = 10.0.0.0/24,192.168.1.0/24, it would have to be:
AllowedIPs = 10.0.0.0/24, 192.168.1.1/32, 192.168.1.2/31, 192.168.1.4/30, 192.168.1.8/29, 192.168.1.16/28, 192.168.1.32/27, 192.168.1.64/26, 192.168.1.128/25
Or there’s something else going wrong. I only tried on Arch. Welp, as I said, it’s not a thing that occurs with WG Tunnel on Android.
Edit 2: Hypothesis confirmed. Excluding the endpoint from AllowedIPs in NetworkManager solves the issue. However, this isn’t a problem with 0.0.0.0/0, nor with WG Tunnel app on Android. I’ll have to check with wg-quick. That seems most official.
Summary: NetworkManager tries to route traffic to WG peer over the same WG interface, and its /32 has to be excluded.
Edit: I noticed one thing, I’ll try excluding the peer endpoint from AllowedIPs. It seems weird if it tries to connect to it over the interface between the 2 peers, which is of course impossible, but maybe? However, it is not matched by 0.0.0.0/0. Welp, time to experiment.
So, for 2 years I thought that NetworkManager Wireguard implementation is simply broken.
When I used a list of address ranges, like I should be (and am) able to do with Wireguard, I couldn’t get any traffic through, however 0.0.0.0/0,::/0 would work.
Today I discovered something… interesting. It actually works… with a smaller list of AllowedIPs. Although even a larger list still ends up being shown by ip r.
So I went to AllowedIPs calculator as usual, created a desired list, pasted it in, and started removing IP ranges until I could ping a remote peer.
Problem solved? Well, no. I hoped it would be the limitation in number of routes, but it (also) seems to depend on route size.
Examples:
This is too much:
0.0.0.0/5,8.0.0.0/7,11.0.0.0/8,12.0.0.0/6,16.0.0.0/4,32.0.0.0/3,64.0.0.0/2,128.0.0.0/3,160.0.0.0/5,168.0.0.0/6,172.0.0.0/12,172.32.0.0/11,172.64.0.0/10,172.128.0.0/9,10.147.0.0/24
Removing one of the routes, 172.128.0.0/9 makes it work.
0.0.0.0/5,8.0.0.0/7,11.0.0.0/8,12.0.0.0/6,16.0.0.0/4,32.0.0.0/3,64.0.0.0/2,128.0.0.0/3,160.0.0.0/5,168.0.0.0/6,172.0.0.0/12,172.32.0.0/11,172.64.0.0/10,10.147.0.0/24
Time for mystery start. Keeping the same number of routes, but decreasing the size of one of them (second last) also makes it work:
0.0.0.0/5,8.0.0.0/7,11.0.0.0/8,12.0.0.0/6,16.0.0.0/4,32.0.0.0/3,64.0.0.0/2,128.0.0.0/3,160.0.0.0/5,168.0.0.0/6,172.0.0.0/12,172.32.0.0/11,172.64.0.0/10,172.128.0.0/10,10.147.0.0/24
Naturally, I tried breaking up 172.128.0.0/9 into 172.128.0.0/10 and 172.192.0.0/10, which breaks it again.
So, it seems to depend on both number and size of the routes. After all, larger ones alone worked.


You found the right workaround.
The Arch Wiki calls this “Loop routing,” where NetworkManager attempts to route traffic to the WireGuard peer’s endpoint through the tunnel itself, creating a routing loop. This occurs because the endpoint IP gets matched by the AllowedIPs ranges, causing the kernel to send handshakes over the tunnel interface instead of the physical interface. Excluding the peer endpoint from AllowedIPs is the standard fix.
Here’s the ArchWiki link (for future readers mostly, you already got it :P): https://wiki.archlinux.org/title/WireGuard#Loop_routing