Kubernetes networking cheat sheet
Services, DNS, Ingress, NetworkPolicy and the CNI, with the command that answers each question and the failure each one distinguishes. Ordered by where in the path a request breaks.
- Kubernetes1.36.4
- CNICalico v3.32.1
- ingress-nginxcontroller-v1.13.1
- MetalLBv0.15.2
- Commands42
- Reviewed21 August 2026
DNS names and resolution
-
<service>.<namespace>.svc.cluster.localThe fully qualified name of a Service. The short form works only within the same namespace.
Cross-namespace calls need at least <service>.<namespace>. A name that resolves from one Pod and not another is usually this.
-
<pod>.<service>.<namespace>.svc.cluster.localA per-Pod name, which only exists for a headless Service.
This is what a StatefulSet's stable identity actually is, and it needs the governing Service to have clusterIP: None.
-
kubectl exec <pod> -- cat /etc/resolv.confThe nameserver and search domains the Pod was given.
`ndots: 5` is why a short name generates several failed lookups before the right one, and why an external name resolves slowly.
-
kubectl exec <pod> -- nslookup <name>Resolve from inside the cluster, through CoreDNS.
Resolving from your laptop proves nothing: cluster names only exist to Pods.
-
kubectl -n kube-system get pods -l k8s-app=kube-dnsWhether CoreDNS is running. A CoreDNS Pod not Ready explains every resolution failure at once.
-
kubectl -n kube-system get cm coredns -o yamlThe Corefile: the cluster domain, forwarding and any stub domains.
-
kubectl -n kube-system logs -l k8s-app=kube-dns --tail=50CoreDNS logs, where an upstream loop or a broken forward shows up.
Services and endpoints
-
kubectl get svc <name> -o wideType, cluster IP, ports and the selector.
-
kubectl get endpointslice -l kubernetes.io/service-name=<svc>The addresses behind a Service. The single most useful Service command.
Empty means the selector matches nothing, the Pods are not Ready, or they are Pending. That splits the problem in one command.
-
kubectl get pods --show-labels -n <ns>Compare Pod labels against the Service selector.
The selector matches labels and nothing else. Two objects can each look correct and still not match.
-
kubectl describe svc <name>Includes the resolved endpoints inline, which is quicker than fetching the EndpointSlice.
-
kubectl get svc <name> -o jsonpath='{.spec.ports[0].targetPort}'Which container port traffic is sent to.
Wrong targetPort with healthy endpoints is a 502 through an Ingress and connection refused directly. It does not look like a Service fault.
-
kubectl get svc <name> -o jsonpath='{.status.loadBalancer.ingress}'The external address, if anything assigned one.
Empty with no events at all means nothing is watching LoadBalancer Services. On bare metal that is the normal state without MetalLB.
-
kubectl get ipaddresspool -AMetalLB's address pools. None means MetalLB is installed and idle.
A pool with no L2Advertisement or BGPAdvertisement allocates addresses and never announces them, which looks healthy and is unreachable.
kube-proxy and the data path
-
kubectl -n kube-system get ds kube-proxyOne Pod per node. A node missing it has broken Service routing and working Pod networking.
-
sudo iptables-save | grep -c KUBE-SERVICESConfirm kube-proxy has written its iptables rules on this node.
-
sudo ipvsadm -LnThe IPVS virtual servers, when kube-proxy runs in IPVS mode.
In iptables mode this is empty. Check the mode before concluding anything from an empty list.
-
sudo nft list tablesWhether the node's rules live in nftables, which modern iptables writes to underneath.
iptables-save and nft list ruleset are two views of the same rules. Rules under both legacy and nft backends is the real problem.
-
ip route get <pod-ip>How this node reaches a Pod address: via another node's IP, or through a tunnel interface.
With Calico's CrossSubnet, same-subnet nodes route natively and the VXLAN device carries nothing.
-
ip -o route | grep -E 'blackhole|dev cali'The per-Pod /32 host routes and the blackhole for the node's own IPAM block.
The blackhole is deliberate: it drops traffic to unassigned addresses in the local block instead of leaking it upstream.
-
kubectl get blockaffinities.crd.projectcalico.orgWhich IPAM block Calico gave each node. This is where Pod IPs come from.
Calico ignores the Node object's spec.podCIDR entirely, so reading it while tracing an address is misleading.
-
kubectl get installation default -o jsonpath='{.status.mtu}'The MTU Calico calculated.
MTU mismatch is the classic overlay failure: small requests work, large ones hang, TLS handshakes complete and then stall.
NetworkPolicy
-
kubectl get netpol -AEvery policy in the cluster. The first command when connectivity is unexplained.
Policies are namespaced, so a count above zero says nothing about whether your namespace is affected. Check both ends of the traffic.
-
kubectl describe netpol -n <ns>Every policy in a namespace explained in sentences, including what is isolated.
Unusually good output for this resource: it spells out that an empty podSelector means all Pods in the namespace.
-
kubectl get networkpolicies.crd.projectcalico.org -ACalico's own policies, which kubectl get netpol never shows.
Also check globalnetworkpolicies, which are cluster-wide and absent from any namespaced listing. Native policies can carry explicit deny actions.
-
kubectl run t -n <ns> --restart=Never --image=busybox:1.36 --command -- wget -qO- --timeout=5 http://<svc>/Probe from inside the namespace, then read it with kubectl logs.
Timed out means packets are dropped, which is a policy or a firewall. Connection refused means routing worked and nothing is listening.
-
wget: bad address <name>The DNS-blocked signature: an egress policy is denying port 53 to kube-system.
Distinct from a timeout. Allow both UDP and TCP 53 with a namespaceSelector on kubernetes.io/metadata.name.
Ingress and Gateway
-
kubectl get ingressclassWhich controllers are installed and what their controller names are.
An Ingress naming an absent class is created with no error, has an empty ADDRESS, and 404s everything.
-
kubectl describe ingress <name>The rules as the controller resolved them, with the Pod IPs behind each backend.
Empty parentheses after a backend mean the Service selects nothing, which is the difference between a routing problem and a backend problem.
-
curl -s -o /dev/null -w '%{http_code}' -H 'Host: <host>' http://<addr>/<path>Test a rule without DNS. 404 is no match, 503 is no endpoints, 502 is a bad backend response.
-
curl -s -H 'Host: <host>' http://<addr>/<path> | grep -iE 'title|center'Read the error body. A server version footer means the backend wrote the 404, not the controller.
The most time-saving check in Ingress debugging, and the one people skip.
-
kubectl logs -n ingress-nginx deploy/ingress-nginx-controller --tail=50The access log: method, path and status for every request the controller handled.
A failing request absent from this log never reached the controller, which moves the search to DNS, the load balancer or NetworkPolicy.
-
curl -sk --resolve <host>:<port>:<ip> https://<host>:<port>/Test HTTPS with the real hostname for SNI while connecting to a chosen address.
A Host header is not enough for TLS: the controller picks a certificate from SNI before decrypting anything.
-
echo | openssl s_client -connect <ip>:443 -servername <host> | openssl x509 -noout -subject -issuer -datesThe certificate actually being served. Subject equal to issuer means self-signed.
A subject naming a Fake Certificate means the controller could not load your Secret and fell back to its own.
-
kubectl get gateway <name> -o jsonpath='{.status.conditions}'Accepted means a controller claimed it; Programmed means the data plane is configured.
Both True with an empty status.addresses means the Gateway's Service has no external address. Programmed is not reachable.
-
kubectl get httproute <name> -o jsonpath='{.status.parents}'Per-parent status. NotAllowedByListeners is a refused attachment; BackendNotFound is a missing Service.
Two independent failures with two separate conditions, which is the diagnostic quality Ingress lacks.
Reading the failure
-
404 from an Ingress or GatewayNo rule matched the host and path, or the backend itself returned it.
Establish who wrote it first: read the body, then ask the backend directly through its Service from a Pod.
-
503 from an IngressA rule matched and the Service has no ready endpoints.
Go straight to the EndpointSlice. Populated endpoints with a 503 means the Ingress names a port the Service does not define.
-
502 from an IngressThe controller connected to a Pod and could not use the answer.
Opposite investigation from a 503: the Pod's real port, the protocol, or a crash mid-request. An HTTPS backend needs backend-protocol: HTTPS.
-
Connection timed outPackets dropped in silence. A NetworkPolicy, a firewall or a broken route.
The backend's logs will be empty, because the request never arrived. That emptiness is the clue.
-
Connection refusedSomething answered with a reset: routing works and nothing is listening on that port.
Points at a wrong port or targetPort, or an application that has not started.
-
curl reports 000No HTTP exchange happened at all: the connection or the TLS handshake failed.
Not a server error, because there was never a response to have a status. Certificate verification failure produces this too.
No command matches that search.