kubectl port-forward with Pods, Deployments and Services

Tested on Rocky Linux 10.2 (Red Quartz) workstation
Package kubectl 1.36.3
Applies to Any host with kubectl configured; Kubernetes 1.27+ cluster with Linux worker nodes
Cert prep CKAD · CKA
Lab environment Multi-node kubeadm cluster with containerd — install Kubernetes with kubeadm
Privilege Normal user (no sudo required on the workstation)
Optional dependency curl is required for the local HTTP request examples
Scope kubectl port-forward syntax; Pod, Deployment, and Service targets; named and multiple ports; random local ports; namespace flag; --address; background patterns; common errors; and when to use port-forward versus Service exposure. Does not cover SSH tunnelling, Ingress installation, LoadBalancer configuration, VPN access, or production tunnel design.

kubectl port-forward opens a temporary tunnel from your workstation to one Pod in the cluster. Use it when you need local access to an application for debugging or a quick check without creating NodePort, LoadBalancer, or Ingress resources. Local HTTP checks in this guide use the curl command against 127.0.0.1 on the forwarded port.

Each foreground command below represents a separate port-forward session. After testing a section, return to the port-forward terminal and press Ctrl+C before starting the next command that reuses local port 8080. kubectl port-forward remains attached to the terminal until it is stopped, and one running process owns the local listening port.


Quick Syntax and Lab Setup

The general form is:

bash
kubectl port-forward TYPE/NAME [LOCAL_PORT:]REMOTE_PORT

kubectl port-forward supports TCP forwarding only. It cannot be used to test a UDP-only application or Service port.

Common targets:

bash
kubectl port-forward pod/POD_NAME 8080:80
bash
kubectl port-forward deployment/web 8080:80
bash
kubectl port-forward service/web 8080:80

Keep these behaviours in mind:

  • The command stays attached to your terminal until you stop it with Ctrl+C.
  • Traffic is forwarded to one selected Pod, not load-balanced across replicas.
  • The session ends when that Pod terminates or you cancel the command.
  • Port-forward is temporary local access, not permanent cluster exposure.

Save the following as pf-lab.yaml:

yaml
apiVersion: v1
kind: Namespace
metadata:
  name: pf-lab
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  namespace: pf-lab
spec:
  replicas: 2
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
      - name: app
        image: nginx:1.27-alpine
        command: ["/bin/sh", "-c"]
        args:
        - |
          echo "server $(hostname)" > /usr/share/nginx/html/index.html
          exec nginx -g "daemon off;"
        ports:
        - name: web
          containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: web
  namespace: pf-lab
spec:
  selector:
    app: web
  ports:
  - name: http
    port: 80
    targetPort: web

Apply the manifest and wait until the rollout completes:

bash
kubectl apply -f pf-lab.yaml
bash
kubectl rollout status deployment/web -n pf-lab --timeout=120s

Sample output:

output
deployment "web" successfully rolled out

Confirm Pods, the Deployment, and the Service before you forward ports:

bash
kubectl get pods -n pf-lab -o wide

Sample output:

output
NAME                  READY   STATUS    RESTARTS   AGE   IP             NODE
web-b86fbf69c-kkh9s   1/1     Running   0          10s   192.168.5.59   worker01
web-b86fbf69c-mjfwq   1/1     Running   0          10s   192.168.5.34   worker01
bash
kubectl get deployment -n pf-lab web

Sample output:

output
NAME   READY   UP-TO-DATE   AVAILABLE   AGE
web    2/2     2            2           12s
bash
kubectl get service -n pf-lab web

Sample output:

output
NAME   TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)   AGE
web    ClusterIP   10.101.13.3   <none>        80/TCP    12s

Store an actual matching Pod for Pod-level examples:

bash
POD=$(kubectl get pods -n pf-lab -l app=web -o jsonpath='{.items[0].metadata.name}')
bash
printf 'Selected Pod: %s\n' "$POD"

Sample output:

output
Selected Pod: web-b86fbf69c-kkh9s

Forward to Kubernetes Resources

Forward to a Pod

Map local port 8080 to TCP port 80 in the selected Pod:

bash
kubectl port-forward -n pf-lab "pod/$POD" 8080:80

Sample output:

output
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80

Leave that terminal attached. In a second terminal on the same workstation, send a request to the local port:

bash
curl http://127.0.0.1:8080

Sample output:

output
server web-b86fbf69c-kkh9s

8080 is the port on your workstation. 80 is a TCP port in the selected Pod's network namespace. The Pod must be Running, and an application must be listening on that remote port. The port does not need to be declared under containerPort, but declaring it documents the listener for other tooling.

Forward to a Deployment

Forward through the Deployment name instead of a single Pod:

bash
kubectl port-forward -n pf-lab deployment/web 8080:80

Sample output:

output
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80

kubectl selects one Pod that belongs to the Deployment and keeps forwarding to it for the session. It does not round-robin across replicas.

Run two requests through the same session:

bash
curl http://127.0.0.1:8080

Sample output:

output
server web-b86fbf69c-mjfwq
bash
curl http://127.0.0.1:8080

Sample output:

output
server web-b86fbf69c-mjfwq

Both responses show the same Pod hostname because one Pod was selected for the entire session. If that Pod terminates during a rollout, stop the command with Ctrl+C and run kubectl port-forward again.

Forward to a Service

Forward using the Service name when you want the remote port to be the Service port:

bash
kubectl port-forward -n pf-lab service/web 8080:80

Sample output:

output
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80
bash
curl http://127.0.0.1:8080

Sample output:

output
server web-b86fbf69c-mjfwq

The remote side uses Service port 80. kubectl selects one backing Pod and forwards to its targetPort. This is a convenient local tunnel, not a full test of kube-proxy, NodePort, LoadBalancer, or Ingress routing.

kubectl port-forward service/... requires Kubernetes to resolve the Service to an authorized Pod. A Service without a selector cannot be port-forwarded this way, even when manually managed EndpointSlices point to other endpoints, because the API server does not proxy port-forward traffic to endpoints that are not mapped to Pods.

For Service port fields and EndpointSlices, see Kubernetes Services. When in-cluster Service routing fails but port-forward works, continue with troubleshoot a Service that is not working.

Use a Named Service Port

When a Service exposes multiple ports, a port name avoids ambiguity. The web Service defines port name http:

yaml
ports:
- name: http
  port: 80
  targetPort: web

Forward using the Service port name:

bash
kubectl port-forward -n pf-lab service/web 8080:http

Sample output:

output
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80

Named ports are especially useful when several Service ports map to different container ports.


Configure Port Mappings

Forward Multiple Ports

One session can forward several local-to-remote mappings to the same selected Pod. The syntax accepts multiple [LOCAL_PORT:]REMOTE_PORT arguments in the same session:

bash
kubectl port-forward -n pf-lab "pod/$POD" 8080:80 8443:80

Sample output:

output
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80
Forwarding from 127.0.0.1:8443 -> 80
Forwarding from [::1]:8443 -> 80

IPv6 lines appear only when the workstation can bind the IPv6 loopback address. Both local ports reach port 80 on the same Pod in this example. Replace 8443:80 with distinct remote ports when the application listens on multiple TCP ports.

Choose a Random Local Port

Omit the local port to let kubectl pick a free one:

bash
kubectl port-forward -n pf-lab "pod/$POD" :80

Sample output:

output
Forwarding from 127.0.0.1:46585 -> 80
Forwarding from [::1]:46585 -> 80

Use the assigned port in your client:

bash
curl http://127.0.0.1:46585

Sample output:

output
server web-b86fbf69c-kkh9s

This helps when a fixed local port such as 8080 is already in use.


Control Namespace and Listening Address

Add -n when the resource lives outside your current context namespace:

bash
kubectl port-forward -n pf-lab service/web 8080:80

You can also set a default namespace on the context instead of repeating -n. Namespace basics are covered in Kubernetes namespaces.

The default is --address=localhost. kubectl attempts to bind both 127.0.0.1 and ::1 and succeeds when at least one loopback address can be bound. Depending on workstation IPv6 support, the output may show one or both listeners.

Bind all local interfaces when another host on your network must reach the forwarded port:

bash
kubectl port-forward -n pf-lab --address=0.0.0.0 service/web 8080:80

Sample output:

output
Forwarding from 0.0.0.0:8080 -> 80

--address=0.0.0.0 allows remote hosts to connect when workstation networking and firewalls permit it. Use it deliberately because the forwarded application is no longer limited to your machine. You can also pass a specific IP or localhost.


Run Port-Forward in the Background

Port-forward holds the terminal open. Practical patterns:

  • Open a dedicated terminal tab or window for the forward session.
  • Run in the background with & and record the shell job or process ID.
  • Redirect output to a log file when you need to capture the assigned port.
  • Stop the process with Ctrl+C or kill when you finish testing.

Start the forward and wait until the listener is active:

bash
kubectl port-forward -n pf-lab deployment/web 8080:80 >/tmp/pf-web.log 2>&1 &
bash
PF_PID=$!

FORWARD_READY=false

for attempt in {1..30}; do
  if grep -q '^Forwarding from' /tmp/pf-web.log; then
    FORWARD_READY=true
    break
  fi

  if ! kill -0 "$PF_PID" 2>/dev/null; then
    cat /tmp/pf-web.log >&2
    exit 1
  fi

  sleep 1
done

if [[ "$FORWARD_READY" != "true" ]]; then
  echo "Timed out waiting for port-forward to start" >&2
  exit 1
fi

printf 'port-forward PID=%s\n' "$PF_PID"
cat /tmp/pf-web.log

Sample output:

output
port-forward PID=505717
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80

Test the tunnel:

bash
curl http://127.0.0.1:8080

Sample output:

output
server web-b86fbf69c-mjfwq

Stop the background process when you finish:

bash
kill "$PF_PID"

This prevents a race where curl runs before the listener is active.


Troubleshoot Common Errors

Symptom Likely cause Fix
address already in use Local port taken by another process Choose another local port, stop the previous port-forward with Ctrl+C, or use :REMOTE_PORT for a random local port
Pod is not Running Workload still starting or failed Wait for Ready Pods or troubleshoot the Deployment
connection refused after the tunnel accepts a connection No application is listening on the selected Pod port, or the Service port resolves to the wrong targetPort For Pod or Deployment forwarding, confirm the application listener and numeric remote port. For Service forwarding, inspect both port and targetPort
Forwarding stops after rollout Selected Pod was replaced Run kubectl port-forward again
Forbidden on port-forward RBAC denies pods/portforward Grant port-forward permission on the target Pod or namespace
Browser works via port-forward but not via Service Port-forward bypasses normal Service path Troubleshoot Service routing separately

When the container image lacks tools, attach a debug container with kubectl debug Pods.


When to Use Port-Forward

Requirement Appropriate?
Temporary local testing Yes
Access a cluster-only dashboard Yes
Debug one Pod Yes
Expose an application permanently No — use Service, Ingress, or LoadBalancer
Test NodePort or LoadBalancer routing No
Validate Ingress host or path rules No — see expose services with Ingress

What's Next


References

Summary

You used kubectl port-forward to reach a web application in pf-lab through Pod, Deployment, and Service targets. The syntax LOCAL_PORT:REMOTE_PORT maps a workstation port to a TCP port in one selected Pod while kubectl keeps a tunnel open for the session.

The main distinction is what the remote port means. Pod and Deployment forwarding target a TCP port in the selected Pod. Service forwarding uses the Service port and resolves to one Pod's targetPort. Deployment and Service forwarding still attach to one Pod for the whole session, so repeated curls show the same hostname until you restart the command. Port-forward is ideal for quick local checks; it does not replace Service exposure or prove that NodePort, LoadBalancer, or Ingress paths work.

When port-forward succeeds but normal Service access fails, you have confirmed that one selected Pod is listening on the requested port through the API-server and kubelet forwarding path. Continue checking Service selectors, EndpointSlices, Service proxying, CNI or NetworkPolicy behaviour, and DNS as applicable. Port-forward may bypass network-level paths that ordinary application traffic uses. Stop the forward session when you finish testing so the local port is released for other tools.


Frequently Asked Questions

1. Does kubectl port-forward load balance across all Pod replicas?

No. kubectl selects one Pod that matches the resource you name and keeps forwarding to that Pod for the session. Repeating a request through the same port-forward session normally hits the same Pod until the session ends or the Pod terminates.

2. What is the difference between forwarding to a Service and forwarding to a Pod?

When forwarding to a Service, the remote value identifies the Service port by number or name, and kubectl resolves it to that port targetPort on one selected Pod. When forwarding to a Pod or Deployment, the remote value is a TCP port in the selected Pod. It does not have to be declared under containerPort, but an application must be listening on it.

3. Why does port-forward stop working after a rollout?

The command attaches to one Pod. When that Pod is replaced during a rollout, the forwarding session ends. Run kubectl port-forward again to attach to a new Pod.

4. Can I use port-forward instead of creating a NodePort Service?

Port-forward is for temporary local access from a workstation with kubectl configured. It does not replace NodePort, LoadBalancer, or Ingress for permanent or shared external exposure.

5. What does LOCAL_PORT:REMOTE_PORT mean in kubectl port-forward?

LOCAL_PORT is the port on your workstation where you connect, such as 8080. REMOTE_PORT is the port reached inside the selected Pod, or the Service port when the resource type is service.

6. Why do I get connection refused during port-forward?

The local tunnel may be up while nothing listens on the remote port, the Pod is not Running, or the Service targetPort does not match the application listener. Confirm Pod phase, container port, and Service port mapping before changing local ports.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)