What "blocked by NetworkPolicy" means
A connection is being dropped because a NetworkPolicy selects one of the pods
involved and does not include a rule that allows that traffic. The moment any
policy selects a pod for a given direction, that pod flips from "allow all" to
"deny unless explicitly allowed" for that direction: "By default, a pod is
non-isolated for ingress; all inbound connections are allowed. A pod is isolated
for ingress if there is any NetworkPolicy that both selects the pod and has
'Ingress' in its policyTypes" (Network Policies).
The same rule holds for egress. The classic symptom is a hang, not a fast
failure: in the official walkthrough, a blocked wget prints wget: download timed out rather than a connection refusal (Declare Network Policy).
Both ends must agree. "For a connection from a source pod to a destination pod to be allowed, both the egress policy on the source pod and the ingress policy on the destination pod need to allow the connection" (Network Policies). So a single missing rule on either side is enough to break the flow.
Confirm it is actually a policy problem first
A timeout has more than one cause. Before chasing policies, separate the three common shapes:
- Times out, no reset — packets are being dropped. NetworkPolicy is a strong candidate. Also check node firewalls and security groups.
- Connection refused (TCP reset) — something answered and rejected, or nothing is listening. That is usually a Service with no ready endpoints, not a policy. See the related page on a Service with no endpoints.
- Name resolution error — DNS failed before any connection was attempted. Often a blocked-DNS-egress policy (covered below) or a separate DNS problem.
List the policies that select the pod on each side:
# Policies in the namespace, and which pods each one selects
kubectl get networkpolicy -n <namespace>
kubectl describe networkpolicy <name> -n <namespace>
# The labels on the pod that determine whether a policy selects it
kubectl get pod <pod> -n <namespace> --show-labels
If no policy selects either pod, the pod is non-isolated and policy is not your cause. If one does, read its rules against the checklist below.
The distinct causes, end to end
1. Default-deny flips on as soon as any policy selects the pod
This is the cause people trip over first. A policy does not have to say "deny" to
block traffic. A default-deny policy is simply one that selects pods and lists
no allow rules. The canonical deny-all manifest selects every pod in the namespace
and isolates both directions (Network Policies):
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
An empty podSelector: {} "selects all pods in the namespace"
(Network Policies).
With no ingress or egress list, nothing is allowed in either direction.
-
What it is: isolation turned on for a pod with no compensating allow rule.
-
Diagnose:
kubectl describe networkpolicyshows a policy whosePodSelectormatches your pod and whose Allowing Ingress/Egress rules are empty. Test a baseline with a throwaway pod, mirroring the official task:kubectl run busybox --rm -ti --image=busybox -- /bin/sh wget --spider --timeout=1 <target>A
download timed outagainst a target that should work is the tell (Declare Network Policy). -
Fix: add a narrowly scoped allow policy for the specific traffic the pod needs (sections below), rather than removing the deny-all. Default-deny plus explicit allows is the intended, auditable pattern.
2. Ingress is isolated but no from rule matches the client
When a policy lists policyTypes: [Ingress], "the only allowed connections into
the pod are those from the pod's node and those allowed by the ingress list"
(Network Policies).
If your client does not match any from element, it is dropped. The official
example allows only pods carrying access: "true"
(Declare Network Policy):
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: access-nginx
spec:
podSelector:
matchLabels:
app: nginx
ingress:
- from:
- podSelector:
matchLabels:
access: "true"
- Diagnose: read the
ingress.fromselectors, then check the client pod's labels and namespace against them. In the example above, a busybox without the label times out; the same busybox started with--labels="access=true"succeeds (Declare Network Policy). - Fix: add the missing label to the client, or add a
fromelement that selects it. Confirm by re-running thewget --spidertest.
3. Egress is isolated but no to rule matches the destination
Symmetric to ingress. With policyTypes: [Egress] and no matching to, the pod
cannot open the connection in the first place. Note the default: "If no
policyTypes are specified on a NetworkPolicy then by default Ingress will
always be set and Egress will be set if the NetworkPolicy has any egress rules"
(Network Policies).
So adding an egress block to a policy you thought was ingress-only silently
turns on egress isolation for every pod it selects.
- Diagnose: check whether
Egressappears inpolicyTypes(explicitly or implicitly). If it does, every outbound destination must be covered by atorule, including in-cluster Services, the API server, and external endpoints. - Fix: add
torules for each destination class the pod legitimately needs. Be specific withportsso you are not opening more than required.
4. A default-deny-egress policy silently breaks DNS
The highest-frequency surprise. The day you add any egress isolation, DNS stops
working unless you explicitly allow it. CoreDNS runs as a Service named kube-dns
in the kube-system namespace, listening on 53/UDP and 53/TCP, with the label
k8s-app=kube-dns for both CoreDNS and kube-dns deployments
(Debugging DNS Resolution).
A deny-all-egress policy drops port 53 to that Service, so name resolution fails
before any connection is attempted — which often looks like the destination is
down, not DNS.
-
What it is: egress isolation that forgot to carve out port 53 to CoreDNS.
-
Diagnose: from inside an affected pod, a connection by IP works but by name fails. Verify CoreDNS is actually healthy and reachable in principle:
kubectl get pods -n kube-system -l k8s-app=kube-dns kubectl get svc -n kube-system kube-dnsThose commands and the
kube-dns/k8s-app=kube-dnsidentifiers are from the official DNS debugging task (Debugging DNS Resolution). If the Service is healthy but in-pod name lookups still fail under an egress policy, the policy is the cause. -
Fix: add an egress allow for UDP and TCP port 53 to the CoreDNS pods. The shape below combines a
namespaceSelectorandpodSelectorin a singletoelement (AND logic — see cause 5) so it matches CoreDNS inkube-system. Label yourkube-systemnamespace, or select it by its built-inkubernetes.io/metadata.namelabel, then validate against your cluster:apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-dns-egress namespace: <your-namespace> spec: podSelector: {} policyTypes: - Egress egress: - to: - namespaceSelector: matchLabels: kubernetes.io/metadata.name: kube-system podSelector: matchLabels: k8s-app: kube-dns ports: - protocol: UDP port: 53 - protocol: TCP port: 53The
k8s-app: kube-dnslabel is documented as present on both CoreDNS and kube-dns (Debugging DNS Resolution). Confirm the namespace label exists in your cluster before relying on it; treat this manifest as a template to validate, not a guaranteed match.
5. namespaceSelector vs podSelector: the AND/OR trap
This decides whether your allow rule is wider or narrower than you think, and it
is a frequent silent block. Two list items under from/to are OR; one item with
two fields is AND. The official docs contrast them directly. One item with both
fields selects "Pods with the label role=client AND in namespaces with the
label user=alice"; two separate items allow "Pods in the local Namespace with
the label role=client OR ... from any Pod in a Namespace with the label
user=alice" (Network Policies).
- The trap: dropping the leading
-(sonamespaceSelectorandpodSelectorshare one list element) turns a broad OR into a strict AND. A rule you believed allowed "all pods in namespace X, plus pods labelled Y here" instead allows only pods that are labelled Y and live in X — and everything else times out. - Diagnose: in
kubectl describe networkpolicy, look closely at the list structure under eachFrom/To. Map every selector to the actual labels on the client pod and its namespace. RememberpodSelectorwith nonamespaceSelectormeans the policy's own namespace only. - Fix: restructure the YAML so the list items reflect the intended AND/OR.
When in doubt, split into separate
-items for OR.
6. The CNI does not implement NetworkPolicy (policies silently ignored)
The inverse failure: you wrote a correct deny policy and traffic still flows, or you cannot understand why a rule is or is not taking effect — because nothing is enforcing it. "Network policies are implemented by the network plugin ... Creating a NetworkPolicy resource without a controller that implements it will have no effect" (Network Policies). The API server accepts the object regardless, so there is no error to alert you.
-
What it is: a CNI without NetworkPolicy support (for example, stock Flannel). Plugins that do enforce it include Antrea, Calico, Cilium, Kube-router, and Weave Net (Declare Network Policy).
-
Diagnose: identify the CNI in use and confirm it supports NetworkPolicy.
kubectl get pods -n kube-system -o wide kubectl get daemonset -n kube-systemLook for the plugin's DaemonSet (calico-node, cilium, antrea-agent, etc.). If you only see a plain CNI with no policy controller, your policies are inert.
-
Fix: deploy or enable a NetworkPolicy-capable plugin per its docs. This is a cluster-level change with real blast radius — installing or swapping a CNI affects every pod's networking. Plan it as a maintenance operation, ideally on a test cluster first, not as a quick patch on a live namespace.
7. Port or protocol mismatch in an otherwise-correct rule
Even when the selectors match, traffic is dropped if the ports block does not
list the port and protocol you are actually using. The example policy allows TCP
6379 ingress and TCP 5978 egress — anything else to those pods is denied
(Network Policies).
Two specific footguns:
-
Protocol mismatch: allowing
TCP 53but notUDP 53breaks most DNS, since resolvers try UDP first. Allow both, as in cause 4. -
Port ranges need CNI support: the
endPortfield targets a range, but "your cluster must be using a CNI plugin that supports theendPortfield"; without it, "the policy will be applied only for the singleportfield" (Network Policies). So a range you think is open may only be open on its first port. -
Diagnose: compare the
portsentries in the policy against the real destination port and protocol of the failing connection. -
Fix: add the missing protocol/port. For ranges, confirm your CNI honors
endPortbefore relying on it.
Recommended approach and tradeoffs
Adopt default-deny per namespace, then add the narrowest explicit allows —
including DNS — rather than running open or writing broad allow-alls. The tradeoff
is real: default-deny is the strongest blast-radius control (a compromised pod
cannot freely reach the rest of the cluster), but it shifts cost onto operations.
Every new dependency now needs a policy change, and the DNS and intra-namespace
allows are easy to forget, producing exactly the timeouts above. Teams that skip
DNS allows or misread the AND/OR semantics spend more time debugging policy than
the policy saves. Stage changes: apply in a non-production namespace, run the
wget --spider --timeout=1 connectivity checks for each required flow, then
promote.
Validation steps
- Confirm which policies select each pod:
kubectl describe networkpolicy ...andkubectl get pod --show-labels. - Reproduce with a throwaway client and an explicit short timeout:
kubectl run busybox --rm -ti --image=busybox -- /bin/shthenwget --spider --timeout=1 <target>— a clean timeout points at a drop (Declare Network Policy). - Test DNS separately by IP versus name; if IP works and name fails under an egress policy, fix the DNS allow.
- Verify the CNI actually enforces policy (cause 6) before trusting any result.
- After each policy edit, re-run the same connectivity check for every required flow, not just the one you were debugging.
How Intellira diagnoses this
Intellira is a read-only RCA assistant: it correlates the signals you would
otherwise gather by hand. For a NetworkPolicy block it can line up the policies
selecting each pod, the pods' labels and namespaces, CoreDNS Service health, and
the CNI in use, so the AND/OR misread or the missing DNS egress surfaces in one
place instead of across several kubectl describe runs. It does not change cluster
state — it will not apply, edit, or delete policies. The remediation manifests and
the CNI decision in cause 6 remain a human change, reviewed and applied by you.
Sources
- Kubernetes Documentation — Network Policies
- Kubernetes Documentation — Declare Network Policy (task)
- Kubernetes Documentation — Debugging DNS Resolution
By Intellira Engineering. AI-assisted draft, reviewed by the Intellira engineering team; claims cited inline; last verified 2026-06-02.