8 min read

Kubernetes Network Policies Explained with Examples

Learn how Kubernetes NetworkPolicy objects work, why default deny matters, how ingress and egress selectors combine, and which CNI plugins actually enforce them.

ByAndré Ribeiro· Founder, Obelinf
Kubernetes Network Policies Explained with Examples
Kubernetes Network Policies Explained with Examples · October 7, 2026
On this page

A Kubernetes cluster is open by default. Every Pod can reach every other Pod unless something stops it, and the flat network that makes services easy to deploy also makes lateral movement easy. NetworkPolicy is the native object that lets you draw boundaries inside that flat network without leaving Kubernetes.

The confusion is usually not about syntax. It is about how policies combine, why a policy that looks correct has no effect, and which plugin is responsible for enforcing it. This guide covers the model, shows working examples, and points out the failure modes that make teams think NetworkPolicy is broken when it is doing exactly what the cluster was told.

What a NetworkPolicy Can and Cannot Do

A NetworkPolicy is a namespaced object. It selects a group of Pods with podSelector and declares allowed traffic in one or both directions using policyTypes. Ingress rules describe traffic arriving at the selected Pods, and egress rules describe traffic leaving them. Anything not explicitly allowed in a direction that is governed is denied.

The object is deliberately narrow. It works at Layer 3 and Layer 4, matching Pods, namespaces, IP blocks, ports, and protocols. It does not inspect HTTP paths or methods, it does not encrypt traffic, and it does not replace a service mesh for application layer controls. It also does not apply to traffic that uses the host network, which is why some system components appear to ignore policy.

The most important property is that policies are additive. There is no priority, no ordering, and no deny rule. If any policy that selects a Pod allows a flow, the flow is allowed, even if another policy is narrower. You build restrictions by starting from deny and adding allowances, not by writing exceptions to a broad allow.

Default Deny Is the Baseline

Kubernetes ships with no policies at all, which means all Pod to Pod traffic is allowed. The first useful policy in any namespace selects every Pod and permits nothing. This is the default deny posture, and it turns the cluster from open to closed so that every later policy is a deliberate, reviewable allowance.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: payments
spec:
  podSelector: {}
  policyTypes:
    - Ingress
    - Egress

An empty podSelector matches every Pod in the namespace. Because policyTypes lists both directions and the spec contains no ingress or egress rules, every connection is blocked. Existing connections are unaffected, but new connections are denied immediately, which is why you should stage default deny with the allowances ready.

Before and after default deny, showing an open flat network versus only explicitly allowed flowsDefault deny closes the flat network before you reopen it on purposeNo policy: every Pod reaches every PodfrontendapidatabaseLateral movement is unrestricted.Default deny: only allowed flows passfrontendapidatabaseThe database accepts api on one port and nothing else.

Default deny is the right posture, but apply it namespace by namespace with the team that owns the workloads. A cluster wide switch that nobody planned for will page people at midnight for traffic that used to just work.

Select Pods, Then Decide Who May Connect

Ingress rules answer two questions: which Pods does this policy govern, and who is allowed to reach them. The governing selection is podSelector at the top level. Each entry in ingress has a from list and a ports list. Any entry that matches both the source and the port allows the connection.

Each item inside from is a peer, and a peer can reference Pods, namespaces, or IP blocks. Peers in the same list are ORed together, while selectors inside a single peer are ANDed. That single distinction explains most selector bugs.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-frontend-to-api
  namespace: payments
spec:
  podSelector:
    matchLabels:
      app: api
  policyTypes:
    - Ingress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: frontend
      ports:
        - protocol: TCP
          port: 8080

This policy governs Pods labeled app: api and allows TCP 8080 only from Pods labeled app: frontend in the same namespace. To also allow a monitoring namespace, add a second peer. Combining a namespaceSelector and a podSelector in one peer means “Pods with these labels, in namespaces with those labels”, which is an AND, not an OR.

Selector logic where peers in a list are ORed and selectors inside one peer are ANDedSeparate peers are OR, one peer with two selectors is ANDTwo peers: allow either onefrom:- podSelector: app=frontend- namespaceSelector: team=obsfrontend Pods OR Obs namespace Podseach peer stands aloneOne peer, two selectors: allow bothfrom:- podSelector: app=frontendnamespaceSelector: team=payfrontend Pods that are also innamespaces labeled team=pay

Use ipBlock for traffic to or from addresses outside the cluster, such as a managed database or a monitoring host. An ipBlock peer can include an except list to carve out subnets, and because it refers to raw addresses rather than Pods, a port on an ipBlock rule must be numeric.

Control Egress, Including DNS

Most teams start with ingress and forget egress. If your default deny policy lists Egress in policyTypes, Pods lose outbound access as well, including the DNS lookups that almost everything depends on. That is why a default deny will take down a healthy application even though it makes no inbound requests.

Allow DNS explicitly, scoped to the namespace that runs it, rather than opening all egress. Modern clusters label every namespace with kubernetes.io/metadata.name, which gives you a stable selector that does not depend on your own labels.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-dns-egress
  namespace: payments
spec:
  podSelector: {}
  policyTypes:
    - Egress
  egress:
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kube-system
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53

Egress rules matter for reducing the blast radius of a compromised workload. A Pod that can only reach the database and the API server is far less useful to an attacker than one that can reach the whole cluster and the internet. When you allow egress to a service, remember that Pod IPs change, so selectors on Pod labels survive churn while hard coded addresses do not.

A frequent oversight is health checking. Kubelet runs on the node and probes Pods over the node network, which is not always governed the same way as Pod traffic. If probes start failing after you apply default deny, verify that the plugin and the cluster are treating node to Pod traffic as you expect before you assume the application is broken.

Which Plugin Actually Enforces Policies

NetworkPolicy is an API contract, not an implementation. The cluster’s network plugin decides whether the rules are enforced at all. Calico, Cilium, Antrea, and OVN-Kubernetes enforce them, and each adds its own extensions and observability. A plain Flannel installation does not, and neither does a default kind or minikube network in many configurations.

Managed services vary. GKE with Dataplane V2 uses Cilium and enforces policies. Legacy GKE Calico mode enforces them, while some clusters without a policy capable dataplane silently ignore the objects. Amazon EKS needs a policy capable option, since the default VPC CNI historically did not enforce native NetworkPolicy. Azure AKS supports either Cilium or Calico as the policy engine.

The dangerous case is a cluster that accepts the objects but does not enforce them. kubectl apply succeeds, the resource appears in kubectl get networkpolicy, and nothing changes. Before you rely on policies, confirm the dataplane with a controlled test: apply default deny to a scratch namespace and verify that a previously working connection now fails.

A Practical Adoption Plan

Roll policies out in a way that keeps the cluster working while you tighten it. Start by observing what actually talks to what, then write allowances that match reality, and only then switch to deny. A namespace that has never had policies is not ready for a blanket default deny on a Friday afternoon.

A workable sequence looks like this:

  1. Choose one namespace and identify its entry points, its dependencies, and its datastores.
  2. Write explicit allow policies for each known flow, including DNS and any egress to external services.
  3. Apply default deny in that namespace and watch the logs and error rates during business hours.
  4. Fix the gaps that appear, then repeat in the next namespace.
  5. Keep the policies in version control next to the workloads they protect, so a deploy and its network rules move together.

Review policies when services change, not only when incidents happen. A renamed label, a new dependency, or a database migration can leave an allow policy pointing at a selector that no longer matches anything, which is a slow outage waiting for the next deploy. The common TCP and UDP ports reference is useful when you are translating an application’s documented ports into policy rules.

What to Remember About Network Policies

NetworkPolicy gives you a flat network by default and a segmented one when you ask for it. The model is small: select Pods, declare the directions you govern, and list the peers and ports that are allowed. The difficulty is entirely in the details, which are worth restating.

  • Policies are additive allow lists with no ordering and no deny rules.
  • A direction is only restricted once a policy selects a Pod for that direction.
  • Peers in a list are ORed, and selectors inside one peer are ANDed.
  • Default deny also blocks DNS, so allow name resolution with the rest of your egress.
  • Enforcement belongs to the CNI, so verify the dataplane before trusting the policy.
  • Host network traffic and node level probes can behave differently and deserve testing.

Treat the policy set as part of the application, reviewed and deployed together with the workload, and it stops being a mysterious object that only matters during incidents.

Frequently Asked Questions

What does a Kubernetes NetworkPolicy do?
A NetworkPolicy selects Pods in a namespace and defines which ingress or egress traffic they are allowed to receive or send. Policies are additive allow lists, so a Pod can only communicate in a direction if at least one policy that selects it permits that traffic.
Is all traffic allowed by default in Kubernetes?
Yes. If no NetworkPolicy selects a Pod, all ingress and egress traffic is allowed. The default is open, so you have to create a policy that selects the Pods you want to restrict before any filtering takes effect.
Do NetworkPolicies work on every Kubernetes cluster?
No. Enforcement depends on the network plugin. Plugins such as Calico, Cilium, Antrea, and OVN-Kubernetes enforce policies, while a plain Flannel installation does not. On a cluster without a policy capable plugin, the objects are accepted but silently ignored.
How do I block all traffic and then allow specific flows?
Create a policy with an empty podSelector to select every Pod in the namespace and set policyTypes to both Ingress and Egress with no rules. That default deny policy blocks everything, and you then add narrower policies that allow the specific traffic each workload needs.

Free Tools

Put this into practice with the free calculators, reference tables, and checklists in the Obelinf toolkit.

Stop reaching for a spreadsheet

Obelinf keeps every subnet, device, circuit, and rack in one live source of truth, with audit logs and a topology view. Free for personal use.

Related Articles