Configure Kubernetes network monitoring with Hubble and Cilium for traffic visibility

Advanced 75 min Aug 25, 2026
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Enable Hubble on Cilium to get real-time eBPF-based flow visibility, deploy Hubble UI and Relay, and integrate flow metrics with Prometheus and Grafana for full Kubernetes network observability.

Prerequisites

  • A running Kubernetes cluster with Cilium installed as the CNI
  • kubectl and Helm 3 configured against the cluster
  • Cluster admin access to install CRDs and DaemonSets
  • An ingress controller with cert-manager for exposing Hubble UI
  • Prometheus Operator or a standalone Prometheus instance for metrics scraping

What this solves

Cilium's eBPF dataplane already sees every packet that crosses your pods, but without Hubble you have no way to query that traffic. This tutorial enables Hubble on an existing Cilium installation, deploys Hubble Relay and Hubble UI, configures the CLI for live flow inspection, and wires flow metrics into Prometheus and Grafana.

By the end you will be able to see which pods talk to which services, which network policies drop traffic, and why, without tailing application logs.

Note: This tutorial assumes Cilium is already installed as your cluster CNI. If you have not set that up yet, follow Install and configure Cilium CNI for Kubernetes with eBPF networking and security policies first.

Prerequisites and Cilium CNI verification

Confirm cluster and tooling

You need a running Kubernetes cluster (kubeadm, EKS, GKE, or self-managed) with Cilium as the CNI, kubectl configured against it, and Helm 3 installed on your workstation.

kubectl version --client
helm version
kubectl get nodes -o wide

Install the Cilium CLI

The Cilium CLI is used to check connectivity, install Hubble, and validate the dataplane. Install the latest release binary directly.

CILIUM_CLI_VERSION=$(curl -s https://raw.githubusercontent.com/cilium/cilium-cli/main/stable.txt)
curl -L --fail --remote-name-all https://github.com/cilium/cilium-cli/releases/download/${CILIUM_CLI_VERSION}/cilium-linux-amd64.tar.gz
sudo tar xzvfC cilium-linux-amd64.tar.gz /usr/local/bin
rm cilium-linux-amd64.tar.gz
CILIUM_CLI_VERSION=$(curl -s https://raw.githubusercontent.com/cilium/cilium-cli/main/stable.txt)
curl -L --fail --remote-name-all https://github.com/cilium/cilium-cli/releases/download/${CILIUM_CLI_VERSION}/cilium-linux-amd64.tar.gz
sudo tar xzvfC cilium-linux-amd64.tar.gz /usr/local/bin
rm cilium-linux-amd64.tar.gz

Verify Cilium status and connectivity

Before enabling Hubble, confirm Cilium itself is healthy across every node. Any node reporting degraded status will produce incomplete flow data later.

cilium status --wait
kubectl -n kube-system get pods -l k8s-app=cilium

Run a full connectivity test if this is a new cluster. It deploys temporary test pods and validates pod-to-pod, pod-to-service, and cross-node traffic.

cilium connectivity test --test-namespace cilium-test
Warning: The connectivity test creates workloads that consume cluster resources for several minutes. Do not run it against a production namespace under heavy load.

Installing and enabling Hubble in Cilium

Enable Hubble on the Cilium DaemonSet

Hubble ships as part of Cilium but is disabled by default. Enabling it turns on the local Hubble server inside each Cilium agent, which exposes flow data over a Unix socket.

cilium hubble enable --ui

This patches the Cilium ConfigMap, restarts the agent pods, and additionally deploys Hubble Relay and Hubble UI in one step. Wait for the rollout to finish.

kubectl -n kube-system rollout status daemonset/cilium
cilium status --wait

Confirm Hubble is reporting flows

Check that the Hubble metrics server and flow API are active inside the agent pods.

kubectl -n kube-system exec -it ds/cilium -- cilium status | grep Hubble

Expected output shows Hubble: Ok along with the relay address once the next steps are complete.

Deploying Hubble Relay and Hubble UI

Verify Relay and UI pods

Hubble Relay aggregates flow data from every agent into a single gRPC endpoint. Hubble UI is the web frontend that queries Relay. Both were deployed by the enable command above, so confirm they are running.

kubectl -n kube-system get pods -l k8s-app=hubble-relay
kubectl -n kube-system get pods -l k8s-app=hubble-ui

Access Hubble UI locally for testing

Use port forwarding to reach the UI before you expose it externally through Ingress.

cilium hubble ui

This opens a browser tab pointed at http://localhost:12000. If you prefer manual port forwarding instead of the CLI wrapper, use kubectl directly.

kubectl -n kube-system port-forward svc/hubble-ui 12000:80

Configuring Hubble CLI for flow inspection

Install the Hubble CLI binary

The Hubble CLI queries Relay directly from your terminal, which is faster than the UI for scripting and troubleshooting.

HUBBLE_VERSION=$(curl -s https://raw.githubusercontent.com/cilium/hubble/master/stable.txt)
curl -L --fail --remote-name-all https://github.com/cilium/hubble/releases/download/${HUBBLE_VERSION}/hubble-linux-amd64.tar.gz
sudo tar xzvfC hubble-linux-amd64.tar.gz /usr/local/bin
rm hubble-linux-amd64.tar.gz

Point Hubble CLI at Relay

Forward Relay's gRPC port locally, then export it as the Hubble server address.

kubectl -n kube-system port-forward svc/hubble-relay 4245:80 &
export HUBBLE_SERVER=localhost:4245

Query live flows

Observe flows in real time, filter by namespace, or watch only dropped packets to debug connectivity issues.

hubble observe --server $HUBBLE_SERVER --last 20
hubble observe --server $HUBBLE_SERVER --namespace production --follow
hubble observe --server $HUBBLE_SERVER --verdict DROPPED --follow

Each line shows source and destination identity, port, protocol, and the verdict (forwarded, dropped, or redirected), which is far more useful than raw packet captures for debugging Kubernetes network policies.

Enabling network policy visibility and observability metrics

Turn on policy verdict metrics

By default Hubble exposes basic flow metrics. Enable additional metrics for DNS, HTTP, and policy verdicts by editing the Cilium Helm values or patching the ConfigMap.

helm upgrade cilium cilium/cilium --namespace kube-system --reuse-values \
  --set hubble.metrics.enabled="{dns,drop,tcp,flow,icmp,http,policy}" \
  --set hubble.metrics.enableOpenMetrics=true

This restarts the Cilium agents again, so run it during a maintenance window on production clusters.

kubectl -n kube-system rollout status daemonset/cilium

Test policy visibility with a deny rule

Apply a simple CiliumNetworkPolicy and confirm Hubble reports the resulting drops. This is the same mechanism used by Implement Kubernetes network policies for pod-to-pod security and traffic isolation, but Hubble gives you the visibility layer that policy alone does not.

cat <<'EOF' | kubectl apply -f -
apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: deny-egress-test
  namespace: production
spec:
  endpointSelector:
    matchLabels:
      app: checkout
  egress:
  - toEndpoints:
    - matchLabels:
        app: catalog
EOF
hubble observe --server $HUBBLE_SERVER --namespace production --verdict DROPPED --follow

Exposing Hubble UI securely via Ingress

Create a dedicated namespace and basic auth secret

Never expose Hubble UI publicly without authentication. It reveals internal service topology and traffic patterns that should stay behind an authenticated proxy.

sudo apt install -y apache2-utils
htpasswd -c auth hubble-admin
kubectl -n kube-system create secret generic hubble-ui-basic-auth --from-file=auth
Never use chmod 777. The generated auth file only needs to be readable by your own user before it becomes a Kubernetes secret. Keep it at 600 permissions and delete it locally once the secret is created.
chmod 600 auth
rm auth

Create the Ingress resource

This assumes an NGINX ingress controller is already installed, as covered in Configure Kubernetes ingress controller with NGINX and SSL certificates using cert-manager.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: hubble-ui
  namespace: kube-system
  annotations:
    nginx.ingress.kubernetes.io/auth-type: basic
    nginx.ingress.kubernetes.io/auth-secret: hubble-ui-basic-auth
    nginx.ingress.kubernetes.io/auth-realm: "Authentication required"
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - hubble.example.com
    secretName: hubble-ui-tls
  rules:
  - host: hubble.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: hubble-ui
            port:
              number: 80
kubectl apply -f /tmp/hubble-ui-ingress.yaml
kubectl -n kube-system get ingress hubble-ui

Restrict access at the network layer too

Add a CiliumNetworkPolicy that only allows the ingress controller to reach Hubble UI, so even a leaked credential cannot be used from an arbitrary pod.

apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: restrict-hubble-ui-ingress
  namespace: kube-system
spec:
  endpointSelector:
    matchLabels:
      k8s-app: hubble-ui
  ingress:
  - fromEndpoints:
    - matchLabels:
        k8s:io.kubernetes.pod.namespace: ingress-nginx
kubectl apply -f /tmp/hubble-ui-policy.yaml

Integrating Hubble metrics with Prometheus and Grafana

Expose Prometheus scrape annotations

Hubble metrics are already exposed on port 9965 by default once enabled in an earlier step. Confirm the endpoint and annotate the service for scraping if you use annotation-based discovery instead of ServiceMonitors.

kubectl -n kube-system get svc hubble-metrics
curl -s http://localhost:9965/metrics | head -n 20

Create a ServiceMonitor for Prometheus Operator

If your cluster uses the Prometheus Operator, as set up in Set up Kubernetes monitoring with Prometheus Operator and custom metrics, add a ServiceMonitor that targets the Hubble metrics service.

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: hubble-metrics
  namespace: kube-system
  labels:
    release: prometheus
spec:
  selector:
    matchLabels:
      k8s-app: hubble
  namespaceSelector:
    matchNames:
    - kube-system
  endpoints:
  - port: hubble-metrics
    interval: 30s
kubectl apply -f /tmp/hubble-servicemonitor.yaml

Import a Grafana dashboard for flow data

Cilium publishes an official Hubble Grafana dashboard. Import it via the Cilium community JSON model, then confirm the panels populate using the metrics enabled earlier.

curl -o hubble-dashboard.json https://raw.githubusercontent.com/cilium/cilium/main/examples/kubernetes/addons/prometheus/files/grafana/dashboards/hubble-dashboard.json

Import this file through the Grafana UI under Dashboards, Import, and select your Prometheus data source. Key panels to watch are dropped packets by policy, DNS response codes, and top talkers by namespace.

Note: If you already run a shared Prometheus and Grafana stack for other services, see Configure advanced Grafana dashboards and alerting with Prometheus integration for alert rule patterns you can reuse for Hubble drop-rate alerts.

Troubleshooting traffic flows and connectivity issues

Trace a specific pod-to-pod connection

Use Hubble's identity-aware filters to isolate traffic between two workloads without noise from the rest of the cluster.

hubble observe --server $HUBBLE_SERVER \
  --from-pod production/checkout-7f9c8d \
  --to-pod production/catalog-5b6d9f \
  --last 50

Check for DNS resolution failures

DNS problems often masquerade as generic connectivity failures. Filter for DNS flows specifically.

hubble observe --server $HUBBLE_SERVER --protocol dns --follow

Inspect agent-level errors

If Hubble itself reports no data for a node, check the Cilium agent logs on that node directly.

kubectl -n kube-system logs ds/cilium -c cilium-agent --since=10m | grep -i hubble

Verify your setup

cilium status --wait
hubble observe --server $HUBBLE_SERVER --last 5
kubectl -n kube-system get pods -l k8s-app=hubble-relay
kubectl -n kube-system get pods -l k8s-app=hubble-ui
curl -sk https://hubble.example.com -u hubble-admin:yourpassword -o /dev/null -w "%{http_code}\n"

Common issues

SymptomCauseFix
hubble observe returns no flowsCLI not pointed at Relay, or Relay not runningConfirm kubectl -n kube-system get pods -l k8s-app=hubble-relay is Running, then re-export HUBBLE_SERVER
Hubble UI shows empty service maphubble.metrics.enabled missing flow typesRe-run the Helm upgrade with the full metrics list including flow and http
Ingress returns 502 for hubble.example.comCiliumNetworkPolicy blocking ingress controller trafficVerify the restrict-hubble-ui-ingress policy matches the actual ingress-nginx namespace label
Prometheus target for hubble-metrics is downServiceMonitor selector labels do not match the serviceRun kubectl -n kube-system get svc hubble-metrics --show-labels and align the matchLabels
DROPPED verdicts on expected trafficCiliumNetworkPolicy default-deny without an explicit allow ruleAdd an explicit ingress or egress rule for the required label pair, then re-check with hubble observe
Agent restarts loop after enabling HubbleResource limits too low for the added Hubble server processIncrease CPU and memory requests on the cilium DaemonSet via Helm values

Next steps

Running this in production?

Want this handled for you? Running eBPF-based network observability at scale adds a second layer of work: keeping Cilium and Hubble versions in sync across node pools, tuning metric cardinality before it overwhelms Prometheus, and staffing on-call for the alerts this visibility generates. See how we run infrastructure like this for European teams.

Automated install script

Run this to automate the entire setup

Don't want to manage this yourself?

We handle infrastructure for businesses that depend on uptime. Fully managed, with one fixed contact who knows your setup.

You get one fixed contact who knows your setup

At their desk in Rotterdam 12:39 · reachable in a message, no ticket form