| 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:
kubectl port-forward TYPE/NAME [LOCAL_PORT:]REMOTE_PORTkubectl port-forward supports TCP forwarding only. It cannot be used to test a UDP-only application or Service port.
Common targets:
kubectl port-forward pod/POD_NAME 8080:80kubectl port-forward deployment/web 8080:80kubectl port-forward service/web 8080:80Keep 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:
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: webApply the manifest and wait until the rollout completes:
kubectl apply -f pf-lab.yamlkubectl rollout status deployment/web -n pf-lab --timeout=120sSample output:
deployment "web" successfully rolled outConfirm Pods, the Deployment, and the Service before you forward ports:
kubectl get pods -n pf-lab -o wideSample 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 worker01kubectl get deployment -n pf-lab webSample output:
NAME READY UP-TO-DATE AVAILABLE AGE
web 2/2 2 2 12skubectl get service -n pf-lab webSample output:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
web ClusterIP 10.101.13.3 <none> 80/TCP 12sStore an actual matching Pod for Pod-level examples:
POD=$(kubectl get pods -n pf-lab -l app=web -o jsonpath='{.items[0].metadata.name}')printf 'Selected Pod: %s\n' "$POD"Sample output:
Selected Pod: web-b86fbf69c-kkh9sForward to Kubernetes Resources
Forward to a Pod
Map local port 8080 to TCP port 80 in the selected Pod:
kubectl port-forward -n pf-lab "pod/$POD" 8080:80Sample output:
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80Leave that terminal attached. In a second terminal on the same workstation, send a request to the local port:
curl http://127.0.0.1:8080Sample output:
server web-b86fbf69c-kkh9s8080 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:
kubectl port-forward -n pf-lab deployment/web 8080:80Sample output:
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80kubectl 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:
curl http://127.0.0.1:8080Sample output:
server web-b86fbf69c-mjfwqcurl http://127.0.0.1:8080Sample output:
server web-b86fbf69c-mjfwqBoth 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:
kubectl port-forward -n pf-lab service/web 8080:80Sample output:
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80curl http://127.0.0.1:8080Sample output:
server web-b86fbf69c-mjfwqThe 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:
ports:
- name: http
port: 80
targetPort: webForward using the Service port name:
kubectl port-forward -n pf-lab service/web 8080:httpSample output:
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80Named 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:
kubectl port-forward -n pf-lab "pod/$POD" 8080:80 8443:80Sample 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 -> 80IPv6 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:
kubectl port-forward -n pf-lab "pod/$POD" :80Sample output:
Forwarding from 127.0.0.1:46585 -> 80
Forwarding from [::1]:46585 -> 80Use the assigned port in your client:
curl http://127.0.0.1:46585Sample output:
server web-b86fbf69c-kkh9sThis 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:
kubectl port-forward -n pf-lab service/web 8080:80You 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:
kubectl port-forward -n pf-lab --address=0.0.0.0 service/web 8080:80Sample 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+Corkillwhen you finish testing.
Start the forward and wait until the listener is active:
kubectl port-forward -n pf-lab deployment/web 8080:80 >/tmp/pf-web.log 2>&1 &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.logSample output:
port-forward PID=505717
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80Test the tunnel:
curl http://127.0.0.1:8080Sample output:
server web-b86fbf69c-mjfwqStop the background process when you finish:
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
- Fix FailedCreatePodSandBox and Kubernetes CNI Errors
- Monitor Kubernetes Pods and Nodes with kubectl top
- kubectl logs, Events and describe with Examples
References
- Kubernetes documentation — Use Port Forwarding to Access Applications in a Cluster
- kubectl reference — port-forward
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.

