hashicorp/terraform · error

no suitable TCP ports (between %d and %d) are available for

Error message

no suitable TCP ports (between %d and %d) are available for the temporary OAuth callback server

What it means

Thrown by listenerForCallback when every TCP port in the host-advertised OAuth callback range is busy after ~1.5x the range size of random attempts. The temporary local OAuth callback server (for the authorization-code flow) could not bind any port, so the OAuth login cannot complete.

Source

Thrown at internal/command/login.go:752

	// another.
	maxTries := availCount + (availCount / 2)

	for tries := 0; tries < maxTries; tries++ {
		port := rand.Intn(availCount) + int(minPort)
		addr := fmt.Sprintf("127.0.0.1:%d", port)
		log.Printf("[TRACE] login: trying %s as a listen address for temporary OAuth callback server", addr)
		l, err := net.Listen("tcp4", addr)
		if err == nil {
			// We use a path that doesn't end in a slash here because some
			// OAuth server implementations don't allow callback URLs to
			// end with slashes.
			callbackURL := fmt.Sprintf("http://localhost:%d/login", port)
			log.Printf("[TRACE] login: callback URL will be %s", callbackURL)
			return l, callbackURL, nil
		}
	}

	return nil, "", fmt.Errorf("no suitable TCP ports (between %d and %d) are available for the temporary OAuth callback server", minPort, maxPort)
}

func (c *LoginCommand) proofKey() (key, challenge string, err error) {
	// Wel use a UUID-like string as the "proof key for code exchange" (PKCE)
	// that will eventually authenticate our request to the token endpoint.
	// Standard UUIDs are explicitly not suitable as secrets according to the
	// UUID spec, but our go-uuid just generates totally random number sequences
	// formatted in the conventional UUID syntax, so that concern does not
	// apply here: this is just a 128-bit crypto-random number.
	uu, err := uuid.GenerateUUID()
	if err != nil {
		return "", "", err
	}

	key = fmt.Sprintf("%s.%09d", uu, rand.Intn(999999999))

	h := sha256.New()
	h.Write([]byte(key))

View on GitHub (pinned to c9def3e214)

Solutions

  1. Free up ports in the advertised range: stop other local servers or stale `terraform login` processes.
  2. Retry `terraform login` after closing other OAuth callback listeners.
  3. If self-hosted TFE/Terraform Enterprise, widen the advertised OAuth callback port range in the service description.
  4. As a workaround, use the token-paste flow or write credentials to the credentials file directly.
Defensive patterns

Strategy: retry

Validate before calling

// Before starting the OAuth flow, probe a candidate callback port in the advertised range.
func freePortIn(minPort, maxPort uint16) (uint16, error) {
    for i := 0; i < (int(maxPort)-int(minPort))*3/2; i++ {
        p := uint16(rand.Intn(int(maxPort)-int(minPort))) + minPort
        l, err := net.Listen("tcp4", fmt.Sprintf("127.0.0.1:%d", p))
        if err == nil { l.Close(); return p, nil }
    }
    return 0, errors.New("no free OAuth callback port")
}

Try / catch

l, cb, err := cmd.ListenerForCallback(minPort, maxPort)
if err != nil {
    // No ports free — close stale listeners and retry, or fall back to token-paste flow.
    return err
}

Prevention

When it happens

Trigger: Produced during `terraform login` OAuth authorization-code flow when net.Listen('tcp4', '127.0.0.1:<port>') fails for every port sampled in [minPort, maxPort]. The port range comes from the host's OAuth service description (minPort/maxPort). Triggered when the range is exhausted by other listeners or the range is tiny/empty.

Common situations: Machine has many services occupying the callback port range; a previous crashed `terraform login` left a callback listener bound; the host advertises a very narrow port range; or another OAuth flow is in progress. Most common on busy dev machines or in containers with limited port availability.

Related errors


AI-assisted analysis of hashicorp/terraform@c9def3e214 (2026-08-07). Data as JSON: /api/errors/e8bc88ab211c9c4b. Report an issue: GitHub.