Kubernetes Network Policy: How It Works and Best Practices

Learn how a Kubernetes network policy isolates Pods, with default-deny and DNS egress patterns and safe rollout tips.

GrowthX8 min read

Kubernetes Network Policy: How It Works and Best Practices

A Kubernetes network policy restricts which Pods, namespaces, and address ranges can reach a set of Pods, turning a flat cluster network, where any Pod reaches any other Pod, into segments that match how your services talk. The control is native to the Kubernetes application programming interface (API) and enforced by the network plugin you already run. The difficult part is writing rules strict enough to protect workloads without cutting off traffic they depend on.

This guide covers how the isolation model and selectors behave, four copy-ready policy patterns including Domain Name System (DNS) egress, and how to roll out and test policies before they reach production.

What Does a Kubernetes Network Policy Control?

A Kubernetes network policy controls which connections can reach or leave the Pods it selects, at the Internet Protocol (IP) address and port level. Each policy is a namespaced object whose peers are Pods, namespaces, and IP blocks. Pods start open, and with no policy in a namespace, all ingress and egress traffic is allowed. A NetworkPolicy has no effect unless your Container Network Interface (CNI) plugin implements it, and the API server accepts the manifest whether or not anything acts on it.

How Kubernetes Network Policies Work

A NetworkPolicy combines a target selector, a list of peers, and a list of ports. The selector isolates the Pods it picks, and the peers and ports decide which connections still get through.

Pod Isolation and Policy Types

Isolation is per direction and starts as soon as a policy selects a Pod. A Pod becomes isolated for ingress when any policy selects it and lists Ingress in policyTypes, and egress works the same way. Once a Pod is isolated, only connections that some applicable policy allows get through, plus reply traffic. If you omit the policyTypes field, Kubernetes always sets Ingress and adds Egress only when the policy has egress rules, so an egress-only policy needs an explicit Egress value.

Multiple policies combine as a union and never conflict. The allowed set for a Pod is the sum of every policy that selects it, so evaluation order is irrelevant and no rule can deny traffic another policy allows. A connection between two Pods succeeds only if the source's egress and the destination's ingress each allow it.

Selectors and Peers

Rules match peers with three selector types, which are podSelector, namespaceSelector, and ipBlock. A podSelector alone matches Pods in the policy's own namespace. A namespaceSelector alone matches every Pod in the selected namespaces, and both in one entry match specific Pods in specific namespaces. The ipBlock peer takes a Classless Inter-Domain Routing (CIDR) range with optional except entries and should target cluster-external IPs, since Pod IPs change.

The dash in front of each selector decides between AND and OR:

ingress:
- from:
  - namespaceSelector: {matchLabels: {user: alice}}
    podSelector: {matchLabels: {role: client}}   # one entry: AND
- from:
  - namespaceSelector: {matchLabels: {user: alice}}
  - podSelector: {matchLabels: {role: client}}   # two entries: OR

The first rule admits Pods labeled role=client inside namespaces labeled user=alice. The second admits client Pods in the local namespace or any Pod in an alice namespace, a far broader rule. Running kubectl describe networkpolicy shows how Kubernetes parsed the entries.

Ports and Protocols

Each rule's ports list matches Transmission Control Protocol (TCP), User Datagram Protocol (UDP), or Stream Control Transmission Protocol (SCTP), and protocol defaults to TCP when omitted. The endPort field, stable since Kubernetes v1.25, opens a numeric range from port to endPort, and a plugin without endPort support applies only the single port. A deny-all policy is only guaranteed to block TCP, UDP, and SCTP, and other protocols vary by plugin.

Common Network Policy Patterns

Four patterns cover most namespaces, and each fits in a short manifest you adapt by changing labels. They rely on consistent app labels and on the immutable kubernetes.io/metadata.name label that the control plane sets on every namespace. Once applied, the patterns also change what your Kubernetes monitoring stack can reach, which the last pattern addresses.

Deny All Ingress by Default

An empty podSelector selects every Pod in the namespace, and listing only Ingress isolates them for inbound traffic:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
spec:
  podSelector: {}
  policyTypes: [Ingress]

This policy leaves egress untouched. The deny-all egress and deny-both variants swap or extend the policyTypes list, and allow-all versions use an empty rule (- {}).

Allow DNS Egress

A default-deny egress policy also blocks DNS, so every namespace with egress isolation needs a rule to CoreDNS. The standard addon runs CoreDNS behind a kube-dns Service labeled k8s-app: kube-dns in kube-system, on UDP and TCP port 53:

  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}]

Clusters running NodeLocal DNSCache send queries to a node-local listen address. Confirming labels with kubectl get pods -n kube-system --show-labels before you apply the policy avoids a selector that matches nothing.

Allow Traffic from One Namespace

Cross-namespace access pairs both selectors in one entry. This example admits web Pods from the frontend namespace to backend Pods on TCP 8080:

spec:
  podSelector: {matchLabels: {app: backend}}
  ingress:
  - from:
    - namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: frontend}}
      podSelector: {matchLabels: {app: web}}
    ports: [{protocol: TCP, port: 8080}]

With both selectors in a single entry, other Pods in frontend and web Pods elsewhere stay blocked. The immutable namespace label prevents anyone from widening the rule by adding a label to another namespace. The frontend side still needs a matching egress rule if that namespace isolates egress.

Allow the Ingress Controller and Monitoring

Default-deny ingress also blocks your ingress controller and Prometheus scrapes. This rule admits the namespace that runs your ingress controller or Gateway API implementation on any port, plus Prometheus Pods on a named metrics port:

  ingress:
  - from:
    - namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: ingress-system}}
  - from:
    - namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: monitoring}}
      podSelector: {matchLabels: {app.kubernetes.io/name: prometheus}}
    ports: [{port: metrics}]

Prometheus labels vary by install, so match them and the ingress-system namespace to your own deployment. After the Ingress NGINX retirement on March 24, 2026, the same pattern applies to whichever Gateway API implementation replaces it.

How to Roll Out Network Policies Safely

Roll out network policies one namespace at a time, ship the DNS egress rule with every egress deny, and stage each policy before it enforces. Skipping the DNS rule causes a common rollout failure. A team applies the deny-all egress pattern to stop outbound internet connections, Pods lose name resolution because the policy also blocks queries to CoreDNS, and the NetworkPolicy API cannot log the blocked connections.

The quick fix is the allow-all egress pattern, which removes the egress restriction and leaves the namespace without egress control until someone writes a scoped replacement. A durable rollout treats policy as part of the platform contract across namespaces:

  • Default-deny per namespace: Each namespace gets its own deny policy, since the base API has no cluster-wide defaults.
  • DNS first: The DNS egress rule ships in the same commit as any egress deny.
  • Policies as code: Manifests live in Git and deploy through the same review pipeline as the workloads they protect.
  • Stage before enforcing: Calico staged policies and Cilium audit mode both report what a policy would drop before it enforces anything.
  • Regular review: Scheduled reviews, as part of broader Kubernetes security practices, remove rules for retired services, because additive policies only grow.

Coralogix, a cross-stack observability platform, adds the connection visibility the NetworkPolicy API lacks. It ingests CNI flow logs from Hubble and Calico alongside Kubernetes metrics and traces, so a policy that cuts off traffic shows up next to the service that starts failing. Pairing every rollout with Kubernetes monitoring dashboards lets you watch error rates in the namespace as each policy lands.

How to Test and Troubleshoot Network Policies

Test a network policy by probing the target from a throwaway Pod with the labels the rule expects, then check flow logs for the paths you didn't probe. You can start the client and probe the target with curl, wget, or nc:

kubectl run probe -n frontend --rm -it --labels=app=web --image=busybox -- sh
wget -qO- -T 2 http://backend.backend:8080
nc -zv -w 2 backend.backend 8080

Repeating the probe without the label tests the deny path. For whole-namespace reasoning, SIG Network's policy-assistant tool simulates verdicts from files or a live cluster, and the older Cyclonus project now points active verification work there.

When nothing gets blocked, the first suspect is enforcement, since an unenforced policy fails silently. Some traffic also sits outside policy entirely. Traffic from a Pod's own node is always allowed, which keeps kubelet probes working. Behavior for hostNetwork Pods is undefined, and the most common implementation treats their traffic like any other traffic to and from the node IP.

Probes only confirm the paths you thought to test. Cilium's Hubble exporter writes flows with verdicts such as DROPPED to a file, and Calico's flow logs API, a tech preview, tags flows as allow or deny. Hubble's file output can be tailed like any other log by Fluent Bit or the OpenTelemetry Collector and shipped into your log management pipeline.

Limits of Kubernetes Network Policies

Production clusters usually need controls beyond the base NetworkPolicy API.

What the NetworkPolicy API Cannot Do

The Kubernetes documentation lists what the NetworkPolicy API cannot do as of Kubernetes 1.37, including these limits:

  • No logging of blocked or accepted connections
  • No explicit deny rules, only allow rules on top of isolation
  • No default policies applied to all namespaces or Pods
  • No Layer 7 or TLS-related controls
  • No targeting of Services by name
  • No way to block localhost or traffic from the Pod's own node

CNI extensions, a cluster-scoped policy API, and service meshes each cover part of this list.

CNI Extensions, AdminNetworkPolicy, and Service Meshes

Enforcement and extensions differ by plugin:

PluginEnforces NetworkPolicyExtensions beyond the base API
CalicoYesPolicy ordering, deny rules, richer match rules
CiliumYesCiliumNetworkPolicy and CiliumClusterwideNetworkPolicy
AntreaYesTiers, Drop and Reject actions, rule logging
FlannelNot nativelykube-network-policies controller or Calico

The upstream path to cluster-wide policy is ClusterNetworkPolicy from the SIG Network network-policy-api project. Release v0.2.0 merged AdminNetworkPolicy and BaselineAdminNetworkPolicy into one resource with an Admin or Baseline tier, and v0.3.0 graduated it to v1beta1 without changing the API shape. Cilium 1.20 supports the upstream cluster-scoped API. The feature is opt-in and stays off until you set --enable-k8s-cluster-network-policy and install the CRD from network-policy-api.

Managed services also differ on defaults. Amazon Elastic Kubernetes Service (EKS) leaves Amazon Virtual Private Cloud (VPC) CNI policy enforcement off by default, and Google Kubernetes Engine (GKE) Dataplane V2 enforces it in clusters that use it. Azure Kubernetes Service (AKS) defaults to no network policy engine.

Service meshes, which route Pod traffic through proxies, add identity and Layer 7 (application-level) controls. Istio's security guidance layers its policies with NetworkPolicy for defense in depth, adding mutual Transport Layer Security (mTLS) and AuthorizationPolicy rules. Linkerd enables automatic mTLS for TCP between meshed Pods.

NetworkPolicy and mesh layers allow or block traffic without alerting. A permitted connection to an unexpected egress destination passes through both without raising an alert. Coralogix Cloud SIEM scans ingested flow logs against detection rules in-stream, so that connection can raise an alert for your security team to triage.

Tight Isolation Without Breaking Working Traffic

Every network policy trades isolation against availability. Stricter rules limit what a compromised Pod can reach, and each new rule also risks cutting a connection a service depends on. The NetworkPolicy API doesn't log what a policy drops, and a broken path often surfaces first as a failing service.

Coralogix keeps those drop verdicts beside the services they affect, which puts a policy change and the failure it caused side by side. To trace each blocked connection in your cluster to the workload it broke, explore the Coralogix platform. You get flow verdicts, metrics, and traces for every namespace in one place.

Frequently Asked Questions About Kubernetes Network Policy

Can a Kubernetes network policy deny traffic?

Only indirectly, because selecting a Pod isolates it, and everything not explicitly allowed is then dropped. Explicit deny rules require CNI extensions such as Calico, Cilium, or Antrea policies.

What is the difference between a network policy and a security group?

A network policy selects Pods by label and is enforced by the CNI inside the cluster. A security group filters traffic at the cloud instance or network interface level. On EKS, Pod security groups control access to AWS services, and network policies handle in-cluster traffic.

Do network policies apply to containers in the same Pod?

No, containers in one Pod share a network namespace and talk over localhost. The API can't block that localhost traffic.

Coralogix

One lake.
Every signal.

Logs, metrics, traces and security data in one queryable place, ready for your agents and your people.