Helm does not create a network; it renders Kubernetes manifests that define how your services are exposed and discovered. When an application deployed with Helm cannot be reached, the causes usually fall into a few buckets: DNS resolution, ports and endpoints, routing and NetworkPolicies, ingress configuration, cloud load balancer assignment, or host firewalls/proxies.
This guide gives a practical, step-by-step path to diagnose and correct Helm-related networking issues without guesswork. You will inventory your environment, render and inspect what Helm will apply, run safe connectivity tests from inside the cluster, and use clear recovery and rollback procedures.
Constructed examples in this article use hypothetical names like myapp, ns-demo, and example.local to illustrate commands and expected outputs.
Version and Environment Inventory
Start with facts you can trust. Record versions, contexts, and release details so every test and fix is traceable.
Prerequisites
- Access to the Kubernetes cluster and namespace where the Helm release runs
- kubectl and helm installed on your workstation
- RBAC allowing read access to the target namespace and kube-system for DNS checks
Inventory commands (read-only)
# Versions
helm version --short
kubectl version --short
# Current context and namespace
kubectl config current-context
kubectl get ns
# Helm releases in your target namespace
NAMESPACE=ns-demo
helm list -n "$NAMESPACE"
# Capture values and rendered manifests for a specific release
RELEASE=myapp
helm get values "$RELEASE" -n "$NAMESPACE" --all > values.$RELEASE.yaml
helm get manifest "$RELEASE" -n "$NAMESPACE" > manifest.$RELEASE.yaml
# Alternatively, render locally without applying
CHART_PATH=./charts/myapp
helm template "$RELEASE" "$CHART_PATH" -n "$NAMESPACE" -f values.$RELEASE.yaml > rendered.$RELEASE.yaml
Expected results
- You know the exact Helm release name and namespace.
- You have values.$RELEASE.yaml and rendered.$RELEASE.yaml to inspect Service, Ingress, and NetworkPolicy definitions.
If any of these steps fail, correct access or path issues before continuing. A missing release or namespace means you may be targeting the wrong environment.
Safe Configuration Path
Minimize risk while you probe the network state.
Scope and isolation
- Work in the specific namespace. Always pass -n "$NAMESPACE" to helm and kubectl.
- Prefer read-only kubectl get/describe and helm get/template first.
- For in-cluster tests, create ephemeral pods that you delete when finished. Do not modify app pods or services until you have a clear finding.
Guardrails
- Do not change cluster-wide DNS or CNI settings during initial triage.
- Use helm upgrade with --wait and a reasonable --timeout for controlled changes.
- Keep rollback available: use helm history and helm rollback.
# Preview an intended change without applying it
helm template "$RELEASE" "$CHART_PATH" -n "$NAMESPACE" -f values.$RELEASE.yaml > /tmp/proposed.yaml
# Apply a change with safety
helm upgrade "$RELEASE" "$CHART_PATH" -n "$NAMESPACE" -f values.$RELEASE.yaml --wait --timeout 5m
# Roll back if needed
helm history "$RELEASE" -n "$NAMESPACE"
# Pick a known-good REV
helm rollback "$RELEASE" REV -n "$NAMESPACE" --wait --timeout 5m
Verification and Diagnostics
Work from general to specific. Stop at the first failing layer and fix it before proceeding.
Quick symptom-to-check map
| Symptom | First check | Likely cause |
|---|---|---|
| Pod cannot resolve a service name | DNS lookup from an in-cluster test pod | CoreDNS issues or wrong service name/namespace |
| Service has no endpoints | Selectors vs pod labels | Label mismatch or pods not Ready |
| Port open but HTTP fails | Port/targetPort mismatch or readiness probe failures | Wrong values.yaml mapping or app not ready |
| Ingress host not reachable | Ingress status and rules | Wrong host, TLS, or controller class |
| External IP Pending | Service type LoadBalancer events | Cloud LB provisioning or quota |
| Works inside cluster, not outside | Node firewall or cloud security group | Egress/ingress blocked at host or network |
Note: The table items are constructed examples.
1) Cluster connectivity and namespace scope
kubectl cluster-info
kubectl get ns | grep -E "(^NAME|$NAMESPACE)"
helm status "$RELEASE" -n "$NAMESPACE"
Expected results
- cluster-info returns URLs for the control plane.
- helm status shows the release with a list of resources.
If helm status fails, confirm the release and namespace. If the release is in a different namespace, switch or adjust NAMESPACE.
2) Inspect rendered Services, Endpoints, and Ingress
Render and compare what Helm intended with what is running.
# Running resources
kubectl get svc, ep, ingress -n "$NAMESPACE"
# Detailed describe for a specific service
SVC=myapp-svc
kubectl describe svc "$SVC" -n "$NAMESPACE"
# Endpoints must list pod IPs and ports
kubectl get endpoints "$SVC" -n "$NAMESPACE" -o wide
# Show pod labels and readiness
kubectl get pods -n "$NAMESPACE" -o wide --show-labels
Expected results
- The Service selector matches pod labels (e.g., app=myapp).
- Endpoints show at least one Ready pod IP and correct port.
Common finding: Endpoints are empty. Fix the selector or the pod labels in values.yaml and roll forward, or roll back to a known revision.
3) DNS: Service discovery inside the cluster
Validate DNS from inside the cluster. Use an ephemeral BusyBox or similar image. Delete the pod afterwards.
# Create a short-lived pod for DNS and connectivity tests
TEST_NS="$NAMESPACE"
kubectl run net-test -n "$TEST_NS" --image=busybox:1.35 --restart=Never --command -- sh -c "sleep 3600"
# Wait for it to be Running
kubectl wait --for=condition=Ready pod/net-test -n "$TEST_NS" --timeout=60s
# DNS lookups
kubectl exec -n "$TEST_NS" net-test -- nslookup kubernetes.default.svc.cluster.local || true
kubectl exec -n "$TEST_NS" net-test -- nslookup myapp-svc.$TEST_NS.svc.cluster.local || true
# Clean up later with: kubectl delete pod/net-test -n "$TEST_NS"
Expected results
- kubernetes.default.svc.cluster.local resolves to the API server cluster IP.
- myapp-svc.$TEST_NS.svc.cluster.local resolves to the Service cluster IP.
If DNS fails
- Check CoreDNS:
kubectl -n kube-system get deploy, po -l k8s-app=kube-dns || true
kubectl -n kube-system get deploy, po -l k8s-app=coredns || true
kubectl -n kube-system logs deploy/coredns --tail=200 || true
kubectl -n kube-system get svc kube-dns || true
- Inspect the service name and namespace. Typos are common. Ensure the FQDN matches serviceName.namespace.svc.cluster.local.
4) Ports and targetPorts: confirm wiring
Verify that your Service port and targetPort match the container port exposed by the pod. Review rendered YAML from Helm and compare to running objects.
# From rendered Helm YAML (search for your service block)
grep -n "kind: Service" -n rendered.$RELEASE.yaml -n | head -n 1
# Then open around that section to read ports and selectors
# Live check of ports
kubectl get svc "$SVC" -n "$NAMESPACE" -o yaml | grep -A5 "ports:"
# Container ports in pods
POD=$(kubectl get pods -n "$NAMESPACE" -l app=myapp -o jsonpath='{.items[0].metadata.name}')
kubectl get pod "$POD" -n "$NAMESPACE" -o jsonpath='{.spec.containers[*].ports}' ; echo
Expected results
- For HTTP on 8080, Service.spec.ports[0].port might be 80, targetPort 8080, and the container has containerPort 8080.
If targetPort does not match the containerPort that the app actually listens on, update values.yaml and redeploy, or roll back.
5) Connectivity tests from inside the cluster
Use straightforward tools to verify TCP reachability and basic HTTP from the net-test pod created earlier.
# TCP test to the service name and port
kubectl exec -n "$TEST_NS" net-test -- sh -c "nc -vz myapp-svc.$TEST_NS.svc.cluster.local 8080 || true"
# HTTP test (if the app speaks HTTP)
kubectl exec -n "$TEST_NS" net-test -- sh -c "wget -S -O - http://myapp-svc.$TEST_NS.svc.cluster.local:8080/health || true"
Expected results
- nc shows succeeded (open) and wget returns a 200 or expected status for /health.
If TCP fails but DNS works, re-check Endpoints and NetworkPolicies. If HTTP fails while TCP opens, check readiness probes and application logs.
6) NetworkPolicies: allow traffic explicitly
If your cluster uses NetworkPolicies, services may be reachable only from specific pods/namespaces. List policies and look for denies.
kubectl get networkpolicy -A
kubectl describe networkpolicy -n "$NAMESPACE" # review policies in the app namespace
Expected results
- Either no policies exist, or existing policies allow traffic from expected sources to your pod labels and ports.
Fix approach
- Add or adjust an allow policy for the app selector on the required ports. Apply via Helm if policies are part of the chart. Validate with the same net-test commands.
7) Ingress and external exposure
If you expose the app via Ingress, confirm rules, class, and status.
ING=myapp-ing
kubectl get ingress "$ING" -n "$NAMESPACE" -o yaml | grep -E "host:|ingressClassName|tls:|service:" -n
kubectl describe ingress "$ING" -n "$NAMESPACE"
Expected results
- The host matches your DNS entry (e.g., app.example.local).
- The serviceName and servicePort align with your Service.
- The Ingress has an address in status.loadBalancer.ingress or your controller advertises a reachable VIP.
Troubleshooting ideas
- If the host is wrong, correct values.yaml and upgrade.
- If status has no address, check the Ingress controller deployment and its service.
- For TLS issues, verify the secret name and certificate validity.
8) LoadBalancer and NodePort services
For Service type LoadBalancer, ensure the external IP is provisioned and open.
kubectl get svc "$SVC" -n "$NAMESPACE" -o wide
kubectl describe svc "$SVC" -n "$NAMESPACE" | sed -n '/Events/,$p'
Expected results
- EXTERNAL-IP is populated (not Pending) and ports align with expectations.
If EXTERNAL-IP is Pending
- Review cloud provider integration and quotas (cluster events may mention reason).
- As a temporary path, switch to NodePort for internal testing and access via nodeIP:nodePort (only in controlled environments).
9) Host firewalls and proxies
When things work in-cluster but not from outside, inspect host firewalls and proxies.
# Linux firewall quick views (run on target nodes if you have access)
sudo iptables -S | head -n 50 || true
sudo nft list ruleset | head -n 80 || true
sudo ufw status verbose || true
# Workstation or jump-host proxies
env | grep -i _proxy || true
Actions
- Allow the required inbound ports on nodes or through the cloud security group.
- Configure NO_PROXY to include cluster subnets and .svc,.cluster.local if your Helm client or tooling needs direct access:
export NO_PROXY="127.0.0.1, localhost,.svc,.cluster.local,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16"
Mapping Helm values to networking fields
If your chart is values-driven, confirm how values.yaml fields map to Service and Ingress. Read the chart templates and defaults.
Constructed mapping example
| Helm values key | Affects resource | Typical field |
|---|---|---|
| service.type | Service | spec.type |
| service.port | Service | spec.ports[].port |
| service.targetPort | Service | spec.ports[].targetPort |
| service.annotations | Service | metadata.annotations |
| ingress.enabled | Ingress | N/A (resource presence) |
| ingress.className | Ingress | spec.ingressClassName |
| ingress.hosts[0].host | Ingress | spec.rules[].host |
| ingress.hosts[0].paths[0].service.port | Ingress | spec.rules[].http.paths[].backend.service.port |
| networkPolicy.enabled | NetworkPolicy | N/A (resource presence) |
Use helm template to render and confirm these fields before applying changes.
Failure Modes and Recovery
Below are common failure modes with precise fixes, expected verification, and rollback guidance. All examples are constructed.
- DNS resolution fails inside the cluster
- Symptom: nslookup myapp-svc.ns-demo.svc.cluster.local fails.
- Likely cause: CoreDNS not running, wrong service name/namespace.
- Fix:
- Ensure CoreDNS deployment is healthy: kubectl -n kube-system get deploy coredns.
- Correct references to the service FQDN in probes or other charts.
- Verify: Run nslookup again from net-test; expect an A record.
- Rollback: If you changed ConfigMap for CoreDNS and broke resolution, roll back that ConfigMap from backup or apply the previous known-good version, then restart CoreDNS pods.
- Service has no endpoints
- Symptom: kubectl get endpoints myapp-svc shows <none>.
- Likely cause: Service selector does not match pod labels; pods not Ready.
- Fix:
- Compare Service.selector to kubectl get pods --show-labels.
- Update values.yaml labels or selectors in the chart, then helm upgrade.
- Verify: Endpoints list pod IPs with correct ports.
- Rollback: helm rollback to the last revision that had matching selectors.
- Port/targetPort mismatch
- Symptom: TCP connects but request fails or times out; logs show app listening on a different port.
- Fix:
- Align service.targetPort with the containerPort or app listen port in values.yaml.
- If using named ports, ensure both the container and the Service reference the same name.
- Verify: wget http://service: port/health returns expected status.
- Rollback: helm rollback to a revision before the port change if disruption occurred.
- NetworkPolicy blocks traffic
- Symptom: Works from some pods but not others; net-test fails in specific namespaces.
- Fix:
- Add an allow policy for the app selector and required ports from the calling namespaces.
- Apply via Helm if the chart owns policies.
- Verify: Repeat net-test TCP/HTTP checks from allowed and denied namespaces.
- Rollback: Revert the last policy change via helm rollback or kubectl apply of the previous manifest.
- Ingress host misconfiguration
- Symptom: curl to https://app.example.local fails with 404 or TLS error.
- Fix:
- Ensure ingress.hosts[].host matches the external DNS record.
- Set ingress.className to match your controller.
- Verify TLS secret names.
- Verify: kubectl describe ingress shows correct rules and address; curl returns the app response.
- Rollback: helm rollback to the last known-good ingress config.
- External IP Pending on LoadBalancer service
- Symptom: EXTERNAL-IP is Pending for minutes; Events mention provisioning.
- Fix:
- Check cloud provider quotas and permissions; adjust annotations if required by your environment.
- Temporarily switch to NodePort for internal testing if safe.
- Verify: kubectl get svc shows a populated EXTERNAL-IP or reachable NodePort.
- Rollback: If a change to annotations caused failure, remove or revert those annotations and redeploy.
- Proxy environment interferes with Helm or tests
- Symptom: helm repo update or connectivity tests behave inconsistently.
- Fix:
- Set NO_PROXY to include in-cluster domains and CIDRs; unset HTTP(S)_PROXY for in-cluster addresses.
- Verify: Retry helm repo update and in-cluster tests.
- Rollback: Restore prior proxy settings if needed and document the exclusions.
Practical rollback and recovery flow
Use Helm history to choose a stable baseline, then move forward with small, testable changes.
# Inspect revisions and dates
helm history "$RELEASE" -n "$NAMESPACE"
# Roll back to a specific working version
helm rollback "$RELEASE" REV -n "$NAMESPACE" --wait --timeout 5m
# Re-apply a corrected values file
helm upgrade "$RELEASE" "$CHART_PATH" -n "$NAMESPACE" -f values.$RELEASE.yaml --wait --timeout 5m
Expected results
- The rollback completes without errors, and Services/Ingress return to known-good behavior.
- After applying corrections, verification checks pass in the order outlined earlier.
Verification: end-to-end sanity pass
After fixes, rerun the minimal end-to-end checks:
- Rendered vs running
- helm get manifest vs kubectl get svc/ing/ep show consistent ports, selectors, and hosts.
- DNS
- nslookup service.namespace.svc.cluster.local resolves inside the cluster.
- TCP and HTTP
- nc -vz service port succeeds; wget http://service: port/health returns expected code.
- Ingress
- kubectl describe ingress shows correct host and address; curl using the host returns the app.
- External access
- If using LoadBalancer, EXTERNAL-IP responds on the expected port.
Operations Checklist
Use this concise checklist for each Helm networking incident.
| Step | Command or action | Expected signal |
|---|---|---|
| 1. Inventory | helm version; kubectl version; helm list -n ns | Versions recorded; release located |
| 2. Values & render | helm get values; helm template | Service/Ingress fields visible |
| 3. Live state | kubectl get svc, ep, ing -n ns | Resources present; endpoints not empty |
| 4. DNS in-cluster | nslookup service.ns.svc.cluster.local | A record returned |
| 5. Ports | kubectl describe svc; compare targetPort/containerPort | Ports align |
| 6. TCP/HTTP test | nc -vz; wget http://service: port/health | Connect and 200/expected code |
| 7. Policies | kubectl get/describe networkpolicy | Allows match traffic |
| 8. Ingress | kubectl describe ingress; curl host | Rules and TLS correct |
| 9. External IP | kubectl get svc -o wide | EXTERNAL-IP assigned |
| 10. Firewalls/proxy | iptables/nft/ufw; NO_PROXY set | Paths open; no proxy interference |
Conclusion
Helm networking troubleshooting is about validating the Kubernetes resources that Helm renders and how they interact with cluster DNS, ports, and routing. By:
- Inventorying versions, namespaces, release names, and rendered manifests,
- Testing DNS and connectivity from inside the cluster first,
- Verifying service selectors, endpoints, and port mappings,
- Checking NetworkPolicies, Ingress rules, and external IP assignment,
- And applying changes with helm upgrade --wait and having helm rollback ready,
you can isolate root causes quickly and recover safely. Start with a narrow, measurable test (for example, resolve the service FQDN and hit a /health endpoint) before expanding scope. With these practices, Helm-driven deployments stay observable, debuggable, and resilient in the face of networking surprises.