{"record":{"id":"b0e186d912f26566","repo":"Budibase/budibase","slug":"failed-to-connect-to-resolved-ip-for-hostname","errorCode":null,"errorMessage":"Failed to connect to resolved IP for ${hostname}: unknown network error","messagePattern":"Failed to connect to resolved IP for (.+?): unknown network error","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/backend-core/src/utils/outboundFetch.ts","lineNumber":212,"sourceCode":"\n  for (let redirects = 0; redirects <= MAX_REDIRECTS; redirects++) {\n    const pinnedIp = await resolveSafePinnedIp(nextUrl)\n    let response: TResponse\n    try {\n      response = await fetchFn(\n        nextUrl,\n        {\n          ...nextRequest,\n          agent: makePinnedAgent(nextUrl, pinnedIp),\n        },\n        pinnedIp\n      )\n    } catch (error) {\n      const hostname = parseUrl(nextUrl).hostname\n      if (error instanceof Error) {\n        error.message = `Failed to connect to resolved IP for ${hostname}: ${error.message}`\n        throw error\n      }\n      throw new Error(\n        `Failed to connect to resolved IP for ${hostname}: unknown network error`\n      )\n    }\n    if (!isRedirect(response.status)) {\n      return response\n    }\n\n    releaseResponseBody(response)\n\n    if (!followRedirects) {\n      throw new Error(\"Redirects are not permitted.\")\n    }\n\n    if (redirects === MAX_REDIRECTS) {\n      break\n    }\n","sourceCodeStart":194,"sourceCodeEnd":230,"githubUrl":"https://github.com/Budibase/budibase/blob/a81a902e9a8fe55b467d106765f6638f12e35c49/packages/backend-core/src/utils/outboundFetch.ts#L194-L230","documentation":"fetchWithBlacklist pins the connection to the first DNS-resolved IP (to prevent DNS rebinding). When the underlying fetchFn call to that pinned IP throws, this catch block wraps the error with the hostname being contacted. The 'unknown network error' variant occurs when the thrown value is not an Error instance.","triggerScenarios":"The TCP/TLS connection to the resolved IP fails: connection refused, timeout, TLS certificate mismatch (because SNI/hostname still applies but IP differs), or fetchFn rejects with a non-Error value (e.g. a string or undefined thrown by a custom fetchFn).","commonSituations":"Target service is down or firewalled despite resolving; corporate proxies blocking direct IP connections; custom fetchFn implementations that throw strings; stale DNS pointing at a decommissioned IP.","solutions":["Read the wrapped original message after the colon — it identifies the real cause (ECONNREFUSED, ETIMEDOUT, CERT_HAS_EXPIRED, etc.).","Verify the resolved IP is reachable: `curl -v https://<hostname>` or telnet to IP:port from the same host.","Check whether a proxy is required (HTTPS_PROXY) and whether direct IP egress is allowed by the firewall.","If using a custom fetchFn, ensure it only throws Error instances so the original message is preserved."],"exampleFix":"// before (custom fetchFn throwing a string)\nthrow \"connection failed\"\n// after\nthrow new Error(\"connection failed\")","handlingStrategy":"retry","validationCode":"// Pre-flight reachability check before the guarded fetch\nconst parsed = new URL(url)\nconst { address } = await dns.promises.lookup(parsed.hostname)\nconst port = parsed.port || (parsed.protocol === \"https:\" ? 443 : 80)\nawait new Promise((resolve, reject) => {\n  const s = net.connect(Number(port), address)\n  s.setTimeout(5000)\n  s.once(\"connect\", () => { s.destroy(); resolve(null) })\n  s.once(\"error\" as symbol, reject)\n  s.once(\"timeout\", () => { s.destroy(); reject(new Error(\"timeout\")) })\n})","typeGuard":null,"tryCatchPattern":"try {\n  return await fetchWithBlacklist(url, req, { fetchFn })\n} catch (err) {\n  const m = err instanceof Error ? err.message : String(err)\n  if (m.startsWith(\"Failed to connect to resolved IP\")) {\n    const cause = m.split(\": \").slice(2).join(\": \") // e.g. ECONNREFUSED\n    if (/ETIMEDOUT|ECONNRESET/.test(cause)) return withRetry(url, req)\n    throw new Error(`Upstream unreachable (${cause}) for ${new URL(url).hostname}`)\n  }\n  throw err\n}","preventionTips":["Always throw Error instances from custom fetchFn so root causes aren't lost.","Add health-check probes for critical upstream endpoints.","Configure sane timeouts on agents; verify firewall/proxy egress rules."],"tags":["network","connection","dns-pinning","fetch"],"backgroundTag":"connection-refused","analyzedSha":"a81a902e9a8fe55b467d106765f6638f12e35c49","analyzedAt":"2026-08-29T01:03:10.972Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}