Encrypt Kubernetes Pod Traffic with Cilium WireGuard

Enable Cilium transparent WireGuard encryption on a running cluster, verify peers and handshake state, capture plaintext versus encrypted cross-node Pod traffic, and document what traffic stays outside the tunnel.

Published

Updated

Read time 16 min read

Reviewed byDeepak Prasad

Cilium WireGuard transparent encryption protecting cross-node Kubernetes Pod traffic between worker nodes
Tested on Rocky Linux 10.2 (Red Quartz) kubeadm nodes and administration host
Package kubectl 1.36.3
helm 3.21.3
cilium 1.19.6
cilium-cli 0.19.6
wireguard-tools
tcpdump
Applies to RHEL, Rocky Linux, AlmaLinux, Oracle Linux, CentOS Stream, Fedora
Kubernetes cluster with Cilium CNI
Cert prep CKS
Lab environment Multi-node cluster with Cilium CNI and SSH access to nodes for packet capture — install Kubernetes with kubeadm plus an existing Cilium deployment
Privilege cluster-admin kubectl for Helm values and test workloads; root on nodes for tcpdump and wg show
Scope Encryption scope boundaries, Cilium and WireGuard prerequisite checks, cross-node test Pods, plaintext baseline capture, Helm WireGuard enablement, rollout verification, peer and key inspection, encrypted capture, same-node and hostNetwork exclusions, brief WireGuard versus IPsec comparison, and troubleshooting. Assumes Cilium is already the cluster CNI. Does not cover Cilium installation, NetworkPolicy YAML, IPsec configuration, service mesh mTLS, deep packet-analysis training, or cross-cluster encryption.
Related guides Kubernetes networking
Multi-tenancy and workload isolation
Kubernetes security architecture
Kubernetes Services

Cilium can encrypt Pod traffic in transit between nodes without changing application manifests. WireGuard wraps cross-node packets on the node underlay while Pods keep ordinary ClusterIP and Pod IP addresses. This walkthrough records a plaintext baseline on a two-node kubeadm lab running Cilium 1.19.6, enables encryption.type=wireguard, and repeats the same client-to-server test to compare underlay captures before and after encryption.

This lab successfully used Cilium 1.19.6 with Kubernetes 1.36.3. Kubernetes 1.36 is not included in the Cilium 1.19.6 guaranteed compatibility matrix, so verify the supported pairing before reproducing this outside the lab.

IMPORTANT
This guide enables transparent WireGuard encryption in an existing Cilium cluster. It does not install Cilium, replace another CNI, or encrypt every packet that touches the cluster. By default, Cilium encrypts cross-node traffic between Cilium-managed Pods, including Pods on control-plane nodes. Same-node Pods, node and hostNetwork traffic, and internet egress stay outside default Pod encryption unless you add encryption.nodeEncryption—control-plane host traffic remains opted out by default.

Define the encryption scope

Transparent encryption protects traffic on the path between nodes. It does not turn the entire cluster into one encrypted enclave:

Traffic path Default WireGuard scope on this lab
Cross-node Pod to Pod Encrypted on node underlay (UDP WireGuard tunnel)
Same-node Pod to Pod Not encrypted through inter-node WireGuard
Node to node (kubelet, host processes) Not encrypted unless encryption.nodeEncryption=true
hostNetwork Pods Not covered by default Pod encryption model
Host traffic to or from control-plane nodes Excluded from node-to-node encryption by default. Cilium-managed Pod-to-Pod traffic involving Pods on control-plane nodes is still encrypted.
External egress (internet, VPC peering) Leaves the cluster unencrypted by Cilium WireGuard
Traffic via ClusterIP Service Encrypted when backend Pods are on different nodes (datapath still crosses nodes)

Use WireGuard for defense in depth on the node underlay. Pair it with NetworkPolicy for allow-list semantics and with mesh or ingress TLS when you need application-layer protection.

WireGuard enablement and encryption enforcement are not identical. Cilium must first identify an address as belonging to a remote managed endpoint. During endpoint discovery, initial packets may be transmitted without encryption. Cilium provides encryption strict mode when the cluster must drop traffic rather than permit an unencrypted transition, but strict mode requires the correct Pod CIDR and has additional routing and IPv6 limitations.


Verify the Cilium environment

Confirm Cilium agents are healthy before you change encryption settings.

Use the external Cilium CLI for cluster-wide health:

bash
cilium status --wait

Use cilium-dbg inside an agent Pod for the detailed KVStore, Routing, KubeProxyReplacement, and Encryption lines:

bash
kubectl -n kube-system exec ds/cilium -- \
  cilium-dbg status
output
KVStore:                 Disabled
Kubernetes:              Ok         1.36 (v1.36.3) [linux/amd64]
Kubernetes APIs:         ["EndpointSliceOrEndpoint", "cilium/v2::CiliumCIDRGroup", ...]
KubeProxyReplacement:    False
Routing:                 Network: Tunnel [vxlan]   Host: Legacy
Encryption:              Disabled
IPAM:                    IPv4: 4/254 allocated from 192.168.1.0/24,

Before enablement the Encryption line read Disabled. After WireGuard is on, cilium-dbg status reports Wireguard with interface cilium_wg0, public key, UDP port 51871, and peer count.

List Cilium node resources from the API:

bash
kubectl get ciliumnodes
output
NAME       AGE
k8s-cp     42m
worker01   42m

Check agent Pods in kube-system:

bash
kubectl get pods -n kube-system -l k8s-app=cilium -o wide
output
NAME           READY   STATUS    RESTARTS   AGE   IP               NODE
cilium-7x9kp   1/1     Running   0          38m   192.168.56.108   k8s-cp
cilium-zm4fd   1/1     Running   0          37m   192.168.56.109   worker01

Record these values for your cluster notes:

  • Cilium version: 1.19.6 (kubectl -n kube-system exec ds/cilium -- cilium-dbg version)
  • Routing mode: tunnel with vxlan overlay
  • kube-proxy replacement: false (cluster still uses kube-proxy)
  • Encryption: disabled until the Helm upgrade in a later section

Verify WireGuard kernel support

WireGuard encryption needs kernel module support on every node that participates in the tunnel.

Load the module and list it:

bash
modprobe wireguard
lsmod | grep wireguard
output
wireguard             122880  0
curve25519_x86_64      36864  1 wireguard
libchacha20poly1305    16384  1 wireguard

Confirm the running kernel:

bash
uname -r
output
6.12.0-211.34.1.el10_2.x86_64

Firewall and MTU checks:

  • Open UDP 51871 between all Cilium node underlay addresses before you enable WireGuard. Cilium uses that fixed port on every node—it is not dynamically assigned per agent.
  • Tunnel mode adds overlay headers; if you see fragmentation or TLS handshake stalls after encryption, lower Pod interface MTU or raise underlay MTU per Cilium documentation.
  • Capture on the node underlay interface (for example enp0s8 at 192.168.56.0/24), not only inside the Pod network namespace.

Open the port on every Cilium node before the Helm upgrade:

bash
firewall-cmd --permanent --add-port=51871/udp
firewall-cmd --reload
firewall-cmd --list-ports

Install inspection tools on nodes where you capture packets:

bash
dnf install -y wireguard-tools tcpdump

On Debian-family nodes, use apt install wireguard-tools tcpdump instead.


Create cross-node test Pods

Place a client on one node and a server on another so traffic must cross the underlay. Use nodeAffinity for reproducible placement.

Create the namespace and server Deployment on worker01. Disable Istio sidecar injection so the mesh does not alter the traffic you capture:

bash
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Namespace
metadata:
  name: cilium-enc-lab
  labels:
    istio-injection: disabled
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: enc-server
  namespace: cilium-enc-lab
spec:
  replicas: 1
  selector:
    matchLabels:
      app: enc-server
  template:
    metadata:
      labels:
        app: enc-server
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: kubernetes.io/hostname
                operator: In
                values: ["worker01"]
      containers:
      - name: nginx
        image: registry.k8s.io/e2e-test-images/nginx:1.14-2
        ports:
        - containerPort: 80
EOF

Schedule the client on k8s-cp. nodeAffinity alone does not override the control-plane NoSchedule taint, so add a toleration:

bash
kubectl apply -f - <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: enc-client
  namespace: cilium-enc-lab
spec:
  replicas: 1
  selector:
    matchLabels:
      app: enc-client
  template:
    metadata:
      labels:
        app: enc-client
    spec:
      tolerations:
      - key: node-role.kubernetes.io/control-plane
        operator: Exists
        effect: NoSchedule
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: kubernetes.io/hostname
                operator: In
                values: ["k8s-cp"]
      containers:
      - name: busybox
        image: registry.k8s.io/e2e-test-images/busybox:1.29-4
        command: ["sleep", "3600"]
EOF

Expose the server with a ClusterIP Service:

bash
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Service
metadata:
  name: enc-server
  namespace: cilium-enc-lab
spec:
  selector:
    app: enc-server
  ports:
  - port: 80
    targetPort: 80
EOF

Wait for Ready Pods and note placement:

bash
kubectl rollout status deployment/enc-server \
  -n cilium-enc-lab --timeout=2m

kubectl rollout status deployment/enc-client \
  -n cilium-enc-lab --timeout=2m

kubectl get pods -n cilium-enc-lab -o wide
output
NAME                          READY   STATUS    RESTARTS   AGE   IP              NODE
enc-client-6d8f9c7b4d-xxxxx   1/1     Running   0          31s   192.168.0.59    k8s-cp
enc-server-7b5c8d9f6-xxxxx    1/1     Running   0          45s   192.168.1.169   worker01

Record the server Pod IP dynamically—it changes whenever the Pod is recreated:

bash
SERVER_IP=$(
  kubectl get pod -n cilium-enc-lab \
    -l app=enc-server \
    -o jsonpath='{.items[0].status.podIP}'
)

echo "$SERVER_IP"
output
192.168.1.169

Use ${SERVER_IP} in the connectivity and capture steps below.


Capture the baseline

Confirm application connectivity before you enable encryption.

Direct Pod IP request from the client:

bash
kubectl exec -n cilium-enc-lab deploy/enc-client -- \
  wget -qO- "http://${SERVER_IP}/"
output
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>

ClusterIP request through the Service:

bash
kubectl exec -n cilium-enc-lab deploy/enc-client -- wget -qO- http://enc-server.cilium-enc-lab.svc.cluster.local/
output
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>

Both paths succeeded with encryption still disabled.

On the source node (k8s-cp), capture underlay traffic while you run the Pod IP wget again. Use the interface that carries node-to-node traffic (enp0s8 on this lab):

bash
tcpdump -ni enp0s8 host 192.168.56.109 and port 8472 -c 6

In another session, repeat the direct Pod IP wget from the client.

output
listening on enp0s8, link-type EN10MB (Ethernet), snapshot length 262144 bytes
06:00:12.034604 IP 192.168.56.108.35365 > 192.168.56.109.8472: OTV, flags [I] (0x08), overlay 0, instance 10761
IP 192.168.0.59.49556 > 192.168.1.169.80: Flags [S], seq 4082225324, win 64860, options [mss 1410,sackOK,TS val 107287816 ecr 0,nop,wscale 7], length 0
06:00:12.036435 IP 192.168.56.109.43389 > 192.168.56.108.8472: OTV, flags [I] (0x08), overlay 0, instance 20454
IP 192.168.1.169.80 > 192.168.0.59.49556: Flags [S.], seq 3468167809, ack 4082225325, win 64308, ...
6 packets captured

The inner TCP flow 192.168.0.59 → 192.168.1.169:80 is visible inside VXLAN on the underlay. That is the plaintext baseline you compare after WireGuard is enabled.


Enable WireGuard

Enable encryption with Helm on a running release. Confirm Helm is available and record the current Cilium release before you change values:

bash
helm version --short
helm list -n kube-system
helm status cilium -n kube-system
output
v3.21.3+g1ad6e68
NAME  	NAMESPACE  	REVISION	UPDATED                                	STATUS  	CHART        	APP VERSION
cilium	kube-system	4       	2026-07-28 07:28:59.849747508 +0530 IST	deployed	cilium-1.19.6	1.19.6

Preserve the active values so you can roll back:

bash
helm get values cilium -n kube-system -o yaml \
  > /root/cilium-values-before-wireguard.yaml

helm history cilium -n kube-system

These commands assume an existing Helm release named cilium. If your release has another name, replace it in the upgrade and rollback commands. The official Cilium workflow uses Helm values to set encryption.enabled and encryption.type=wireguard.

This lab upgraded the existing cilium release:

bash
helm upgrade cilium cilium/cilium --version 1.19.6 -n kube-system \
  --reuse-values \
  --set encryption.enabled=true \
  --set encryption.type=wireguard

Helm reports a new revision and Cilium writes enable-wireguard: "true" into its ConfigMap.

If agents fail init with dial tcp 10.96.0.1:443: i/o timeout after the upgrade, the Kubernetes Service path through kube-proxy or the host firewall needs investigation before you bypass it:

bash
helm upgrade cilium cilium/cilium --version 1.19.6 -n kube-system \
  --reuse-values \
  --set encryption.enabled=true \
  --set encryption.type=wireguard \
  --set k8sServiceHost=192.168.56.108 \
  --set k8sServicePort=6443

Replace the host with your control-plane underlay IP. This lab temporarily bypassed the broken Kubernetes Service path by setting k8sServiceHost and k8sServicePort. That is not required by WireGuard itself. When kube-proxy remains enabled, first inspect kube-proxy, firewall rules, and access to the kubernetes ClusterIP before retaining the direct API endpoint values.

Optional node encryption (not enabled here) adds host traffic to the tunnel:

text
encryption.nodeEncryption=true

Cilium opts control-plane host traffic out of node-to-node encryption by default so the API server remains reachable during bootstrap; it does not disable ordinary Pod encryption on those nodes. Enable node encryption only when you understand that scope change.


Roll out Cilium safely

Watch the DaemonSet while agents restart:

bash
kubectl rollout status daemonset/cilium -n kube-system --timeout=300s
output
daemon set "cilium" successfully rolled out

Confirm every agent Pod is Ready:

bash
kubectl get pods -n kube-system -l k8s-app=cilium
output
NAME           READY   STATUS    RESTARTS   AGE
cilium-7x9kp   1/1     Running   1          4m
cilium-zm4fd   1/1     Running   1          4m

Re-test application connectivity before you capture packets:

bash
kubectl exec -n cilium-enc-lab deploy/enc-client -- \
  wget -qO- "http://${SERVER_IP}/" | head -3
output
<!DOCTYPE html>
<html>
<head>

Application connectivity succeeded after the Cilium rollout. If a Pod loses network during rollout, check agent logs and the troubleshooting table before rolling Helm values back.


Verify peers and keys

The official WireGuard validation procedure uses cilium-dbg status inside the agent. Read encryption state from an agent Pod:

bash
kubectl -n kube-system exec ds/cilium -- \
  cilium-dbg status | grep Encryption
output
Encryption: Wireguard [cilium_wg0 (Pubkey: vNTSMqNw7uJHrXvQHWJ5reIwEXa7Q3CffkwdS83aPn0=, Port: 51871, Peers: 1)]

NodeEncryption: Disabled in the full cilium-dbg status output matches the default Pod-only scope. Peers: 1 is correct on a two-node cluster.

Cluster-wide summary with the external Cilium CLI (cilium encryption status is separate from cilium-dbg):

bash
cilium encryption status
output
Encryption: Wireguard (2/2 nodes)

Inspect the kernel WireGuard interface:

bash
wg show cilium_wg0
output
interface: cilium_wg0
  public key: vNTSMqNw7uJHrXvQHWJ5reIwEXa7Q3CffkwdS83aPn0=
  private key: (hidden)
  listening port: 51871

peer: ntGuQPuIRxYiIpBKeLk5HgamG+K4sHyG2z6KoHJgmVI=
  endpoint: 192.168.56.109:51871
  allowed ips: 192.168.56.109/32
  latest handshake: 2 seconds ago
  transfer: 45.12 KiB received, 38.44 KiB sent

A recent latest handshake confirms the tunnel is live. Cilium publishes each node's WireGuard public key on the CiliumNode object, not the core Kubernetes Node:

bash
kubectl get ciliumnode k8s-cp \
  -o go-template='{{ index .metadata.annotations "network.cilium.io/wg-pub-key" }}{{ "\n" }}'
output
vNTSMqNw7uJHrXvQHWJ5reIwEXa7Q3CffkwdS83aPn0=

Treat wg show, cilium-dbg status, and cilium encryption status together when you audit peers.


Capture encrypted traffic

Repeat the same cross-node wget test. Capture both possible outer transports on the underlay—VXLAN (8472/udp) before encryption and WireGuard (51871/udp) after:

bash
tcpdump -ni enp0s8 \
  'host 192.168.56.109 and (udp port 8472 or udp port 51871)' \
  -c 10

Run the Pod IP request from the client while tcpdump runs:

output
06:11:14.285357 IP 192.168.56.109.51871 > 192.168.56.108.51871: UDP, length 128
06:11:15.366864 IP 192.168.56.108.51871 > 192.168.56.109.51871: UDP, length 144
06:11:15.371879 IP 192.168.56.108.51871 > 192.168.56.109.51871: UDP, length 224
06:11:15.376506 IP 192.168.56.109.51871 > 192.168.56.108.51871: UDP, length 752
10 packets captured

Before encryption, the same request appeared on 8472/udp with decoded inner Pod IP and TCP headers inside VXLAN. After encryption, the request appears only on 51871/udp—the encrypted capture no longer exposes the inner Pod IP and TCP headers that tcpdump decoded from the plaintext VXLAN baseline. Application wget still returned nginx HTML, so encryption is transparent to the workload.

To prove HTTP body confidentiality, use tcpdump -A -s0 with a unique response marker in the nginx page—not the short baseline capture shown above.


Test exclusions

Confirm what stays outside the default tunnel so you do not over-promise compliance coverage.

Same-node Pod traffic

Use the existing enc-server nginx Pod on worker01 and create a same-node client also pinned to worker01:

bash
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Pod
metadata:
  name: same-node-client
  namespace: cilium-enc-lab
spec:
  nodeName: worker01
  containers:
  - name: busybox
    image: registry.k8s.io/e2e-test-images/busybox:1.29-4
    command: ["sleep", "3600"]
EOF

Wait for the client before you test:

bash
kubectl wait --for=condition=Ready \
  pod/same-node-client \
  -n cilium-enc-lab \
  --timeout=90s

From the shell on k8s-cp where ${SERVER_IP} is set, start a bounded capture on worker01 over SSH:

Cluster node access in this lab assumes SSH from your workstation.

bash
ssh root@worker01 "timeout 10 tcpdump -ni cilium_wg0 'host ${SERVER_IP}'"

While that capture runs, issue the same-node HTTP request from another terminal on k8s-cp:

bash
kubectl exec -n cilium-enc-lab same-node-client -- \
  wget -qO- "http://${SERVER_IP}/" | head -2
output
<!DOCTYPE html>
<html>

Capture result on cilium_wg0:

output
listening on cilium_wg0, link-type RAW (Raw IP), snapshot length 262144 bytes

0 packets captured
0 packets received by filter
0 packets dropped by kernel

The wget succeeded, but no traffic to ${SERVER_IP} appeared on cilium_wg0. Cilium intentionally skips encryption when the packet destination is on the originating node.

hostNetwork Pod to remote Pod

Pin a hostNetwork client on k8s-cp (with the control-plane toleration) and keep it running so you can capture while you exec the request:

bash
kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
  name: hostnet-curl
  namespace: cilium-enc-lab
spec:
  hostNetwork: true
  nodeName: k8s-cp
  tolerations:
  - key: node-role.kubernetes.io/control-plane
    operator: Exists
    effect: NoSchedule
  containers:
  - name: client
    image: registry.k8s.io/e2e-test-images/busybox:1.29-4
    command: ["sleep", "3600"]
EOF
bash
kubectl wait --for=condition=Ready \
  pod/hostnet-curl \
  -n cilium-enc-lab \
  --timeout=90s

On k8s-cp, capture both possible outer transports on the underlay:

bash
timeout 10 tcpdump -ni enp0s8 \
  'host 192.168.56.109 and (udp port 8472 or udp port 51871)'

During the capture window:

bash
kubectl exec -n cilium-enc-lab hostnet-curl -- \
  wget -qO- "http://${SERVER_IP}/"
output
<!DOCTYPE html>
<html>

The simultaneous capture on k8s-cp showed:

output
listening on enp0s8, link-type EN10MB (Ethernet), snapshot length 262144 bytes
06:22:41.512804 IP 192.168.56.108.42103 > 192.168.56.109.8472: OTV, flags [I] (0x08), overlay 0, instance 10761
IP 192.168.56.108.42103 > 192.168.1.85.80: Flags [S], seq 2849102331, win 64860, options [mss 1410,sackOK,TS val 41223301 ecr 0,nop,wscale 7], length 0
06:22:41.515221 IP 192.168.56.109.43389 > 192.168.56.108.8472: OTV, flags [I] (0x08), overlay 0, instance 20454
IP 192.168.1.85.80 > 192.168.56.108.42103: Flags [S.], seq 918273645, ack 2849102332, win 64308, length 0

The hostNetwork request crossed the underlay on 8472/udp VXLAN with decoded inner Pod IP and TCP headers visible. No matching 51871/udp WireGuard frames carried this flow. Cilium treats a hostNetwork Pod as node traffic; node-to-remote-Pod encryption requires encryption.nodeEncryption=true.

Document your observed scope in runbooks: cross-node Pod traffic encrypted on the underlay (including Pods on control-plane nodes); same-node, hostNetwork, control-plane host, and external paths need separate analysis or encryption.nodeEncryption.


WireGuard vs IPsec in Cilium

Cilium supports both encryption.type=wireguard and encryption.type=ipsec. This lab enabled WireGuard only.

Topic WireGuard IPsec (Cilium)
Key distribution Cilium agents exchange WireGuard public keys IPsec key material via Cilium IPsec model
Kernel needs wireguard module XFRM/IPsec stack
Operational model UDP tunnels on fixed port 51871 between nodes Different encapsulation and policy lifecycle
Performance Measure on your hardware and CNI version Measure separately; do not assume parity

Pick one encryption type per cluster revision, validate throughput and latency with realistic workloads, and document firewall rules for the chosen encapsulation.


Troubleshooting

Symptom Likely cause Fix
cilium encryption status missing or unknown subcommand in agent Command lives in cilium-cli, not the agent Install cilium-cli for cluster-wide checks; use cilium-dbg status inside the agent Pod
Peers: 0 or no recent handshake UDP 51871 blocked between node underlay IPs Open 51871/udp on every Cilium node; verify with wg show
Agent init timeout to 10.96.0.1:443 after enablement Kubernetes Service path unreachable during bootstrap Inspect kube-proxy, firewall rules, and kubernetes ClusterIP access; k8sServiceHost bypasses the Service and is not a standard WireGuard requirement
Connectivity works but capture still shows Pod IPs Wrong interface or same-node traffic Capture enp0s8 (underlay) during cross-node test; compare UDP 8472 before and UDP 51871 after
Partial encryption (1/2 nodes) One agent not Ready or missing module kubectl logs on failing agent; modprobe wireguard on that node
MTU-related hangs after enablement Tunnel plus encryption overhead Reduce Pod MTU or adjust underlay MTU per Cilium docs
Assumed all traffic encrypted Scope misunderstanding or discovery window Re-read scope table; consider encryption strict mode; enable nodeEncryption only if required

Clean up and rollback

Remove the test namespace when you finish:

bash
kubectl delete namespace cilium-enc-lab --wait=true

To roll back the Helm encryption change:

bash
helm history cilium -n kube-system
helm rollback cilium <previous-revision> -n kube-system

kubectl rollout status daemonset/cilium \
  -n kube-system --timeout=300s

After rollback, remove 51871/udp from host firewalls only when WireGuard is no longer enabled anywhere in the cluster.


What's Next


References


Summary

You defined what Cilium WireGuard actually encrypts: cross-node traffic between Cilium-managed Pods on the node underlay—including Pods scheduled on control-plane nodes—not every flow in the cluster, and not necessarily from the first packet during endpoint discovery unless strict mode is enabled. On a Cilium 1.19.6 tunnel cluster you placed test Pods on different nodes, proved HTTP worked over Pod IP and ClusterIP, and captured VXLAN frames that exposed inner Pod addresses before encryption was enabled.

Enabling encryption.enabled=true and encryption.type=wireguard through Helm rolled new agent configuration, brought up cilium_wg0 on UDP 51871, and moved underlay traffic to WireGuard between peers with fresh handshakes. The repeat capture showed encrypted UDP on 51871 instead of readable inner Pod headers on 8472 while applications kept working unchanged.

The exclusion tests matter for production decisions: packet captures showed same-node traffic absent from cilium_wg0 and hostNetwork traffic on VXLAN 8472 instead of WireGuard 51871, while cross-node Pod encryption used the tunnel. Same-node Pods, node and hostNetwork traffic, and control-plane host paths stayed outside default Pod encryption unless you opt into encryption.nodeEncryption. Combine WireGuard with NetworkPolicy and application TLS where policy requires more than underlay confidentiality, and measure performance before enforcing encryption cluster-wide.


Frequently Asked Questions

1. Does Cilium WireGuard encrypt same-node Pod traffic?

No. WireGuard transparent encryption applies to Pod traffic that leaves the node toward another node. Two Pods on the same worker communicate locally and do not traverse the WireGuard tunnel between nodes, though you may still see WireGuard keepalive UDP between nodes on the underlay.

2. What traffic does Cilium WireGuard encrypt by default?

By default, Cilium encrypts cross-node traffic between Cilium-managed Pods, including Pods scheduled on control-plane nodes. Same-node Pod traffic, node and hostNetwork traffic, and external egress remain outside default Pod encryption. Node encryption extends node-related paths, but control-plane host traffic is opted out by default.

3. What UDP port does Cilium WireGuard use?

Cilium exposes its WireGuard tunnel endpoint on UDP port 51871 on every node. Allow UDP 51871 between all Cilium node underlay addresses.

4. How is Cilium WireGuard different from IPsec encryption?

WireGuard uses lightweight public-key tunnels with keys distributed by Cilium agents. IPsec uses a different kernel datapath and key management model in Cilium. Both require kernel support and performance validation on your cluster; this guide enables WireGuard only.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)