Skip to content
Intellira
Kubernetesmedium severity

NetworkPolicyBlocked

Connections time out or are refused because a NetworkPolicy denies them. How to confirm isolation, find the missing allow rule, and unblock DNS.

Written by Intellira Engineering, Editorial team

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 networkpolicy shows a policy whose PodSelector matches 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 out against 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.from selectors, 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 from element that selects it. Confirm by re-running the wget --spider test.

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 Egress appears in policyTypes (explicitly or implicitly). If it does, every outbound destination must be covered by a to rule, including in-cluster Services, the API server, and external endpoints.
  • Fix: add to rules for each destination class the pod legitimately needs. Be specific with ports so 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-dns
    

    Those commands and the kube-dns / k8s-app=kube-dns identifiers 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 namespaceSelector and podSelector in a single to element (AND logic — see cause 5) so it matches CoreDNS in kube-system. Label your kube-system namespace, or select it by its built-in kubernetes.io/metadata.name label, 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: 53
    

    The k8s-app: kube-dns label 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 - (so namespaceSelector and podSelector share 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 each From/To. Map every selector to the actual labels on the client pod and its namespace. Remember podSelector with no namespaceSelector means 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-system
    

    Look 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 53 but not UDP 53 breaks most DNS, since resolvers try UDP first. Allow both, as in cause 4.

  • Port ranges need CNI support: the endPort field targets a range, but "your cluster must be using a CNI plugin that supports the endPort field"; without it, "the policy will be applied only for the single port field" (Network Policies). So a range you think is open may only be open on its first port.

  • Diagnose: compare the ports entries 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 endPort before relying on it.

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

  1. Confirm which policies select each pod: kubectl describe networkpolicy ... and kubectl get pod --show-labels.
  2. Reproduce with a throwaway client and an explicit short timeout: kubectl run busybox --rm -ti --image=busybox -- /bin/sh then wget --spider --timeout=1 <target> — a clean timeout points at a drop (Declare Network Policy).
  3. Test DNS separately by IP versus name; if IP works and name fails under an egress policy, fix the DNS allow.
  4. Verify the CNI actually enforces policy (cause 6) before trusting any result.
  5. 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

By Intellira Engineering. AI-assisted draft, reviewed by the Intellira engineering team; claims cited inline; last verified 2026-06-02.

Frequently asked questions

Why does my connection time out instead of getting "connection refused"?
A NetworkPolicy drops packets silently, so the client waits and times out. A refused connection (TCP reset) usually means nothing is listening on that port, which is a Service or endpoint problem, not a policy problem.
I created a NetworkPolicy but nothing changed. Why?
NetworkPolicy is enforced by the CNI network plugin. If your plugin does not implement it (for example, stock Flannel), the API server accepts the object but no controller acts on it, so it has no effect.
Do I need a rule for DNS?
Yes, if you apply any egress policy to a pod. A default-deny-egress policy also blocks UDP/TCP port 53 to CoreDNS, which breaks every name lookup. Add an explicit egress allow for DNS.

Related errors

Find the root cause of NetworkPolicyBlocked on your stack

Connect read-only and Intellira correlates the change behind it across Bitbucket, Jenkins, ArgoCD and Kubernetes — with the evidence to prove it.