payloadcms/payload · warning · Error

Failed to fetch from ${stringifiedUrl}, ${error.message}

Error message

Failed to fetch from ${stringifiedUrl}, ${error.message}

What it means

`safeFetch`'s fallback branch catches any fetch error that is **not** an SSRF `unsafe` block — network failures, DNS resolution errors (NXDOMAIN), TLS/cert errors, connection refused/reset, timeouts — and re-throws a plain `Error` `Failed to fetch from <stringifiedUrl>, <original message>`. The original cause is preserved in the message text (not in `error.cause`).

Source

Thrown at packages/payload/src/uploads/safeFetch.ts:112

      redirect: 'manual', // Prevent automatic redirects
    })) as unknown as Response
  } catch (error) {
    if (error instanceof Error) {
      if (error.cause instanceof Error && error.cause.message.includes('unsafe')) {
        // Errors thrown from within interceptors always have 'fetch error' as the message
        // The desired message we want to bubble up is in the cause
        throw new Error(error.cause.message)
      } else {
        let stringifiedUrl: string | undefined = undefined
        if (typeof unverifiedUrl === 'string') {
          stringifiedUrl = unverifiedUrl
        } else if (unverifiedUrl instanceof URL) {
          stringifiedUrl = unverifiedUrl.toString()
        } else if (unverifiedUrl instanceof Request) {
          stringifiedUrl = unverifiedUrl.url
        }

        throw new Error(`Failed to fetch from ${stringifiedUrl}, ${error.message}`)
      }
    }
    throw error
  }
}

View on GitHub (pinned to 00c58b35c0)

Solutions

  1. From the server, run `curl -v <url>` / `nslookup <hostname>` to confirm reachability and TLS.
  2. Open the required egress (host/port) in firewall/security groups; configure an HTTP(S) proxy if your network mandates one.
  3. Fix the target's TLS cert (valid, non-expired, trusted CA) or add the CA to the trust store.
  4. Use a resolvable, reachable hostname; avoid internal-only names in production fetches.
  5. Retry transient network failures with bounded backoff.
  6. Add the URL to `skipSafeFetch`/`allowList` only if you have deliberately bypassed safeFetch and still see transport errors.

Example fix

// before — calling update with an unreachable remote URL
await payload.update({ collection: 'media', id, data: { url: 'https://files.example.com/a.png' } })
// server cannot resolve/connect -> Failed to fetch from ...

// after — verify reachability first, then store a working URL
const ok = await fetch('https://files.example.com/a.png').then(r => r.ok).catch(() => false)
if (ok) {
  await payload.update({ collection: 'media', id, data: { url: 'https://files.example.com/a.png' } })
} else {
  // upload the bytes directly instead of remote-fetching
}
Defensive patterns

Strategy: retry

Validate before calling

async function isReachable(url: string, timeoutMs = 5000): Promise<boolean> {
  const ctrl = new AbortController()
  const t = setTimeout(() => ctrl.abort(), timeoutMs)
  try {
    const res = await fetch(url, { method: 'HEAD', signal: ctrl.signal })
    return res.ok
  } catch { return false } finally { clearTimeout(t) }
}

if (!(await isReachable(doc.url))) {
  // unreachable — fix DNS/TLS/egress or upload bytes directly
}

Type guard

function isTransportError(err: unknown): boolean {
  return err instanceof Error && /failed to fetch from/i.test(err.message) && !/unsafe/i.test(err.message)
}

Try / catch

async function fetchWithRetry(url: string, attempts = 3) {
  for (let i = 0; i < attempts; i++) {
    try { return await fetch(url) }
    catch (err) {
      if (i === attempts - 1 || !isTransportError(err)) throw err
      await new Promise((r) => setTimeout(r, 2 ** i * 200))
    }
  }
}

Prevention

When it happens

Trigger: An external-file or paste-URL fetch where: DNS for the hostname fails (NXDOMAIN), the connection is refused/reset, TLS handshake fails (expired/self-signed cert), the server times out, or a proxy/egress firewall blocks the connection. Distinct from a successful-but-non-2xx response (that's error 271).

Common situations: No/incorrect egress rules (firewall, security group, VPC). DNS misconfiguration in the container. Self-signed/expired TLS certs on the target. Target host down. A proxy is required but not configured. IPv6-only target with no IPv6 egress. Local dev hitting an unreachable hostname.

Related errors


AI-assisted analysis of payloadcms/payload@00c58b35c0 (2026-08-12). Data as JSON: /api/errors/bea0e8b5109b0c71. Report an issue: GitHub.