Resolving WireGuard connectivity issues in Docker and Kubernetes requires verifying that the client configuration correctly maps to the host’s network namespace and that IP forwarding is enabled on the underlying Linux node. Common failures stem from missing kernel modules, incorrect peer endpoints, or firewall rules blocking UDP traffic on port 51820. This guide provides a systematic approach to diagnosing handshake failures, routing loops, and DNS resolution problems within containerized environments.
Verifying Kernel Module and Host Configuration
Before diving into container-specific settings, ensure the host system supports WireGuard at the kernel level. The
wireguard module must be loaded and visible in the output of
lsmod | grep wireguard. If it is missing, install the appropriate package for your distribution, such as
linux-modules-extra-$(uname -r) on Ubuntu or
kmod-wireguard on RHEL-based systems.
Additionally, IP forwarding must be enabled on the host to allow traffic to route between the WireGuard interface and other networks. Run
sysctl net.ipv4.ip_forward=1 to enable this temporarily. To make it persistent across reboots, add
net.ipv4.ip_forward = 1 to
/etc/sysctl.conf or a dedicated file in
/etc/sysctl.d/. Without this setting, even if the WireGuard tunnel establishes successfully, packets will not traverse between the virtual private network and the public internet or local subnet.
Docker Network Namespace and Privileged Access
Docker containers run in isolated network namespaces by default. When running a WireGuard client inside a container, you must ensure that the container has access to the necessary network interfaces and capabilities. The most common issue arises when the container cannot bind to the
wg0 interface because it lacks privileged access.
To resolve this, start your container with the
--cap-add=NET_ADMIN flag. This grants the container the ability to configure network interfaces, which is essential for creating and managing WireGuard tunnels. In some cases, particularly when using
network_mode: host, you may also need to set
--privileged=true to allow full access to the host’s network stack. However, prefer
--cap-add=NET_ADMIN over full privilege to maintain security best practices.
If you are using Docker Compose, your service definition should look like this:
services:
wireguard-client:
image: linuxserver/wireguard
cap_add:
- NET_ADMIN
devices:
- /dev/net/tun:/dev/net/tun
volumes:
- ./config:/config
environment:
- PGID=1000
- PUID=1000
Note the inclusion of the
/dev/net/tun device. WireGuard relies on TUN/TAP devices to create virtual network interfaces. Without mapping this device into the container, the application will fail to initialize the tunnel interface, resulting in immediate connection errors.
Kubernetes Pod Security and Service Mesh Integration
In Kubernetes, the process is more complex due to the abstraction layers provided by CNI (Container Network Interface) plugins and pod security policies. By default, standard pods do not have
NET_ADMIN capabilities. You must explicitly grant these permissions via a Security Context in your Pod or Deployment manifest.
securityContext:
capabilities:
add:
- NET_ADMIN
privileged: false
Setting
privileged: true is generally discouraged in production Kubernetes clusters due to security risks. Instead, rely on the
NET_ADMIN capability. Additionally, ensure that the CNI plugin used by your cluster supports custom routes. Some cloud providers’ managed Kubernetes services restrict the ability to add custom routes or modify network interfaces directly.
Another critical aspect in Kubernetes is handling DNS resolution. WireGuard clients often need to resolve internal domain names through the VPN gateway. Ensure that your CoreDNS or kube-dns configuration allows queries to pass through the tunnel. If you experience "no internet" issues despite a successful handshake, check if the default route is being overridden incorrectly. Use
ip route show table all inside the pod to verify that traffic destined for external IPs is routed through the WireGuard interface, while local traffic remains on the primary interface.
For detailed troubleshooting of similar network isolation issues, see
Converting WireGuard Docker Setup to Kubernetes: Handshake OK, No Internet Access.
Firewall Rules and Port Forwarding
WireGuard uses UDP port 51820 by default. Firewalls on both the host and any intermediate routers must allow inbound and outbound traffic on this port. On Linux hosts using
ufw, run
sudo ufw allow 51820/udp. For
iptables, ensure there are no DROP rules blocking UDP 51820.
In Kubernetes, if you are exposing the WireGuard server externally via a LoadBalancer or NodePort, ensure that the service type is set to
NodePort or
LoadBalancer and that the target port matches the WireGuard listening port. Cloud provider firewalls (such as AWS Security Groups or Azure NSGs) also need to be configured to allow ingress on port 51820 from the source IP addresses of your clients.
If you are experiencing issues accessing local network resources through the WireGuard tunnel, refer to
Unable to Access Services on Local Network (192.168.1.0/24) with Wireguard and Caddy Docker for insights on routing and subnet configurations.
Diagnosing Handshake and Routing Failures
When the connection fails, use
wg quick down wg0 and
wg quick up wg0 to restart the interface and check logs. Look for errors related to key mismatches or invalid configurations. The
wg show command provides real-time statistics, including last handshake time. If the handshake time is "never," the endpoint is unreachable or blocked by a firewall.
Use
tcpdump -i wg0 to capture traffic on the WireGuard interface. This helps determine if packets are being encrypted and sent correctly. If you see outgoing packets but no incoming responses, the issue likely lies in the remote server’s configuration or network path. Conversely, if you see incoming packets but no response, the problem is usually on the client side, such as incorrect routing tables or disabled IP forwarding.
Check the
AllowedIPs field in the client configuration. It must match the subnets you want to route through the tunnel. A common mistake is setting
AllowedIPs = 0.0.0.0/0 without ensuring that the server is configured to forward all traffic to the internet. If you only want to route specific subnets, specify them explicitly, such as
AllowedIPs = 10.0.0.0/24.
Frequently Asked Questions
Why does my WireGuard container lose internet access after connecting?
This typically happens because the default route is not being updated correctly or IP forwarding is disabled on the host. Ensure that
sysctl net.ipv4.ip_forward=1 is active and that your WireGuard configuration includes
0.0.0.0/0 in
AllowedIPs if you intend to route all traffic. Verify that the container’s default gateway points to the WireGuard interface.
Can I run multiple WireGuard clients in separate Docker containers?
Yes, each container can run its own instance, but they must use different internal interface names and ports to avoid conflicts. You must map distinct host ports to each container’s WireGuard listening port and ensure that the
/dev/net/tun device is accessible to all containers.
How do I troubleshoot DNS resolution inside a WireGuard Kubernetes pod?
DNS issues often arise because the pod cannot reach the upstream DNS server through the tunnel. Add the DNS server IP to the
AllowedIPs list in your WireGuard configuration to ensure DNS queries are routed through the tunnel. Alternatively, configure the pod to use an internal DNS resolver that is reachable via the Kubernetes service mesh.
What is the difference between network_mode: host and bridge for WireGuard?
Using
network_mode: host allows the container to share the host’s network stack, simplifying routing and firewall interactions but reducing isolation. The
bridge mode isolates the container but requires explicit port mapping and careful routing configuration to ensure traffic flows correctly through the WireGuard interface.
Why is my WireGuard handshake failing even though the port is open?
Handshake failures are often caused by mismatched public keys or incorrect endpoint addresses. Verify that the client’s
PublicKey matches the server’s
PrivateKey and vice versa. Also, ensure that the
Endpoint in the client configuration points to the correct public IP and port of the WireGuard server.