vercel/next.js · error · ImageError

"url" parameter is valid but upstream response timed out

Error message

"url" parameter is valid but upstream response timed out

What it means

Thrown by fetchExternalImage (ImageError 504) when fetching the upstream image URL times out. The fetch is wrapped in AbortSignal.timeout(7_000) (7 seconds); if it rejects with a TimeoutError, Next.js logs the href and returns a 504 to the client. This indicates the upstream image host is unreachable or too slow.

Source

Thrown at packages/next/src/server/image-optimizer.ts:894

        'upstream image',
        href,
        'hostname resolved to private IP',
        JSON.stringify(privateIps),
        'If this is expected and you understand SSRF risk, use images.dangerouslyAllowLocalIP = true to continue.'
      )
      throw new ImageError(400, '"url" parameter is not allowed')
    }
  }
  const res = await fetch(href, {
    signal: AbortSignal.timeout(7_000),
    redirect: 'manual',
  }).catch((err) => err as Error)

  if (res instanceof Error) {
    const err = res as Error
    if (err.name === 'TimeoutError') {
      Log.error('upstream image response timed out for', href)
      throw new ImageError(
        504,
        '"url" parameter is valid but upstream response timed out'
      )
    }
    throw err
  }

  const locationHeader = res.headers.get('Location')
  if (
    isRedirect(res.status) &&
    locationHeader &&
    URL.canParse(locationHeader, href)
  ) {
    if (count === 0) {
      Log.error('upstream image response had too many redirects', href)
      throw new ImageError(
        508,
        '"url" parameter is valid but upstream response is invalid'

View on GitHub (pinned to 0ae8c72462)

Solutions

  1. Retry the request; timeouts are often transient.
  2. Verify the upstream image URL is reachable from the server (curl with timing from the host).
  3. Self-host/mirror the image locally or via a CDN edge closer to the server.
  4. If you control the timeout, consider caching the optimized image (images.minimumCacheTTL) to avoid repeated upstream fetches.

Example fix

# before
/_next/image?url=https://slow-host.example/photo.jpg&w=640&q=75  # 504

# after
# verify reachability and cache; mirror to a faster origin
curl -I --max-time 10 https://slow-host.example/photo.jpg
# then reference a faster/CDN-backed URL
Defensive patterns

Strategy: retry

Validate before calling

// Probe upstream reachability within the optimizer's timeout before serving
async function isUpstreamReachable(href: string, ms = 7000) {
  const ctrl = new AbortController()
  const t = setTimeout(() => ctrl.abort(), ms)
  try {
    const r = await fetch(href, { method: 'HEAD', signal: ctrl.signal })
    return r.ok
  } catch { return false } finally { clearTimeout(t) }
}

Prevention

When it happens

Trigger: fetch(href) with a 7-second abort timeout rejects and err.name === 'TimeoutError'. The catch converts it to ImageError(504, '"url" parameter is valid but upstream response timed out').

Common situations: Upstream image server is slow or overloaded. Network latency between the Next.js server and the image host. A firewall or egress filter dropping packets so the connection hangs until timeout. Transient upstream outage.

Understand the failure class

Related errors


AI-assisted analysis of vercel/next.js@0ae8c72462 (2026-08-06). Data as JSON: /api/errors/0ec56a42e8229f2b. Report an issue: GitHub.