How to troubleshoot a Kubernetes Service

A Kubernetes Service can keep its ClusterIP and DNS name even when no backend pod can receive traffic. When requests to a Service fail, hang, or refuse a kubectl port-forward test, start by proving whether the Service has selected ready pods and published them through EndpointSlices.

Service routing depends on three connected pieces. The selector on the Service must match pod labels, the Service port and targetPort must lead to the container listener, and the selected pods must be ready enough to appear as endpoints. A mismatch in any layer leaves ClusterIP, NodePort, LoadBalancer, and port-forward paths without a usable backend.

One common Service failure has web selecting app=api while the running backend pod is labeled app=web. Replace web and any namespace options with the affected Service, then apply only the correction that matches the workload that should receive traffic.

Steps to troubleshoot a Kubernetes Service:

  1. Inspect the Service selector, ports, and endpoint summary.
    $ kubectl describe service web
    Name:                     web
    Namespace:                default
    Labels:                   app=web
    Annotations:              <none>
    Selector:                 app=api
    Type:                     ClusterIP
    IP Family Policy:         SingleStack
    IP Families:              IPv4
    IP:                       10.96.186.225
    IPs:                      10.96.186.225
    Port:                     <unset>  80/TCP
    TargetPort:               8080/TCP
    Endpoints:
    Session Affinity:         None
    Internal Traffic Policy:  Cluster
    Events:                   <none>

    Add --namespace app when the Service is not in the current namespace. The empty Endpoints line means no selected ready backend is available for this Service.

  2. Compare the Service selector with pod labels in the same namespace.
    $ kubectl get pods --show-labels
    NAME                  READY   STATUS    RESTARTS   AGE     LABELS
    web-7897fdff9-dp5sl   1/1     Running   0          2m16s   app=web,pod-template-hash=7897fdff9

    The Service selector app=api does not match the backend pod label app=web. Patch or update only the intended Service; a broad selector can send traffic to unrelated pods.

  3. Check the Service EndpointSlice records.
    $ kubectl get endpointslice -l kubernetes.io/service-name=web
    NAME        ADDRESSTYPE   PORTS     ENDPOINTS   AGE
    web-82d9s   IPv4          <unset>   <unset>     2m8s

    EndpointSlices are the current Service backend records. A row with ENDPOINTS set to <unset> usually points to a selector, readiness, or target-port problem.

  4. Correct the Service selector to match the intended backend pods.
    $ kubectl patch service web --type=merge -p '{"spec":{"selector":{"app":"web"}}}'
    service/web patched

    For source-controlled workloads, make the same selector change in the Service manifest or Helm values so the fix survives the next apply.

  5. Confirm that the EndpointSlice now has a backend address and port.
    $ kubectl get endpointslice -l kubernetes.io/service-name=web
    NAME        ADDRESSTYPE   PORTS   ENDPOINTS    AGE
    web-82d9s   IPv4          8080    10.244.0.5   2m8s

    If endpoints appear but traffic still fails, compare the Service targetPort with the container listener and check the backend pod logs.

  6. Start a temporary Service port-forward.
    $ kubectl port-forward service/web 18080:80
    Forwarding from 127.0.0.1:18080 -> 8080
    Forwarding from [::1]:18080 -> 8080
    Handling connection for 18080

    18080:80 maps local port 18080 to Service port 80. Keep this terminal open while testing.

  7. Request the Service through the forwarded local port from another terminal.
    $ curl --silent http://127.0.0.1:18080/
    NOW: 2026-06-26 13:28:09 +0000 UTC

    A backend response proves the Service selector, EndpointSlice, target port, and selected pod path are working for this Service.

  8. Stop the temporary port-forward session.
    ^C