Troubleshooting¶
Detailed scenarios from real-world debugging sessions with solutions.
Scenario 1: Handshake Never Completes¶
Symptoms:
wg show wire_kube
peer: <pubkey>
endpoint: 1.2.3.4:51820
latest handshake: (none)
transfer: 0 B received, 1.2 KiB sent
Root Cause: Both peers are behind Symmetric NAT. STUN-discovered endpoints are unreliable because each new destination gets a different NAT mapping.
Diagnosis:
# Check agent logs for NAT type
kubectl logs -n wirekube-system <agent-pod> | grep -i "symmetric\|nat type"
# Manual STUN check (run on the node)
stun stun.cloudflare.com 3478
stun stun.l.google.com 19302
# If mapped ports differ → Symmetric NAT
Fix:
- Ensure relay is configured:
kubectl get wirekubemesh default -o yaml | grep relay - Set
relay.mode: autoand verifyhandshakeTimeoutSeconds - Check relay connectivity:
nc -zv <relay-endpoint> 3478 - Restart the agent:
kubectl rollout restart ds/wirekube-agent -n wirekube-system
Scenario 2: EPERM on UDP Write (Cilium)¶
Symptoms:
Root Cause: Cilium's cgroup BPF program (cil_sock4_sendmsg) intercepts
sendto() calls and returns EPERM.
Fix:
The adaptive proxy auto-switches to syscall.Write mode. Check agent logs:
Alternative: set Cilium socketLB.hostNamespaceOnly: true to prevent BPF hooks
on hostNetwork: true pods.
See CNI Compatibility for details.
Scenario 3: Relay Connection Timeout¶
Symptoms:
Root Cause: Relay server unreachable (firewall, service down, NLB health check).
Diagnosis:
# Test TCP connectivity from node
nc -zv <relay-endpoint> 3478
# Check relay pods
kubectl get pods -n wirekube-system -l app=wirekube-relay
# Check the cluster-local control Service for managed relay
kubectl get svc wirekube-relay-control -n wirekube-system -o wide
Fix:
The relay client auto-reconnects with exponential backoff (1s–30s). If the relay is persistently unreachable:
- Check firewall or security group rules for the selected relay endpoint
- For managed relay, verify cluster DNS and routing to
wirekube-relay-control.<namespace>.svc.cluster.local - For external relay, verify the public LoadBalancer or NodePort address
- After fixing, the agent reconnects automatically without a restart
Scenario 4: IPSec xfrm Conflict¶
Symptoms:
pingbetween nodes shows 100% packet losstcpdump -i wire_kubeshows both outgoing requests and incoming replies- But
pingstill reports no packets received
Root Cause: Existing IPSec xfrm policies intercept traffic on the wire_kube
interface. The kernel applies xfrm policies to inbound packets and drops them
because they weren't encrypted with IPSec.
Diagnosis:
# Check if xfrm bypass is enabled
cat /proc/sys/net/ipv4/conf/wire_kube/disable_xfrm
cat /proc/sys/net/ipv4/conf/wire_kube/disable_policy
# Both should be 1
# Check for IPSec xfrm policies
ip xfrm policy show
Fix:
The agent sets disable_xfrm=1 and disable_policy=1 on the WireGuard interface
automatically. If these values are 0:
- Check that the DaemonSet mounts
/proc/sys/netfrom the host - The volume
host-proc-sys-netshould mount hostPath/proc/sys/netto/host/proc/sys/net - Check agent logs for
xfrm bypass enabledor sysctl warnings - Manual override:
echo 1 > /proc/sys/net/ipv4/conf/wire_kube/disable_xfrm
Scenario 5: Relay Proxy Address in CRD¶
Symptoms:
kubectl get wirekubepeer <name> -o yaml
# spec.endpoint: "127.0.0.1:54321" ← relay proxy address, not real endpoint
Root Cause: The NAT endpoint reflection feature wrote the relay proxy's local loopback address back into the CRD.
Fix:
The agent filters 127.0.0.1:* from NAT reflection. To manually fix:
kubectl patch wirekubepeer <name> --type merge \
-p '{"spec":{"endpoint":"<correct-public-ip>:51820"}}'
Scenario 6: Same-VPC Nodes Cannot Communicate¶
Symptoms:
- Nodes in the same VPC/subnet fail to establish WireGuard handshake
wg showshows packets sent but 0 received
Root Cause: Missing fwmark routing rule causes a WireGuard packet loop.
Diagnosis:
ip rule show | grep 0x574B
# Should show a main-table fwmark rule below the local-table rule and above priority 200
Fix:
The agent creates and repairs this rule automatically. The exact priority is dynamic and may be greater than 110 when the host moves the local-table rule.
Scenario 7: Stale Interface After Crash¶
Symptoms:
Root Cause: A previous agent instance crashed and left a stale wire_kube interface.
Fix:
The default DaemonSet has no initContainer. The userspace engine attempts to reattach an existing TUN, migrates a legacy kernel WireGuard link, and refuses to delete a foreign interface. For manual cleanup of abandoned WireKube state:
ip link del wire_kube 2>/dev/null
ip rule del fwmark 0x574B 2>/dev/null
ip route flush table 22347 2>/dev/null
Scenario 8: AllowedIPs Empty → No Traffic¶
Symptoms:
Peer CRD exists but no routes are added and no traffic flows.
Root Cause: AllowedIPs are intentionally user-managed. When empty, the agent enters passive mode and adds no routes for any peer.
Fix:
Scenario 9: Public Endpoint Overwritten with Private IP¶
Symptoms:
A node's CRD endpoint shows a private IP (e.g., 10.0.0.5:51820) instead of
the STUN-discovered public IP.
Root Cause: Another agent's reflectNATEndpoints wrote a private IP from
its WireGuard kernel cache back to the CRD, overwriting the public endpoint.
Fix:
The agent's reflectNATEndpoints function prevents downgrading a public IP to
a private IP. If the CRD is already corrupted:
kubectl patch wirekubepeer <name> --type merge \
-p '{"spec":{"endpoint":"<correct-public-ip>:51820"}}'
Scenario 10: Managed Relay DNS or Service Unreachable¶
Symptoms:
The agent repeatedly fails to connect to wirekube-relay-control.wirekube-system.svc.cluster.local:3478.
Root Cause: provider: managed uses the cluster-local relay control Service, but this node cannot resolve cluster DNS or route to Services during bootstrap.
Fix:
Verify the control Service and DNS from the affected node. If cluster service routing cannot be made available before WireKube starts, switch the mesh to provider: external and use the relay's public LoadBalancer or NodePort address:
See Relay Entry Points for LoadBalancer, NodePort, and HTTP CONNECT proxy configurations.
Scenario: Agent crashes with creating TUN wire_kube: operation not permitted¶
Symptoms: On Ubuntu 24.04 + kernel ≥ 6.14 hosts the agent loops with
ERROR agent setup failed, retrying
{"error":"creating WireGuard interface: creating TUN wire_kube: operation not permitted"}
even though CAP_NET_ADMIN is granted and /dev/net/tun exists with crw-rw-rw-.
Root cause: containerd's default device cgroup denies write on
/dev/net/tun regardless of the granted Linux capabilities, and the
stock AppArmor profile (cri-containerd.apparmor.d) also blocks the
TUNSETIFF ioctl wireguard-go issues after opening the device. Older
distros (Ubuntu 22.04 / kernel 6.8) happen to have a more permissive
default and work with just CAP_NET_ADMIN, which is why the shipped
DaemonSet originally didn't set privileged: true.
Fix: The shipped config/agent/daemonset.yaml now enables
privileged: true and appArmorProfile: Unconfined. Re-apply or patch
an existing cluster:
spec:
template:
spec:
containers:
- name: agent
securityContext:
privileged: true
appArmorProfile:
type: Unconfined
capabilities:
add: ["NET_ADMIN", "SYS_MODULE"]
The agent already runs with hostNetwork: true, so the added surface
from privileged is narrow. It is the only K8s-visible switch
that loosens the device cgroup enough for TUN creation.
Scenario: Public IP appears in AllowedIPs and SSH breaks¶
Symptoms: After enabling autoAllowedIPs.includeNodeInternalIP on a
cloud provider whose kubelet exposes the public IP as Node.InternalIP
(Oracle Cloud, etc.), SSH and kubelet traffic to other nodes
start timing out. kubectl get wirekubepeer -o yaml shows entries like
[<meshIP>/32, <public-IP>/32].
Root cause: Older agent builds (≤ v0.0.10-dev.5) fell back to the
Node.InternalIP value even when it was a public address, which caused
the agent to add a /32 route for that public IP onto wire_kube. The
next tunnel flap — or even just agent restart — then hijacked SSH,
kubelet, and apiserver traffic.
Fix: Upgrade to v0.0.10-dev.6 or later. The agent now:
- Walks
Node.status.addressesand uses only private (RFC1918 / CGNAT / loopback / link-local) entries. - If none are present, scans local interfaces directly, so cloud instances that hide a private secondary behind a kubelet-reported public InternalIP still get picked up.
- Never, under any code path, auto-publishes a public IP. Operators
who want a specific non-standard private address can set the
wirekube.io/internal-ipnode annotation to override selection.
Cleanup after upgrade:
# Delete stale peer CRs so the next agent restart recreates them
# without the lingering public-IP entry.
kubectl delete wirekubepeer --all
kubectl -n wirekube-system rollout restart ds/wirekube-agent
Diagnostic Commands Reference¶
| Command | Purpose |
|---|---|
wg show wire_kube |
WireGuard interface status |
wg show wire_kube dump |
Machine-parseable peer dump |
ip route show dev wire_kube |
Routes through WireGuard |
ip route show table 22347 |
WireKube routing table |
ip rule show |
Routing policy (check fwmark 0x574B) |
ss -tnp \| grep 3478 |
Relay TCP connection status |
kubectl get wirekubepeers -o wide |
All peer CRDs |
kubectl logs -n wirekube-system -l app.kubernetes.io/name=wirekube-agent |
Agent logs |
tcpdump -i wire_kube -n |
WireGuard decrypted traffic |
tcpdump -i eth0 udp port 51820 |
WireGuard encrypted packets |
cat /proc/sys/net/ipv4/conf/wire_kube/disable_xfrm |
xfrm bypass status |
cat /proc/sys/net/ipv4/conf/wire_kube/disable_policy |
xfrm policy bypass status |