kubernetes/kops · error

error dialing tcp %s: %w

Error message

error dialing tcp %s: %w

What it means

Dial wraps the error from net.Dialer.DialContext with this message when the initial TCP connection to host:22 (or bastion:22) fails. The wrapped %w error is the underlying net error (timeout, connection refused, no route to host, DNS failure). The library uses a 5-second dial timeout, so slow/unreachable hosts surface as i/o timeout.

Source

Thrown at pkg/dump/dumper.go:690

func (f *sshClientFactoryImplementation) Dial(ctx context.Context, host string, useBastion bool) (sshClient, error) {
	addr := host
	if useBastion {
		if f.bastion == "" {
			return nil, fmt.Errorf("bastion is not set, but useBastion is true")
		}
		addr = f.bastion
	}

	if addr == "" {
		return nil, fmt.Errorf("host is empty")
	}
	addr = net.JoinHostPort(addr, "22")
	d := net.Dialer{
		Timeout: 5 * time.Second,
	}
	conn, err := d.DialContext(ctx, "tcp", addr)
	if err != nil {
		return nil, fmt.Errorf("error dialing tcp %s: %w", addr, err)
	}

	// We have a TCP connection; we will force-close it to support context cancellation

	var client *ssh.Client
	finished := make(chan error)
	go func() {
		c, chans, reqs, err := ssh.NewClientConn(conn, addr, f.sshConfig)
		if err == nil {
			client = ssh.NewClient(c, chans, reqs)
			if useBastion {
				err = agent.ForwardToAgent(client, f.keyRing)
				if err != nil {
					err = fmt.Errorf("forwarding ssh auth to keyring: %w", err)
				}
			}
		}

View on GitHub (pinned to 4c8573c808)

Solutions

  1. Check network reachability: open port 22 in the security group / firewall for your source IP, or run the dump from within the VPC
  2. Verify the target host/bastion instance is running and its recorded IP/DNS is current (kops get instances / cloud console)
  3. If a bastion is required, confirm it is up and Dial is being called with useBastion=true and a valid bastion address
  4. Inspect the wrapped error in the message to distinguish timeout vs connection refused vs DNS failure and act accordingly

Example fix

// before (host unreachable directly)
client, err := factory.Dial(ctx, privateNodeIP, false)

// after (route via bastion)
client, err := factory.Dial(ctx, privateNodeIP, factory.HasBastion())
Defensive patterns

Strategy: retry

Validate before calling

conn, err := net.DialTimeout("tcp", net.JoinHostPort(host, "22"), 3*time.Second)
if err != nil {
	return fmt.Errorf("host %s unreachable on tcp/22 before dump: %w", host, err)
}
conn.Close()

Type guard

func isDialError(err error) bool { return strings.Contains(err.Error(), "error dialing tcp") }

Try / catch

client, err := factory.Dial(ctx, host, useBastion)
if err != nil {
	var netErr net.Error
	if strings.Contains(err.Error(), "error dialing tcp") && errors.As(err, &netErr) {
		return fmt.Errorf("check security groups allow tcp/22 to %s: %w", host, err)
	}
	return err
}

Prevention

When it happens

Trigger: TCP dial to addr (net.JoinHostPort(host, "22") or bastion:22) fails: security group blocks port 22, host is down/terminated, DNS name unresolvable, 5s timeout exceeded, or wrong IP recorded for the instance.

Common situations: AWS security groups not allowing SSH from the operator's network; dumping a cluster where nodes are in private subnets and neither direct SSH nor the bastion is reachable; bastion instance stopped; stale DNS or IP after node replacement.

Understand the failure class

Background: ECONNREFUSED and "connection refused" / "could not connect to server" errors: what they mean and how to fix them — this error's family across 44 libraries.

Related errors


AI-assisted analysis of kubernetes/kops@4c8573c808 (2026-09-05). Data as JSON: /api/errors/2d964c8fc18b0be0. Report an issue: GitHub.