gofiber/fiber · warning

failed to resolve TCP address after adding port

Error message

failed to resolve TCP address after adding port: %w

What it means

Returned by resolveRemoteAddr in the adaptor middleware when net.ResolveTCPAddr fails a second time after the function appended the default port ':80' to a host that was missing a port. This is the fallback path for hostnames or non-literal addresses passed as a remote address to Fiber's HTTP-handler-to-Fiber adaptor (used by net/http-based reverse proxies).

Solutions

  1. Ensure the host portion of RemoteAddr is resolvable from the Fiber process: getent hosts <host> or nslookup <host>.
  2. Fix /etc/resolv.conf or the container's DNS so the resolver is reachable.
  3. If behind a proxy, set Request.RemoteAddr to a literal IP:port rather than a hostname before calling the adaptor.
  4. Strip brackets/spaces from synthesized RemoteAddr values.
  5. For IPv6, pass [ip]:port form so the fast path handles it without the resolver.

Example fix

// before: hostname not resolvable by the backend
r.RemoteAddr = "internal-svc:8080" // DNS only known to the proxy

// after: use a literal IP the backend can resolve, or fix DNS
r.RemoteAddr = net.JoinHostPort(realClientIP, "8080")
Defensive patterns

Strategy: validation

Validate before calling

// Before passing RemoteAddr to the adaptor, normalize it to a literal ip:port.
func normalizeRemoteAddr(raw string) (string, error) {
    host, port, err := net.SplitHostPort(raw)
    if err == nil && port != "" {
        if ip := net.ParseIP(host); ip != nil {
            return net.JoinHostPort(host, port), nil
        }
    }
    // resolve hostname to IP first to avoid the resolver-retry path
    ips, err := net.LookupHost(host)
    if err != nil || len(ips) == 0 {
        return "", fmt.Errorf("cannot resolve %q: %w", host, err)
    }
    if port == "" { port = "80" }
    return net.JoinHostPort(ips[0], port), nil
}

Try / catch

addr, err := resolveRemoteAddr(remote, local)
if err != nil {
    if errors.Is(err, adaptor.ErrRemoteAddrTooLong) || strings.Contains(err.Error(), "failed to resolve TCP address") {
        // fall back to a safe default RemoteAddr so the request still proceeds
        addr = fallbackAddr
    } else {
        return err
    }
}

Prevention

When it happens

Trigger: A reverse proxy (httputil.ReverseProxy) forwards requests to the Fiber app via adaptor.FiberApp or adaptor.HTTPHandler with a Request.RemoteAddr whose host portion is a hostname that does not resolve in DNS, or contains invalid characters, or where the resolver is unavailable. The first ResolveTCPAddr reported 'missing port', the function appended ':80', and the retry still failed.

Common situations: Running behind a proxy that sets X-Forwarded-Host to a hostname the backend cannot resolve (split-horizon DNS, internal-only hostname); container where /etc/resolv.conf points at an unreachable resolver; the remote address was synthesized from a Host header containing brackets or spaces; localhost6/IPv6 literals that defeat the fast path and hit the resolver with a malformed string.

Related errors


AI-assisted analysis of gofiber/fiber@a105acad6c (2026-08-11). Data as JSON: /api/errors/f91f0ab2ab6a79ac. Report an issue: GitHub.

Appendix: source

Thrown at middleware/adaptor/adaptor.go:571

				return newTCPAddr(a[:], port, ip.Zone()), nil
			}
		}
	}

	resolved, err := net.ResolveTCPAddr("tcp", remoteAddr)
	if err == nil {
		return resolved, nil
	}

	var addrErr *net.AddrError
	if errors.As(err, &addrErr) && addrErr != nil && addrErr.Err == "missing port in address" {
		if len(remoteAddr) > 253 { // Max hostname length
			return nil, ErrRemoteAddrTooLong
		}
		remoteAddr = net.JoinHostPort(remoteAddr, "80")
		resolved, err2 := net.ResolveTCPAddr("tcp", remoteAddr)
		if err2 != nil {
			return nil, fmt.Errorf("failed to resolve TCP address after adding port: %w", err2)
		}
		return resolved, nil
	}
	return nil, fmt.Errorf("failed to resolve TCP address: %w", err)
}

func handlerFunc(app *fiber.App, h ...fiber.Handler) http.HandlerFunc {
	// App.Config returns the config by value, so read the body limit once at
	// construction instead of copying the whole 624-byte struct on every
	// request. Fiber only writes app.config in New. The error handler is
	// deliberately not cached: App.ErrorHandler resolves a mounted sub-app's
	// handler from the request path, and that lookup belongs per request.
	maxBodySize := int64(app.Config().BodyLimit)

	return func(w http.ResponseWriter, r *http.Request) {
		// New fasthttp Ctx from pool
		pctx := ctxPool.Get().(*pooledCtx) //nolint:forcetypeassert,errcheck // not needed
		fctx := &pctx.fctx

View on GitHub (pinned to a105acad6c)