vercel/next.js · error · ImageError
"url" parameter is not allowed
Error message
"url" parameter is not allowed
What it means
Thrown by fetchExternalImage (ImageError 400) when an external image URL's hostname resolves to a private/loopback IP and images.dangerouslyAllowLocalIP is false. Next.js performs DNS resolution (including all A/AAAA records) and rejects any private IP to prevent Server-Side Request Forgery (SSRF). This is a security guard, not a bug.
Source
Thrown at packages/next/src/server/image-optimizer.ts:882
let ips = [hostname]
if (!isIP(hostname)) {
const records = await lookup(hostname, {
family: 0,
all: true,
hints: ALL,
}).catch((_) => [{ address: hostname }])
ips = records.map((record) => record.address)
}
const privateIps = ips.filter((ip) => isPrivateIp(ip))
if (privateIps.length > 0) {
Log.error(
'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
}View on GitHub (pinned to 0ae8c72462)
Solutions
- If SSRF risk is understood and acceptable, set images.dangerouslyAllowLocalIP = true in next.config.
- Whitelist only safe remote domains via images.remotePatterns/domains instead of arbitrary URLs.
- Move the upstream image host to a public IP/domain so it doesn't resolve privately.
- For internal images, serve them locally and use fetchInternalImage instead of an external URL.
Example fix
// before
module.exports = { images: { remotePatterns: [{ protocol: 'https', hostname: '**' }] } }
// after (explicit, allow local IP only if intended)
module.exports = {
images: {
dangerouslyAllowLocalIP: true,
remotePatterns: [{ protocol: 'http', hostname: 'internal.local' }],
},
} Defensive patterns
Strategy: validation
Validate before calling
// Pre-validate remote image URLs against your allowed, non-private hosts
import { isIP } from 'net'
import { lookup } from 'dns/promises'
import ipaddr from 'ipaddr.js'
async function isAllowedRemoteImage(href: string, allowedHosts: string[]) {
const { hostname } = new URL(href)
if (!allowedHosts.includes(hostname)) return false
const addrs = isIP(hostname) ? [hostname] : (await lookup(hostname, { all: true })).map(r => r.address)
return addrs.every(a => !ipaddr.parse(a).range().match(/^(private|loopback|linkLocal)$/))
} Prevention
- Use images.remotePatterns with explicit hostnames, never a wildcard private range.
- Only set dangerouslyAllowLocalIP after reviewing SSRF risk.
- Validate remote image URLs at the data layer, not just at request time.
When it happens
Trigger: An /_next/image?url=... request points at a hostname that resolves to a private IP (10.x, 192.168.x, 127.x, 169.254.x, etc.) and dangerouslyAllowLocalIP is not enabled. The privateIps filter is non-empty and the throw at line 882 fires.
Common situations: Pointing the image loader at an internal service on a private network. A hostname that is public today but resolves to a private range in a containerized/VPN environment. Local development with localhost-like hostnames. DNS rebinding where a hostname flips to a private IP.
Related errors
- "url" parameter is valid but upstream response timed out
- "url" parameter is valid but upstream response is invalid
- Failed to fetch ${url}
- [${label}] warmup: all ${batchSize} requests failed — server
- Failed to fetch ${url}, too many retries
AI-assisted analysis of vercel/next.js@0ae8c72462 (2026-08-06).
Data as JSON: /api/errors/55ebbf5b4b275519.
Report an issue: GitHub.