| Tested on | Rocky Linux 10.2 (Red Quartz) kubeadm nodes and administration host |
|---|---|
| Package | kubectl 1.36.3helm 3.21.3cilium 1.19.6cilium-cli 0.19.6wireguard-toolstcpdump |
| 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.
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:
cilium status --waitUse cilium-dbg inside an agent Pod for the detailed KVStore, Routing, KubeProxyReplacement, and Encryption lines:
kubectl -n kube-system exec ds/cilium -- \
cilium-dbg statusKVStore: 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:
kubectl get ciliumnodesNAME AGE
k8s-cp 42m
worker01 42mCheck agent Pods in kube-system:
kubectl get pods -n kube-system -l k8s-app=cilium -o wideNAME 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 worker01Record 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:
modprobe wireguard
lsmod | grep wireguardwireguard 122880 0
curve25519_x86_64 36864 1 wireguard
libchacha20poly1305 16384 1 wireguardConfirm the running kernel:
uname -r6.12.0-211.34.1.el10_2.x86_64Firewall 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
enp0s8at192.168.56.0/24), not only inside the Pod network namespace.
Open the port on every Cilium node before the Helm upgrade:
firewall-cmd --permanent --add-port=51871/udp
firewall-cmd --reload
firewall-cmd --list-portsInstall inspection tools on nodes where you capture packets:
dnf install -y wireguard-tools tcpdumpOn 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:
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
EOFSchedule the client on k8s-cp. nodeAffinity alone does not override the control-plane NoSchedule taint, so add a toleration:
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"]
EOFExpose the server with a ClusterIP Service:
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
EOFWait for Ready Pods and note placement:
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 wideNAME 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 worker01Record the server Pod IP dynamically—it changes whenever the Pod is recreated:
SERVER_IP=$(
kubectl get pod -n cilium-enc-lab \
-l app=enc-server \
-o jsonpath='{.items[0].status.podIP}'
)
echo "$SERVER_IP"192.168.1.169Use ${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:
kubectl exec -n cilium-enc-lab deploy/enc-client -- \
wget -qO- "http://${SERVER_IP}/"<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>ClusterIP request through the Service:
kubectl exec -n cilium-enc-lab deploy/enc-client -- wget -qO- http://enc-server.cilium-enc-lab.svc.cluster.local/<!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):
tcpdump -ni enp0s8 host 192.168.56.109 and port 8472 -c 6In another session, repeat the direct Pod IP wget from the client.
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 capturedThe 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:
helm version --short
helm list -n kube-system
helm status cilium -n kube-systemv3.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.6Preserve the active values so you can roll back:
helm get values cilium -n kube-system -o yaml \
> /root/cilium-values-before-wireguard.yaml
helm history cilium -n kube-systemThese 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:
helm upgrade cilium cilium/cilium --version 1.19.6 -n kube-system \
--reuse-values \
--set encryption.enabled=true \
--set encryption.type=wireguardHelm 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:
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=6443Replace 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:
encryption.nodeEncryption=trueCilium 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:
kubectl rollout status daemonset/cilium -n kube-system --timeout=300sdaemon set "cilium" successfully rolled outConfirm every agent Pod is Ready:
kubectl get pods -n kube-system -l k8s-app=ciliumNAME READY STATUS RESTARTS AGE
cilium-7x9kp 1/1 Running 1 4m
cilium-zm4fd 1/1 Running 1 4mRe-test application connectivity before you capture packets:
kubectl exec -n cilium-enc-lab deploy/enc-client -- \
wget -qO- "http://${SERVER_IP}/" | head -3<!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:
kubectl -n kube-system exec ds/cilium -- \
cilium-dbg status | grep EncryptionEncryption: 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):
cilium encryption statusEncryption: Wireguard (2/2 nodes)Inspect the kernel WireGuard interface:
wg show cilium_wg0interface: 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 sentA 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:
kubectl get ciliumnode k8s-cp \
-o go-template='{{ index .metadata.annotations "network.cilium.io/wg-pub-key" }}{{ "\n" }}'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:
tcpdump -ni enp0s8 \
'host 192.168.56.109 and (udp port 8472 or udp port 51871)' \
-c 10Run the Pod IP request from the client while tcpdump runs:
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 capturedBefore 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:
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"]
EOFWait for the client before you test:
kubectl wait --for=condition=Ready \
pod/same-node-client \
-n cilium-enc-lab \
--timeout=90sFrom 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.
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:
kubectl exec -n cilium-enc-lab same-node-client -- \
wget -qO- "http://${SERVER_IP}/" | head -2<!DOCTYPE html>
<html>Capture result on cilium_wg0:
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 kernelThe 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:
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"]
EOFkubectl wait --for=condition=Ready \
pod/hostnet-curl \
-n cilium-enc-lab \
--timeout=90sOn k8s-cp, capture both possible outer transports on the underlay:
timeout 10 tcpdump -ni enp0s8 \
'host 192.168.56.109 and (udp port 8472 or udp port 51871)'During the capture window:
kubectl exec -n cilium-enc-lab hostnet-curl -- \
wget -qO- "http://${SERVER_IP}/"<!DOCTYPE html>
<html>The simultaneous capture on k8s-cp showed:
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 0The 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:
kubectl delete namespace cilium-enc-lab --wait=trueTo roll back the Helm encryption change:
helm history cilium -n kube-system
helm rollback cilium <previous-revision> -n kube-system
kubectl rollout status daemonset/cilium \
-n kube-system --timeout=300sAfter rollback, remove 51871/udp from host firewalls only when WireGuard is no longer enabled anywhere in the cluster.
What's Next
- Enable Istio Mutual TLS with PeerAuthentication
- Build Secure Minimal Container Images
- Scan Container Images for Vulnerabilities with Trivy
References
- Cilium WireGuard transparent encryption — enablement and
cilium-dbg statusvalidation inside agent Pods - Cilium Helm values (
encryption.*) - Cilium system requirements — kernel, firewall, and port prerequisites
- Cilium Kubernetes compatibility — supported Kubernetes version matrix
- Kubernetes Services
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.

