Cilium policy verdicts are the ultimate arbiter of whether a packet is allowed or denied, and understanding how to read these logs is key to debugging why your network traffic is getting dropped.
Let’s say you’re seeing packets dropped between pod-a in namespace-a and pod-b in namespace-b, and you suspect a Cilium policy is the culprit. Your first step is to find the Cilium agent logs on the node where pod-a is running.
The core of the problem lies in the fact that Cilium intercepts all network traffic and applies Network Policies (and other policy types) to decide what’s allowed. When a packet doesn’t match any allow rule and doesn’t fall under a deny rule either (which is less common for explicit drops), it can be implicitly denied if no policy explicitly permits it. The policy verdict logs tell you exactly which policy (or lack thereof) caused the drop.
Here’s how to dive into the logs:
First, identify the Cilium agent pod on the node where pod-a resides. You can usually find this by running kubectl get pods -n kube-system | grep cilium. Let’s assume it’s named cilium-abcdef.
Next, you’ll want to access the logs of this agent pod. The most effective way to get granular policy information is to increase the log level temporarily.
kubectl logs -n kube-system cilium-abcdef -f --level debug
Now, trigger the traffic from pod-a to pod-b again. You’ll be looking for log entries that mention "Policy Verdict" or "drop." The output will be verbose, so filtering is your friend. Try filtering for the IP address of pod-b or the destination port.
kubectl logs -n kube-system cilium-abcdef -f --level debug | grep "Policy Verdict"
Or, to be more specific to a potential drop:
kubectl logs -n kube-system cilium-abcdef -f --level debug | grep "drop"
When you see a relevant log line, it might look something like this:
... level=debug msg="Policy Verdict" src-pod="pod-a(namespace-a)" dst-pod="pod-b(namespace-b)" proto=TCP sport=54321 dport=80 verdict=DENY rule="<none>" reason="no matching policy"
This is the smoking gun. It tells you:
src-pod: The origin of the packet.dst-pod: The intended destination.proto: The protocol (e.g., TCP, UDP).sport,dport: Source and destination ports.verdict: Crucially,DENY.reason:no matching policy. This is the key. It means noallowrule in any applicable Cilium Network Policy (or other policy like K8s NetworkPolicy, CiliumNetworkPolicy, etc.) explicitly permitted this traffic.
Common Causes and Fixes:
-
Implicit Deny (Most Common):
- Diagnosis: The log shows
verdict=DENYandreason="no matching policy". This is the default behavior in Kubernetes networking and Cilium: if no policy explicitly allows traffic, it’s dropped. - Fix: Apply a Cilium Network Policy (CNP) or Kubernetes NetworkPolicy that explicitly allows the desired traffic. For example, to allow
pod-ato reachpod-bon TCP port 80:
This policy, applied inapiVersion: cilium.io/v2 kind: CiliumNetworkPolicy metadata: name: allow-pod-a-to-pod-b namespace: namespace-a # Or the namespace where pod-a resides spec: endpointSelector: matchLabels: io.kubernetes.pod.name: pod-a ingress: - fromEndpoints: - matchLabels: io.kubernetes.pod.name: pod-b io.kubernetes.pod.namespace: namespace-b toPorts: - ports: - protocol: TCP port: "80"namespace-a(or whereverpod-ais), allowspod-ato initiate connections topod-bon TCP port 80. - Why it works: Cilium evaluates policies based on selectors. This CNP selects
pod-aand defines aningressrule allowing traffic from specific endpoints (pod-b) to specific ports (80). Without this explicitallow, the traffic is implicitly denied by default.
- Diagnosis: The log shows
-
Incorrect
toPortsorprotocolin Policy:- Diagnosis: Logs show
verdict=DENYbut thereasonmight be more specific, or you see the correct policy matched but the port/protocol doesn’t align. The traffic might be going to TCP port 8080, but your policy only allows TCP port 80. - Fix: Review your CNP or NetworkPolicy and ensure the
toPortsandprotocolfields accurately reflect the traffic being sent. For the example above, ifpod-blistens on TCP 8080, change"port": "80"to"port": "8080". - Why it works: Policies are exact matches. If the packet’s destination port or protocol doesn’t match any of the
toPortsorprotocolentries in anallowrule, that rule won’t permit the packet, leading to a potential deny.
- Diagnosis: Logs show
-
Incorrect
fromEndpointsorfromCIDRin Policy:- Diagnosis: Similar to above, but the source is the issue.
pod-ais trying to connect, but the policy onpod-b(if applied to the destination) or a cluster-wide policy doesn’t listpod-a(or its namespace/labels) in aningressrule’sfromEndpointsorfromCIDRsection. - Fix: Adjust the
fromEndpointsorfromCIDRin the relevant policy. Ifpod-ahas the labelapp=frontend, and the policy onpod-bneeds to allow it:# Policy on namespace-b, selecting pod-b spec: endpointSelector: matchLabels: io.kubernetes.pod.name: pod-b ingress: - fromEndpoints: - matchLabels: app: frontend # This label must match pod-a io.kubernetes.pod.namespace: namespace-a toPorts: - ports: - protocol: TCP port: "80" - Why it works: Policies define who is allowed to initiate connections. If the source endpoint (identified by labels or CIDR) isn’t listed in an
allowrule’sfromEndpointsorfromCIDRsection, the traffic is not permitted by that rule.
- Diagnosis: Similar to above, but the source is the issue.
-
Policy Applied to the Wrong Namespace or Endpoint:
- Diagnosis: You’ve written a policy, but it’s not being applied where you expect. For instance, a CNP with
namespace: namespace-amight not be selectingpod-aifpod-adoesn’t have the labels specified inspec.endpointSelector. Or, you might be applying a policy to allow traffic topod-b, but the policy is in the wrong namespace. - Fix: Double-check the
metadata.namespaceandspec.endpointSelectorin your CNP, and themetadata.namespaceandspec.podSelectorin your K8s NetworkPolicy. Ensure they correctly target the endpoint(s) you intend to control. - Why it works: Cilium’s policy enforcement is namespace-aware and endpoint-selector driven. If the policy object isn’t in the same namespace as the endpoint it’s supposed to govern (unless using cluster-wide policies), or if the
endpointSelectordoesn’t match the labels on the pod, the policy simply won’t be applied to that pod.
- Diagnosis: You’ve written a policy, but it’s not being applied where you expect. For instance, a CNP with
-
Order of Operations with Multiple Policies:
- Diagnosis: You might have both Kubernetes
NetworkPolicyand CiliumCiliumNetworkPolicyobjects in play. Cilium respects K8s NetworkPolicies, but CNPs can offer more granular control. If a K8sNetworkPolicydenies traffic, a subsequent CNP allowing it might not be reached if the initial deny is final. Conversely, if a K8sNetworkPolicyallows traffic, but a CNP denies it, the CNP’s deny will take precedence. - Fix: Understand the hierarchy. Kubernetes NetworkPolicies are evaluated first. If they deny, the packet is dropped. If they allow, Cilium’s CNPs are then evaluated. A
DENYverdict from a CNP will drop the packet. Aim for clarity: use one type of policy for a given traffic flow, or ensure your combined policies don’t create conflicting rules. - Why it works: Policy evaluation follows a specific order. For Kubernetes NetworkPolicies, if any
NetworkPolicyselects the pod and has aningressrule that doesn’t match, the packet is denied. If it does match, it’s allowed. Then, Cilium’s own policies are applied. Adenyfrom Cilium will override an earlierallowfrom K8s.
- Diagnosis: You might have both Kubernetes
-
Service Type and Policy Enforcement:
- Diagnosis: Sometimes, traffic might be dropped before it even reaches the pod’s IP if it’s being routed through a Kubernetes Service. Policies are applied at the pod IP level. If a Service is routing traffic to
pod-bon a specific port, and the policy onpod-bis too restrictive, the packet will be dropped after leaving the Service but before enteringpod-b. - Fix: Ensure your policies are aligned with how Services are directing traffic. If a Service is sending traffic to
pod-bon TCP port 80, the policy onpod-bmust explicitly allow TCP port 80. - Why it works: Services act as an intermediary. The packet arrives at
pod-b’s IP address and port, but it’s the Cilium agent on that pod’s node that intercepts and applies policies. The Service’s existence doesn’t bypass Cilium’s policy enforcement for the destination pod.
- Diagnosis: Sometimes, traffic might be dropped before it even reaches the pod’s IP if it’s being routed through a Kubernetes Service. Policies are applied at the pod IP level. If a Service is routing traffic to
After applying a fix, remember to reduce the log level back to info or default on the Cilium agent pod to avoid excessive log generation and potential performance impact:
kubectl logs -n kube-system cilium-abcdef --level info
The next error you’ll hit is likely a "connection refused" or "timeout" if the policy is now allowing traffic but the application on pod-b isn’t listening on the expected port, or if DNS resolution is failing.