Next.js Images Broken in Production but Fine Locally: Fix
next/image works on your laptop but shows broken images after deploy, with 'upstream response is invalid' or a 400 on /_next/image. Find which of five causes it is by testing one URL.
When next/image works locally but breaks in production, the image optimizer on your server could not fetch or accept the source image. The usual causes are a host missing from remotePatterns, an image host that blocks your server's IP, a signed or private URL, a custom server that points the optimizer at the wrong port, or a Next.js version bug. Open the failing /_next/image?url=... request in a new tab, read the status code, and the cause is usually obvious.
The key idea is that the browser never asks the image host directly. It asks your own server at /_next/image, and your server fetches the original, resizes it and sends it back. Your laptop and your production server fetch from different networks, with different config and sometimes different versions. That is why one works and the other does not.
This guide uses real reports from the Next.js GitHub issue tracker. We could not open nextjs.org pages from our writing environment, so check the current Image docs before relying on a default value.
Step 1: Read the Real Error From /_next/image
Do not debug the <Image> component yet. Open DevTools, go to the Network tab, find the request to /_next/image?url=...&w=...&q=... and note the status code and response body.
You can also test it from a terminal:
curl -i "https://your-site.com/_next/image?url=https%3A%2F%2Fcdn.example.com%2Fphoto.jpg&w=640&q=75"
The message tells you which step to read next:
"url" parameter is not allowedwith a 400: the host is not allowed. Go to Step 2."url" parameter is valid but upstream response is invalid: the server reached for the image and the answer was bad. Go to Steps 3 to 5.- A 404 for a local image: the file is not in
public/in the deployed build. Check the path and its letter case.
Also check your server logs for the line upstream image response failed for <url> <status>. The status after the URL is the answer from the image host.
Step 2: Add the Host to remotePatterns
Next.js only optimizes remote images from hosts you list. This stops other people from using your server to resize their images. If the host is missing, you get a 400. It can look like a "production only" bug when you tested locally with a local file or a different host.
// next.config.js
module.exports = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'cdn.example.com',
pathname: '/photos/**',
},
],
},
}
Rules that trip people up:
- Match the protocol, hostname, port and path exactly.
cdn.example.comdoes not coverimages.example.com. - Prefer
remotePatternsover the olderimages.domains. One dev.to guide notesdomainsis deprecated and has no protocol or path control. - Config changes need a restart locally and a new deploy in production.
- A Reddit thread on this exact error reports the error continuing with correct syntax. A reply suggested the config file was not being read. Check the file name and that it sits at the project root.
Step 3: Test the Image From the Production Server
If the host is allowed and you still see "upstream response is invalid", ask a simple question: can your server fetch this URL? Your browser and your laptop can be allowed while a cloud server is not.
In a long-running Next.js discussion, a maintainer said the likely cause was the image host blocking the server's IP address. A test fetch from the server returned a 503 while the same image loaded locally. The poster also found that turning optimization off made the images load.
Add a temporary route to test:
// app/api/debug-image/route.ts (delete after testing)
export async function GET() {
const res = await fetch('https://cdn.example.com/photo.jpg')
return Response.json({ status: res.status, type: res.headers.get('content-type') })
}
Deploy it, call it, and read the status. A 403, 429 or 503 means the host is refusing your server. Look for hotlink protection, bot rules or a CDN firewall on the image host. If you own the host, allow your server or put the images on a CDN that accepts requests from anywhere.
Step 4: Check for Signed, Expiring or Private URLs
The image must be publicly reachable at the moment your server asks. In a Next.js issue, the error was upstream image response failed ... 403. The image was an expiring signed URL from a storage host, and the host said it failed to authenticate the request. A maintainer replied that if the upstream image is unavailable, Next.js can do little, and the image must be publicly accessible.
Common versions of this:
- Signed URLs from AI image tools or storage buckets that expire after a short time.
- Images behind a login or a preview-only bucket.
- URLs that only work with a cookie your browser has and your server does not.
Fix it by copying the image to storage you control when it is created, and saving that stable URL in your database. Do not store the temporary one.
Step 5: Check Port, Custom Server and Version
If you run a custom Express server or a container, the optimizer may call your own app. One GitHub issue shows connect ECONNREFUSED ::1:3000 from a reporter who set PORT=8080. The optimizer tried port 3000 instead of the real port. Make sure the port the server listens on matches the port Next.js was started with, and test with curl from inside the container.
The same issue reports the error appearing after upgrades to some 13.4.x and 13.5.x releases. Some people downgraded to 13.5.2, and one said 13.5.4-canary.1 worked. That thread is from 2023, so treat it as a clue and not a fix. If errors started right after you upgraded Next.js, check the release notes and issue tracker for your exact version.
The same thread shows one user avoiding the problem by serving images from a CDN instead of the public folder.
Step 6: Choose a Fallback While You Investigate
If you need images back now, you have options:
- Set
unoptimizedon the failing<Image>. The browser then loads the original URL directly, which skips your server's fetch. This does not help if the original URL itself is blocked or expired. - Use a plain
<img>tag for a small set of external images. A 2023 commenter in the discussion above said this loaded images that<Image unoptimized />could not for certain domains. - Use a custom
loaderthat points to an image CDN, so resizing happens there.
These hide the problem for users. They do not explain why the server could not fetch the image, so still finish Step 3.
How to Catch This Before Users Do
Broken images do not throw errors in your logs the way a crash does. The page returns 200, and only the image requests fail. If nobody tells you, you will not know. Our post on finding production bugs before users report them covers how to see failures like this early, and the Next.js error monitoring guide covers the four places a Next.js app can fail.
How Relia Helps
Relia watches the users, APIs and servers of a live product. A burst of failing /_next/image requests after a deploy is the kind of related signal it joins into one incident, explained in plain words, with who was affected. You can add checks on key pages, and a public status page. Setup on Node.js and browser apps is npx relia-sdk@latest init. Relia does not change your config for you. You still make the fix. There is a Free plan and a live demo at app.tryrelia.com/demo.
FAQ
Why is next/image working in development but not on Vercel?
Your laptop and Vercel fetch the image from different networks. The image host may block cloud IPs, the URL may be signed and expired, or the host may be missing from remotePatterns in the deployed config. Read the status on /_next/image first.
What does "url parameter is valid but upstream response is invalid" mean?
The optimizer on your server asked for the original image and did not get a usable image back. Check the status after upstream image response failed in your logs. 403, 429 and 503 point at the image host, and ECONNREFUSED points at your own port or network.
Should I use remotePatterns or domains?
Use remotePatterns. It lets you set protocol, host, port and path, and domains is described as deprecated in the guides we read.
Can I turn off image optimization?
Yes, with unoptimized on a single image or in next.config.js. Images then load as originals, which can be larger and slower on mobile, so use it as a stopgap.
Do I need a redeploy after changing next.config.js?
Yes. Restart the dev server locally and redeploy in production.