hashicorp/terraform · error
no suitable TCP ports
Error message
no suitable TCP ports (between %d and %d) are available for the temporary OAuth callback server
What it means
Thrown by `LoginCommand.listenerForCallback` after exhausting `maxTries` (150% of the port range size) random attempts to `net.Listen("tcp4", "127.0.0.1:<port>")` across the `[minPort, maxPort]` window advertised by the server's OAuth client config. The temporary local HTTP server is needed to receive the OAuth authorization-code redirect. This is a local-resource exhaustion error, not a remote-server error.
Solutions
- Free loopback ports: stop other local servers / Docker containers / competing `terraform login` processes, then retry.
- Raise the OS ephemeral port range (Linux): `sysctl -w net.ipv4.ip_local_port_range="10000 65000"` so more candidates fall inside the advertised window.
- Allow the process through endpoint/firewall software that may be blocking loopback TCP binds.
- Retry on a different machine or runner where fewer ports are occupied.
- If controlling the TFE server, widen the advertised `min_port`/`max_port` in the OAuth client config.
Example fix
# Linux: widen ephemeral port range sudo sysctl -w net.ipv4.ip_local_port_range="10000 65000" # then retry terraform login app.terraform.io
Defensive patterns
Strategy: validation
Validate before calling
// Check that at least one port in a candidate range is bindable before login.
func freePortAvailable(min, max uint16) bool {
for i := 0; i < 50; i++ {
p := int(min) + rand.Intn(int(max)-int(min))
l, err := net.Listen("tcp4", fmt.Sprintf("127.0.0.1:%d", p))
if err == nil { l.Close(); return true }
}
return false
} Type guard
null
Try / catch
// net.Listen errors are not retried inside listenerForCallback beyond maxTries; // callers cannot retry meaningfully. Surface the error and advise freeing ports.
Prevention
- Keep the OS ephemeral port range wide on dev/CI machines.
- Avoid running many concurrent `terraform login` / local servers.
- Allow loopback TCP binds in endpoint-security software.
When it happens
Trigger: Every randomly chosen port in the range returned a non-nil error from `net.Listen`. The range comes from `clientConfig.MinPort`/`clientConfig.MaxPort` discovered via the host's OAuth service descriptor. The loop runs `availCount + availCount/2` times before giving up.
Common situations: A development machine with many local services (Docker, other IDEs, preview servers) consuming ephemeral ports; a CI runner with a restricted or crowded port range; security software (endpoint agents) holding or blocking loopback binds; Linux with a narrow `net.ipv4.ip_local_port_range` that excludes the advertised window; another `terraform login` already in flight holding a port.
Related errors
- Failed to retrieve user account details
- a network issue prevented cloud configuration;
- a network issue prevented cloud configuration;
- bucket not exists
- couldn't read information for cloud run
AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11).
Data as JSON: /api/errors/e8bc88ab211c9c4b.
Report an issue: GitHub.
Appendix: 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 d32a084675)