HTTP status code · Client errors (4xx)
404 Not Found
404 Not Found means the server could not find anything at the requested URL, or chose not to reveal that something exists there. The most common cause is a broken link: the page was moved or deleted without a redirect, or the address has a typo.
| Class | 4xx, Client errors |
|---|---|
| Defined in | RFC 9110 §15.5.5 |
| Cacheable by default | Yes, heuristically cacheable; browsers and CDNs may keep it without explicit headers |
| Safe to retry | Not as-is; the same URL keeps returning 404 until something changes on the server |
| Relevant headers |
|
What 404 means
RFC 9110, section 15.5.5, defines 404 as the origin server not finding a current representation for the target resource, or not being willing to disclose that one exists. The second half matters: GitHub answers 404 for a private repository you cannot see, rather than 403, so that outsiders cannot confirm the repository exists.
A 404 says nothing about whether the absence is temporary or permanent. If you know a page is gone for good, the spec prefers 410 Gone. A 404 is also heuristically cacheable, so a CDN may keep serving it for a while after you publish the missing page unless you send Cache-Control.
The status line is what counts, not the page content. A friendly "page not found" template served with 200 OK is a soft 404: search engines flag it, monitoring treats it as success, and API clients try to parse it as data.
Common causes
If you are visiting the site
- A typo in the address, or the wrong letter case: /About and /about are different paths on most Linux servers.
- An old bookmark or search result pointing to a page that has since been renamed or removed.
- A link on another site that was never updated after the target site was reorganized.
If you run the server
- A page or product was deleted or its slug changed, and no 301 redirect was added from the old URL.
- A single-page app (React, Vue) with client-side routing: /dashboard works when you click to it but returns 404 on refresh, because the server has no file at that path and no fallback to index.html.
- Trailing-slash mismatch between the links and the server config (/docs vs /docs/), or files missing from the deploy because the build output or document root points to the wrong folder.
- In an API, the ID in the path does not exist, was soft-deleted, or belongs to another tenant and is hidden on purpose.
How to fix it
If you are visiting the site
- Check the spelling and the letter case of the URL, then try removing the last path segment to land on the parent section.
- Use the site search or its sitemap to find where the content moved.
- For content that is really gone, look for a copy on the Wayback Machine (web.archive.org).
- If a link on another site sent you there, tell that site owner; the fix has to happen on their side.
If you run the server
- Add a 301 (or 308) redirect from every old URL that still gets traffic or has backlinks to its closest new equivalent. Your access logs and the "Not found (404)" report in Google Search Console list the URLs that matter.
- For an SPA, make unknown paths fall back to index.html (nginx: try_files $uri $uri/ /index.html) and let the client router render its own not-found view.
- Return 410 Gone for content you removed deliberately, so crawlers drop it faster.
- Keep your custom 404 page, but make sure it is served with status 404, not 200. Check with curl -I https://example.com/does-not-exist.
How to send 404
In Next.js pages and layouts (not route handlers), call notFound() from next/navigation; it renders not-found.tsx with a 404 status.
app.get('/reports/:id', (req, res) => {
res.status(404).json({ error: 'Report not found' });
});// app/reports/[id]/route.ts
export async function GET() {
return Response.json({ error: 'Report not found' }, { status: 404 });
}mux.HandleFunc("GET /reports/{id}", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusNotFound) // 404
w.Write([]byte(`{"error":"Report not found"}`))
})from fastapi import FastAPI, HTTPException
app = FastAPI()
@app.get("/reports/{id}")
def get_report(id: str):
raise HTTPException(status_code=404, detail="Report not found")# Custom page, real 404 status
error_page 404 /404.html;
location = /404.html {
internal;
}
# Deliberately hide a path
location /internal/ {
return 404;
}Commonly confused with
- 404 vs 410
- 410 Gone says the resource existed and is permanently gone; 404 makes no claim about whether it ever existed or will come back.
- 404 vs 403
- 403 confirms the resource exists but you may not have it; servers often answer 404 instead to avoid leaking that it exists.
- 404 vs 400
- 400 means the request itself is malformed; 404 means the request was fine but nothing lives at that URL.
Frequently asked questions
- Do 404 errors hurt SEO?
- A 404 for a page that really no longer exists is normal and does not hurt the rest of the site; Google drops the URL after recrawling it. They cost you when the missing URL had backlinks or traffic, so redirect those with a 301 to the closest equivalent page.
- What is a soft 404?
- A page that tells the visitor the content was not found but is served with status 200. Google reports these as soft 404s in Search Console and may drop them anyway. Fix it by returning a real 404 or 410 status.
- Should an API return 404 or an empty 200?
- Return 404 when a single resource does not exist, such as GET /users/42. Return 200 with an empty array for a collection query with no matches, such as GET /users?name=zed: the collection exists, it just has no items.
- Why does my React or Vue app show 404 when I refresh the page?
- The browser asks the server for /dashboard, but the server only has index.html. Configure a fallback so unknown paths serve index.html (try_files $uri $uri/ /index.html in nginx, or the equivalent rewrite on your host) and let the client router take over.
Last reviewed by Arielton Oberek.