Mastering Port Forwarding as a Service: Running Kubernetes Port Forwards with systemd

DevOps & Cloud Engineer — building scalable, automated, and intelligent systems. Developer of sorts | Automator | Innovator
Search for a command to run...

DevOps & Cloud Engineer — building scalable, automated, and intelligent systems. Developer of sorts | Automator | Innovator
No comments yet. Be the first to comment.
Artificial Intelligence has become one of the biggest talking points in software engineering. Every week, a new tool promises to automate deployments, fix production incidents, or replace developers a

You spin up a Kubernetes workload. You connect to PostgreSQL. You add sslmode=require because security matters. And then PostgreSQL replies with: server does not support SSL, but SSL was required Wai

When you first approach Tencent Cloud, it feels familiar if you come from AWS or Azure. There are VPCs, Kubernetes clusters, load balancers, object storage, IAM, everything you would expect. But once

When I first automated Docker builds and deployments, I thought the hard part would be writing the YAML. It was not. The real challenges were versioning, preventing accidental rollbacks, handling envi

For a long time, Docker Compose felt like the perfect solution. Simple YAML, fast local setup, predictable behavior. For a single service or even a small stack, it works beautifully. But at some point, reality catches up. As the application grew, tra...

CodeOps Studies
40 posts
Simple write-ups on day to day code or devops experiments, tests etc.
Kubernetes port forwarding is extremely useful for quick access to internal services. It allows local tools to reach cluster applications without exposing them through Ingress or LoadBalancer services. The problem is that port forwarding breaks easily. It stops when the terminal closes, when the network changes, or when the forwarding process restarts. Many engineers use it only during development because of these limitations.
The good news is that port forwarding can be turned into a persistent background service that runs automatically on boot and restarts on failure. This can be achieved by running kubectl port-forward under systemd. Once configured, the forwarding behaves like any other system-level service.
The goal was to expose a PostgreSQL instance running inside your Kubernetes cluster so that it could be accessed securely from another machine. Instead of exposing the database publicly or creating a LoadBalancer, you used a port-forward running as a persistent systemd service. This behaves very much like an SSH tunnel, but managed automatically by Kubernetes.
Accessing a PostgreSQL database that lives inside a Kubernetes cluster often starts with a quick command:
kubectl port-forward pod/postgres 15432:5432 -n postgresql
It works.
It is simple.
And it also breaks the moment:
your SSH session closes
the terminal dies
the pod restarts
network hiccups occur
For development or for connecting external applications, this becomes frustrating. You want PostgreSQL running inside the cluster to feel like it is running locally. Always reachable. No interruptions.
This is exactly where a persistent port-forwarding setup becomes extremely useful.
In this blog, I explain how I automated PostgreSQL port-forwarding using:
a simple shell script
a systemd service
dynamic pod detection
persistent reconnection
This ensures that even if the pod restarts or the port-forward crashes, the system automatically brings it back up.
Port-forwarding is not designed for long-term, production-grade networking. It is a debugging convenience.
When you run:
kubectl port-forward pod/postgres 15432:5432
You are telling Kubernetes:
Open 15432 on your local machine
Forward all traffic from this local port
To port 5432 inside the postgres pod
The moment the terminal stops, or the pod is replaced by a new one during a deployment or restart, the connection is lost.
This leads to:
connection refused
ECONNRESET
your app cannot connect to the database
your scripts or migrations failing mid-run
The ideal fix is using a LoadBalancer, NodePort, an internal service mesh, or VPN into the cluster.
But when that is not possible (for example in a locked-down internal environment), persistent port-forwarding is a surprisingly useful workaround.
We will set up:
A shell script that:
Continuously finds the current PostgreSQL pod
Establishes the port-forward
Automatically retries if the pod changes or the forward crashes
A systemd service that:
Runs this script in the background
Starts automatically on boot
Restarts on failure
This results in a self-healing port-forward that always stays alive.
Save it as:
/home/ubuntu/pg-portforward.sh
#!/bin/bash
set -e
export KUBECONFIG="${HOME}/.k0s/kubeconfig"
NAMESPACE="postgresql"
LOCAL_PORT=15432
REMOTE_PORT=5432
while true; do
POD=$(kubectl get pod -n $NAMESPACE -l app=postgres \
-o jsonpath='{.items[0].metadata.name}' 2>/dev/null)
if [ -z "$POD" ]; then
echo "[$(date)] Postgres pod not found. Retrying in 5s..."
sleep 5
continue
fi
echo "[$(date)] Forwarding to pod: $POD"
kubectl -n $NAMESPACE port-forward pod/$POD ${LOCAL_PORT}:${REMOTE_PORT}
echo "[$(date)] Port-forward crashed. Restarting in 5s..."
sleep 5
done
Make it executable:
chmod +x pg-portforward.sh
Create:
/etc/systemd/system/pg-portforward.service
[Unit]
Description=Persistent port-forward for PostgreSQL pod
After=network.target
[Service]
User=ubuntu
Environment="KUBECONFIG=/home/ubuntu/.k0s/kubeconfig"
Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
ExecStart=/home/ubuntu/pg-portforward.sh
Restart=always
RestartSec=5
KillMode=process
[Install]
WantedBy=multi-user.target
Enable and start it:
sudo systemctl daemon-reload
sudo systemctl enable pg-portforward
sudo systemctl start pg-portforward
sudo systemctl status pg-portforward
PostgreSQL pods often restart during:
upgrades
node draining
scaling events
A static pod name does not work.
The script uses a label selector:
-l app=postgres
You can substitute whatever label your Helm chart applies.
If the port-forward dies (common), the script simply loops and starts again.
If the script itself fails, systemd restarts it.
If the machine reboots, the service auto-starts.
This ensures PostgreSQL inside Kubernetes remains reachable on localhost:5432 at all times.
Port-forwarding dies when:
the pod restarts
the connection breaks
kubectl crashes
the terminal closes
To keep the port-forward alive forever, you wrapped it in:
a small bash script that loops forever, automatically reconnecting
a systemd service that starts on boot and restarts on failure
This means your Postgres database is always reachable through that forward without babysitting the terminal.
From your local machine:
psql -h <your-node-ip> -p 15432 -U postgres -d yourdb
If SSH-tunneling:
ssh -L 5432:localhost:15432 ubuntu@<server>
psql -h localhost -p 5432
If port-forward is running correctly, the connection will be instant.
In DBBeaver:


systemd does not automatically inherit your PATH.
This is why we added:
Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
Your kubeconfig was located here:
/home/ubuntu/.k0s/kubeconfig
So we exported it in both the script and the service.
The script automatically handles this.
You avoided exposing Postgres publicly. Only users who can SSH into the server can reach the database. No load balancers. No ingress. No nodePort. Just a controlled, encrypted tunnel.
Port forwarding is usually thought of as a temporary debugging feature, but with a small wrapper script and a systemd unit, it becomes a powerful mechanism for stable access to internal services. For PostgreSQL in particular, this method delivers reliable availability without relaxing cluster security.
This setup is easy to maintain, self-healing, and ideal for environments where PostgreSQL must be reachable consistently through a trusted machine.