netbirdio/netbird · error

dial context: %w

Error message

dial context: %w

What it means

CreateConnection failed at grpc.DialContext. Because the dial uses grpc.WithBlock and a 30-second context timeout, the returned error is whatever blocked the connection until the deadline: context.DeadlineExceeded when the server never became reachable, or transport-level failures such as connection refused, TLS x509 verification errors (certificate not trusted by the system pool or the embedded fallback roots), or DNS resolution failures from the custom dialer. The %w wrap preserves the gRPC cause for errors.Is/As inspection.

Source

Thrown at client/grpc/dialer.go:62

	}

	connCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
	defer cancel()

	opts := []grpc.DialOption{
		transportOption,
		WithCustomDialer(tlsEnabled, component),
		grpc.WithBlock(),
		grpc.WithKeepaliveParams(keepalive.ClientParameters{
			Time:    30 * time.Second,
			Timeout: 10 * time.Second,
		}),
	}
	opts = append(opts, extraOpts...)

	conn, err := grpc.DialContext(connCtx, addr, opts...)
	if err != nil {
		return nil, fmt.Errorf("dial context: %w", err)
	}

	return conn, nil
}

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Check the wrapped error class first: errors.Is(err, context.DeadlineExceeded) means unreachable, x509 errors mean trust, connection refused means port/service
  2. Verify the service is listening on the configured address and the URL scheme/port match the deployment (443 vs custom)
  3. Install the CA on the client OS trust store or serve a publicly-trusted certificate so SystemCertPool validates it
  4. Test basic reachability outside the app (openssl s_client, curl, nc) to isolate TLS vs network vs DNS
  5. Retry with backoff for transient outages; the fixed 30s WithBlock window is not tunable by callers

Example fix

// before
conn, err := grpc.CreateConnection(ctx, addr, true, "/management")
if err != nil { return err }

// after
conn, err := grpc.CreateConnection(ctx, addr, true, "/management")
if err != nil {
    if errors.Is(err, context.DeadlineExceeded) {
        return fmt.Errorf("management unreachable at %s: %w", addr, err)
    }
    return fmt.Errorf("connect management: %w", err)
}
Defensive patterns

Strategy: retry

Validate before calling

// preflight outside the app before dialing
// nc -vz host 443  /  openssl s_client -connect host:443
u, err := url.Parse(addr)
if err != nil || u.Host == "" {
    return fmt.Errorf("invalid service address %q", addr)
}

Type guard

func reachableAddr(addr string) bool {
    u, err := url.Parse(addr)
    return err == nil && u.Host != "" && u.Port() != ""
}

Try / catch

var conn *grpc.ClientConn
err := backoff.Retry(func() error {
    c, e := grpc.CreateConnection(ctx, addr, tlsEnabled, "/management")
    if e != nil {
        if errors.Is(e, context.DeadlineExceeded) || isTemporary(e) {
            return e // retry with backoff
        }
        return backoff.Permanent(e) // TLS trust / config: fix, don't retry
    }
    conn = c
    return nil
}, grpc.Backoff(ctx))

Prevention

When it happens

Trigger: Management or signal service down or unreachable at addr; wrong host/port in the configuration; TLS enabled against a server whose certificate chain is not in SystemCertPool nor the embedded roots; a custom dialer/proxy path (WebSocket component like /management or /signal) failing; network offline or DNS broken so WithBlock exhausts the 30s window.

Common situations: Self-hosted NetBird with a self-signed or privately-signed CA not installed on the client host; typo'd management URL; firewall blocking the gRPC port; the 30s dial timeout being too short on high-latency or proxy-chained networks; system cert pool unavailable on stripped-down containers causing fallback roots that do not trust the server.

Related errors


AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16). Data as JSON: /api/errors/0918b704e78812a5. Report an issue: GitHub.