kubectl times out on a GKE cluster: find the setting that blocks it
dial means your address is not in the authorized
networks of the public endpoint, or your Mac has no route to the private one.
Unable through a bastion or a VPN means that
range is missing from the list. net/http: from a bastion
in another region means global access is off. A gcloud error about
allowExternalTraffic, or an HTML 403 page, means the DNS-based endpoint is closed
to outside traffic. The messages below are what kubectl printed on GKE 1.36 when we turned each setting off
on a test cluster.
By Ostgate · Updated
Find your error
| kubectl or gcloud prints | Where kubectl connects | Setting that blocks it |
|---|---|---|
dial |
The public endpoint | Your public IP is not in authorized networks |
dial |
The private endpoint, from outside the VPC | None: there is no route. Use a path into the VPC |
Unable |
The private endpoint through a bastion or a VPN | The bastion's subnet or the VPN range is not in authorized networks |
net/http: |
The private endpoint through a bastion in another region | Global access on the control plane is off |
…allowExternalTraffic |
get-credentials |
The DNS-based endpoint blocks external traffic |
Error and Error 403 (Forbidden)!!1 |
The DNS-based endpoint, with an older kubeconfig | The same |
The |
Nowhere: get-credentials failed and wrote no entry |
Read the gcloud error above it |
context |
Any, with --request-timeout set |
The i/o timeout, cut short |
A timeout means nothing answered. EOF and the 403 page are refusals: each
answer came back in 31 to 153 ms in our runs.
Read the authorized list before you change it
Three of the fixes below edit the cluster's authorized networks, and
--master- replaces the whole list. Read it first, so
the update can repeat every range:
gcloud container clusters describe acme-gke \
--location=europe-central2-a --project=acme-prod \
--format="value(controlPlaneEndpointsConfig.ipEndpointsConfig.authorizedNetworksConfig.cidrBlocks[].cidrBlock)"
10.80.1.0/24;10.99.0.0/24
If the cluster enforces the list on its private endpoint
(privateEndpointEnforcementEnabled: in the full describe
output), keep --enable-
in every update, as the commands below do.
Unable to connect to the server: dial tcp …:443: i/o timeout
On a cluster with its public endpoint on, and a Mac whose address was not in the list,
kubectl get ns printed five memcache.go lines 30 seconds apart,
then:
Unable to connect to the server: dial tcp 198.51.100.20:443: i/o timeout
It took 150 seconds, and we ran it 83 seconds after the last change to the cluster had
returned, so it was not a change still spreading. With --request-timeout=20s
the same case ends in
context deadline exceeded (Client..
Add your Mac's public IPv4 as a /32. To read it:
dig -4 +short TXT o-o.myaddr.l.google.com @ns1.google.com
Then restate the list with it, here 203.0.113.7:
gcloud container clusters update acme-gke \
--location=europe-central2-a --project=acme-prod \
--enable-master-authorized-networks \
--master-authorized-networks=10.80.1.0/24,10.99.0.0/24,203.0.113.7/32 \
--enable-authorized-networks-on-private-endpoint
kubectl listed namespaces one second after this returned.
The same i/o timeout, also after 150 seconds, appears when the kubeconfig's server is the
private address, for example https://10.80.0.3 from
get-credentials , and your Mac is outside the VPC. No cluster
setting fixes that one: the Mac needs a path into the network.
kubectl to a private GKE cluster from a
Mac covers the DNS-based endpoint, the Connect gateway, a VPN and an IAP bastion.
Unable to connect to the server: EOF
We kept an SSH forward from 127.0.0.1:18443 through a bastion VM to the private
endpoint, and removed the bastion's subnet from authorized networks. Sixteen seconds after
the update returned, kubectl through the forward stopped working:
Unable to connect to the server: EOF
It took 10.66 seconds. With -v=6, kubectl shows ten tries, each one answered in
31 to 106 ms and followed by "Got . With
--request-timeout=10s the message is
error: .
At the TLS layer, openssl
connected and then printed unexpected : the TCP connection
opens and the control plane drops the handshake. A VPN client range missing from the list
fails the same way.
Read the bastion's subnet, then the subnet's range:
gcloud compute instances describe bastion-1 \
--zone=europe-central2-a --project=acme-prod \
--format="value(networkInterfaces[0].subnetwork.basename(),networkInterfaces[0].networkIP)"
gcloud compute networks subnets describe acme-bastion-subnet \
--region=europe-central2 --project=acme-prod \
--format="value(ipCidrRange)"
acme-bastion-subnet 10.80.1.2 10.80.1.0/24
Then put it back in the list with the ranges that were already there:
gcloud container clusters update acme-gke \
--location=europe-central2-a --project=acme-prod \
--enable-master-authorized-networks \
--master-authorized-networks=10.80.1.0/24,10.99.0.0/24 \
--enable-authorized-networks-on-private-endpoint
kubectl through the forward worked six seconds after it returned. For a VPN, the range is
the client range the VPC routes back to the VPN; on our test VPN kubectl worked 47 seconds
after that range was added. Setting up the bastion forward itself, with the
tls-server-name it needs, is in
IAP bastion: ssh -L and
tls-server-name.
net/http: TLS handshake timeout
We tried a bastion in europe-west1-b against a control plane in europe-central2-a, with the
far bastion's subnet 10.81.0.0/24 in authorized networks and global access off.
kubectl failed with:
Unable to connect to the server: net/http: TLS handshake timeout
Turning on global access fixed it, and kubectl listed the pods:
gcloud container clusters update acme-gke \
--location=europe-central2-a --project=acme-prod \
--enable-master-global-access
With global access off, the far bastion got the same message whether its subnet was in the list or not, so after enabling global access, check the list too. A bastion in the same region whose subnet is missing gets EOF instead.
allowExternalTraffic is disabled, and the 403 page
On a cluster whose DNS-based endpoint blocks external traffic, gcloud refuses to write the kubeconfig entry:
ERROR: (gcloud.container.clusters.get-credentials) controlPlaneEndpointsConfig.dnsEndpointConfig.allowExternalTraffic is disabled for cluster acme-gke.
If you run kubectl anyway, with no entry written, it falls back to
The ,
which says nothing about GKE.
A kubeconfig written while the endpoint was open gets an HTML error page instead, printed
five times as memcache.go errors and once more at the end:
Error from server (Forbidden): <!DOCTYPE html> <html lang=en> <meta charset=utf-8> <meta name=viewport content="initial-scale=1, minimum-scale=1, width=device-width"> <title>Error 403 (Forbidden)!!1</title> ... <p><b>403.</b> <ins>That’s an error.</ins> <p> <ins>That’s all we know.</ins>
It came back in 56 to 153 ms each time. An administrator allows external traffic with:
gcloud container clusters update acme-gke \
--location=europe-central2-a --project=acme-prod \
--enable-dns-access
The old kubeconfig worked two seconds after the update returned, and
get-credentials wrote a new entry. If the cluster's owners want
the DNS endpoint closed, a bastion does not help: in our test the endpoint answered with the
same 403 from inside the VPC and through private.. Use
another path instead.
What we tested on
A zonal Standard cluster on v1.36.4-gke.2046003 in europe-central2-a, with
kubectl v1.35.0 and Google Cloud SDK 590.0.0 on a Mac with macOS 27. The i/o timeout, EOF
and 403 cases and their fixes were run on 3 November 2026 with kubectl's default output.
The TLS handshake timeout and the VPN case are from 2 November 2026 on the same cluster.
Questions
Why does kubectl time out with dial tcp ...:443: i/o timeout on GKE?
Nothing answers at that address from where you are. On the public endpoint, your public IP is not in the cluster's authorized networks. On the private address, your machine has no route into the VPC. kubectl retries for about 150 seconds before it prints the error.
Does --master-authorized-networks add a range to the list?
No. It replaces the whole list. Read the current ranges with gcloud and repeat all of them, plus the new one, in the update.
Why does kubectl through a bastion fail with EOF?
The cluster enforces authorized networks on its private endpoint and the bastion's subnet range is not in the list. The TCP connection opens and the control plane drops the TLS handshake. Add the subnet's ipCidrRange to the list.
What does Error 403 (Forbidden)!!1 from kubectl mean on GKE?
kubectl reached the cluster's DNS-based endpoint while it blocks external traffic, and it answered with an HTML 403 page. It answers at once and does not time out. gcloud with --enable-dns-access allows external traffic.
kubectl through an IAP bastion. Ostgate's forward profiles carry the control plane's port through a bastion VM without a terminal; the setup is in kubectl to a private GKE cluster from a Mac.