KubernetesKubernetes

The connection to the server localhost:8080 was refused

kubectl is talking to a default address because it found no kubeconfig. How to confirm which config and context it is using, and point it at the real cluster.

easy fix6 min read

the kubernetes error
The connection to the server localhost:8080 was refused - did you specify the right host or port?

error: You must be logged in to the server (Unauthorized)

Unable to connect to the server: dial tcp 10.0.0.1:6443: i/o timeout

Do this first3 steps

Run these in order. Each one tells you what its output means before you change anything.

  1. 1

    Check whether kubectl has any config at all

    kubectl config view --minify

    If this prints an empty config or errors, kubectl found no kubeconfig and fell back to its built-in default of localhost:8080. Nothing is listening there, hence "connection refused". This is the cause the large majority of the time.

  2. 2

    See which file kubectl is actually reading

    echo "KUBECONFIG=${KUBECONFIG:-<unset, using ~/.kube/config>}" && ls -l ~/.kube/config

    An unset KUBECONFIG means ~/.kube/config, and if that file does not exist you have found it. A set KUBECONFIG pointing at a path that no longer exists produces exactly the same symptom.

  3. 3

    List contexts and select the one you want

    kubectl config get-contexts && kubectl config use-context my-cluster

    If contexts exist but none is marked current, kubectl has a config and no idea which cluster to use. Selecting one fixes it immediately.

All 9 sections

The connection to the server localhost:8080 was refused almost never means a server is down. It means kubectl could not find a kubeconfig, so it fell back to its compiled-in default of localhost:8080 and nothing is listening there.

The port is the tell. You never configured 8080, and no modern cluster serves on it. Kubernetes API servers listen on 6443. Seeing localhost:8080 in the error is kubectl telling you it is guessing.

Confirm there is no usable config

kubectl config view --minify

If you get this, the diagnosis is done:

error: current-context is not set

or an empty config:

apiVersion: v1
clusters: null
contexts: null
kind: Config
users: null

Then check where kubectl was looking:

echo "${KUBECONFIG:-unset}"
ls -l ~/.kube/config

Three outcomes, each with a different fix:

What you seeWhat it means
KUBECONFIG unset, no ~/.kube/configkubectl has no config at all
KUBECONFIG set to a path that does not existkubectl silently ignores the missing file
Config exists, no current contextkubectl has clusters but no selected one

Fix 1: No kubeconfig at all

Get one from wherever the cluster lives. The command differs per provider, and each of these writes or merges into ~/.kube/config for you:

# EKS
aws eks update-kubeconfig --region eu-west-1 --name production

# GKE
gcloud container clusters get-credentials production --region europe-west1

# AKS
az aks get-credentials --resource-group rg-prod --name production

# kind / minikube, which usually do this automatically
kind export kubeconfig --name dev
minikube update-context

Then confirm:

kubectl config current-context
kubectl get nodes

Fix 2: KUBECONFIG points somewhere wrong

This one is nastier because there is no warning. kubectl does not tell you the file is missing, it just behaves as though you have no config.

echo "$KUBECONFIG"
# /Users/me/.kube/configs/old-cluster.yaml

ls -l "$KUBECONFIG"
# ls: no such file or directory

A stale export in ~/.zshrc or ~/.bashrc is the usual source, often left behind after a cluster was deleted. Either fix the path or unset it:

unset KUBECONFIG

KUBECONFIG also accepts several paths separated by :, merged left to right:

export KUBECONFIG=~/.kube/config:~/.kube/staging.yaml

That is useful, but if any one path is wrong you get partial config and confusing results rather than an error.

Fix 3: Config exists but no context is selected

kubectl config get-contexts
CURRENT   NAME         CLUSTER      AUTHINFO     NAMESPACE
          dev          dev          dev-admin
          production   production   prod-admin

No asterisk in CURRENT means nothing is selected. Pick one:

kubectl config use-context production

When sudo is involved

Running sudo kubectl get pods reads root's home directory, not yours, so it looks for /root/.kube/config and finds nothing. This is a very common cause on a machine where the rest of the container tooling needs sudo.

kubectl does not need root. Drop the sudo.

These three messages look different and have genuinely different causes. It is worth knowing which one you have:

The connection to the server localhost:8080 was refused. No config found. Everything above.

You must be logged in to the server (Unauthorized). Config found, cluster reached, credentials rejected. Usually an expired token or an exec credential plugin that cannot run. Check the users section of your config:

kubectl config view --minify -o jsonpath='{.users[0].user.exec.command}'

If that names aws or gke-gcloud-auth-plugin, the binary must be on your PATH. For EKS specifically, an expired session shows up here and aws sso login or refreshing credentials fixes it.

Unable to connect to the server: dial tcp ...: i/o timeout. Config found, correct address, nothing answered. The API server is genuinely unreachable: a private endpoint that needs a VPN, a security group that does not allow your IP, or a cluster that is actually down. Confirm the address first:

kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}'

Then test reachability independently of kubectl:

curl -k -m 5 https://B1A2C3.gr7.eu-west-1.eks.amazonaws.com/version

A timeout there is a network problem, not a kubectl problem.

In CI

A pipeline hitting localhost:8080 means the kubeconfig was never written into the job. Runners start clean, so a config on your laptop is irrelevant.

# GitLab CI
deploy:
  script:
    - aws eks update-kubeconfig --region eu-west-1 --name production
    - kubectl apply -f k8s/

If you inject the config as a base64 secret instead, write it before the first kubectl call and keep the permissions tight:

mkdir -p ~/.kube
echo "$KUBE_CONFIG_B64" | base64 -d > ~/.kube/config
chmod 600 ~/.kube/config

chmod 600 matters because some credential plugins refuse a world-readable kubeconfig.

A checklist

  1. kubectl config view --minify. Empty or erroring → no config.
  2. echo $KUBECONFIG. Set to a path that does not exist → unset it or fix it.
  3. kubectl config get-contexts. No asterisk → kubectl config use-context <name>.
  4. Using sudo? Stop. It reads /root/.kube/config.
  5. Getting Unauthorized instead → credentials expired, refresh them.
  6. Getting i/o timeout instead → network path to the API server, not kubectl.
  7. In CI, write the kubeconfig inside the job before any kubectl command.

Frequently Asked Questions

Why does kubectl try to connect to localhost:8080?

Because that is its compiled-in default when it finds no kubeconfig. kubectl looks at the KUBECONFIG environment variable first, then ~/.kube/config. If neither yields a usable file it assumes an unsecured API server on the local machine at port 8080, which was how very old clusters worked. Nothing listens there on a modern setup, so the connection is refused. Seeing port 8080 in the message is therefore a reliable signal that the problem is missing configuration rather than a broken cluster.

How do I tell kubectl which kubeconfig to use?

Set the KUBECONFIG environment variable to the file path, or pass --kubeconfig /path/to/config on a single command. KUBECONFIG also accepts several paths separated by colons, merged left to right, which is how people keep one file per cluster. Be careful with that: if one path in the list does not exist, kubectl merges what it can and gives no warning, so you get a partial config and confusing results rather than a clear error.

Why does kubectl work for me but not with sudo?

Because sudo runs as root and root has a different home directory. Your config is at ~/.kube/config, which resolves to /root/.kube/config under sudo, and that file usually does not exist. kubectl does not need root privileges to talk to an API server, so the fix is simply to drop the sudo. If you genuinely need it, pass the path explicitly with sudo kubectl --kubeconfig ~/.kube/config, remembering that ~ will still expand to root's home unless you write the path out in full.

What is the difference between connection refused and i/o timeout?

Refused means something actively rejected the connection, which on localhost:8080 means nothing is listening. Timeout means the packets went out and nothing came back, so the address is plausible but unreachable: a private API endpoint needing a VPN, a security group that does not allow your IP, or a cluster that is down. Check the address kubectl is using with kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}', then test it with curl to separate a network problem from a kubectl problem.

Why does my kubeconfig work locally but fail in CI?

CI runners start from a clean image with no kubeconfig, so anything on your laptop is irrelevant. The job has to obtain credentials itself, either by calling the provider (aws eks update-kubeconfig, gcloud container clusters get-credentials) or by writing a config from a stored secret before the first kubectl command. If you write one from a secret, chmod 600 it: some credential plugins refuse to use a kubeconfig that is readable by other users.

Reference and practice

Learn the underlying concept

Other Kubernetes errors