Express "Cannot GET /" (tested)
Cannot GET / is the default 404 page of Express. It means no route and no middleware answered that method and path. It does not mean the server is down. We reproduced every common cause in Express 5.2.1 and 4.22.3 and recorded the exact response.
1. What the response looks like
When nothing handles a request, Express's final handler sends this. Byte for byte the same in Express 5.2.1 and 4.22.3:
HTTP/1.1 404 Not Found
Content-Security-Policy: default-src 'none'
X-Content-Type-Options: nosniff
Content-Type: text/html; charset=utf-8
Content-Length: 139
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Error</title>
</head>
<body>
<pre>Cannot GET /</pre>
</body>
</html>
The text is always Cannot <METHOD> <path>. The query string is dropped: /nope?x=1 gave Cannot GET /nope. The path is shown as sent, still percent-encoded (/%3Cscript%3E stayed encoded). The Content-Security-Policy: default-src 'none' and nosniff headers appear only on this built-in 404. Our own routes did not get them. If you see those two headers on a 404, Express itself answered, not nginx or your code.
2. Cause and fix table
| Cause | What we saw | Fix |
|---|---|---|
No route for / (only /api defined) | 404 Cannot GET / | Add app.get('/', ...) or serve an index.html with express.static |
Router mounted at /api, URL without prefix | GET /users 404, GET /api/users 200 | Call the full path, or change the mount point |
Prefix written twice (app.use('/api', r) + r.get('/api/users')) | /api/users 404, /api/api/users 200 | Inside the router use r.get('/users') |
express.static('public') started from another folder | Cannot GET / and Cannot GET /index.html | express.static(path.join(__dirname, 'public')) |
| Static folder does not exist | 404 Cannot GET /, no startup error | Check the path; Express does not warn |
| SPA refresh on a client-side route | Cannot GET /dashboard/settings | Catch-all that sends index.html (section 5) |
app.get('*') in Express 5 | App crashes at startup: Missing parameter name at index 1: * | Use '/{*splat}' |
strict routing on, URL has trailing slash | Cannot GET /about/ | Turn it off (default) or add both routes |
case sensitive routing on, wrong case | Cannot GET /about for route /About | Turn it off (default) or fix the link |
| Wrong method (form POSTs to a GET-only route) | 404 Cannot POST /login | Add app.post('/login', ...) |
3. Router prefixes, trailing slashes and case
A router mounted with app.use('/api', router) only sees paths below /api. Its routes are relative to the mount point. Repeating the prefix inside the router is a common slip: it makes the real URL /api/api/users.
const r = express.Router();
r.get('/users', listUsers); // not '/api/users'
app.use('/api', r); // GET /api/users -> 200
With the router's own r.get('/'), both /api and /api/ returned 200.
By default Express ignores one trailing slash and ignores case. In both versions, route /about answered /about/, and route /About answered /about. Only when we turned on app.set('strict routing', true) did /about/ return Cannot GET /about/. With app.set('case sensitive routing', true), /about returned Cannot GET /about. If you get these 404s, check for those settings.
4. express.static: relative paths and __dirname
A relative path in express.static('public') is resolved against the current working directory, not the file it is written in. We started the same app from /tmp instead of the project folder. Both / and /index.html gave Cannot GET. There was no warning at startup, and a folder that does not exist at all behaved the same.
const path = require('path');
app.use(express.static(path.join(__dirname, 'public')));
With the absolute path, GET / returned 200 with public/index.html, whatever folder the process ran from. In ES modules, __dirname does not exist; use import.meta.dirname (per the Node.js docs, not measured). A missing file inside a correct folder still gives Cannot GET /missing.css; static passes it on rather than sending its own 404.
5. SPA refresh and the catch-all route
A React, Vue or Angular app changes the URL in the browser without asking the server. Reload on /dashboard/settings and the browser asks Express for that path. With only express.static('dist'), we got Cannot GET /dashboard/settings. The fix is a catch-all after static that sends index.html.
Express 5: the old '*' no longer works. Express 5 uses path-to-regexp 8, and every wildcard needs a name. This line crashes the app at startup, before it listens:
app.get('*', sendIndex);
// TypeError: Missing parameter name at index 1: *; visit https://git.new/pathToRegexpError for info
The thrown object is a PathError whose name is TypeError. '/*' and '/api/*' failed the same way, at index 2 and 6. The Express 4 regex style '(.*)' failed with Unexpected ( at index 0: (.*), and '/:path(.*)' with Unexpected ( at index 6.
The working Express 5 form wraps a named wildcard in braces, which makes it optional so it also matches /:
app.use(express.static(path.join(__dirname, 'dist')));
app.get('/{*splat}', (req, res) =>
res.sendFile(path.join(__dirname, 'dist', 'index.html')));
Results in Express 5.2.1: / 200, /dashboard/settings 200, POST /dashboard still Cannot POST /dashboard. Without braces, '/*splat' matched /a/b (req.params.splat was ["a","b"], an array) but not /. A regex, app.get(/.*/, ...), worked in both versions.
Express 4: app.get('*', ...) works and served index.html for /dashboard/settings. The Express 5 syntax does not work in 4.22.3: '/{*splat}' gave Cannot GET /dashboard/settings, and no error was thrown. So copy the syntax that matches your installed version.
One catch: the catch-all also answered /api/missing with 200 and the HTML page. That hides broken API calls and leads to JSON parse errors in the client (see Unexpected end of JSON input). Put an API 404 before the catch-all:
app.use('/api', apiRouter);
app.use('/api', (req, res) => res.status(404).json({ error: 'Not found' }));
app.get('/{*splat}', sendIndex);
A plain app.use(sendIndex) fallback also works in both versions, but it answered POST /x with 200 and the page too. app.get is safer.
6. Cannot POST, PUT, DELETE: wrong method
With only app.get('/login'), other methods got a 404, not a 405:
| Request | Express 5.2.1 | Express 4.22.3 |
|---|---|---|
| POST /login | 404 Cannot POST /login | same |
| PUT /login | 404 Cannot PUT /login | same |
| DELETE /login | 404 Cannot DELETE /login | same |
| HEAD /login | 200, handled by the GET route, empty body | same |
| OPTIONS /login | 200, Allow: GET, HEAD, text/plain | 200, Allow: GET,HEAD, text/html |
So Cannot POST usually means the form's method or action does not match a route, or the POST route is on a router mounted somewhere else. If you want a proper 405 with an Allow header, add it yourself with router.route('/login').get(...).all(...); see 405 Method Not Allowed.
7. Replace the default 404 page
Add a middleware after all routes. It runs only when nothing else answered:
app.use((req, res) => {
res.status(404).json({ error: 'Not found', path: req.originalUrl });
});
We got 404 with {"error":"Not found","path":"/"} and application/json. Note that the default default-src 'none' and nosniff headers are gone then; add them with your normal security headers if you need them (see X-Content-Type-Options: nosniff). If a page is removed for good, 410 may fit better than 404: 410 Gone vs 404.
8. Debug checklist
- Look at the method and path in the message. That is exactly what Express received, minus the query string.
- Check the two headers.
default-src 'none'plusnosniffmeans the request reached Express and fell through every route. - Run
DEBUG=express:* node app.jsto log routing. (Per the Express docs, not measured here.) - Check route order. Middleware runs top to bottom; the catch-all and the 404 handler must be last.
- Check
npm ls express. Wildcard syntax differs between 4 and 5.
Frequently asked questions
What does "Cannot GET /" mean in Express?
It is Express's built-in 404. No route or middleware handled a GET request for /. The server is running; it just has nothing for that path. We saw the same 139-byte HTML page in Express 5.2.1 and 4.22.3.
Why does app.get('*') crash in Express 5?
Express 5 uses path-to-regexp 8, where wildcards must have a name. app.get('*') threw "Missing parameter name at index 1: *" at startup. Use app.get('/{*splat}', ...) instead; the braces make it also match /.
Why do I get Cannot GET when I refresh my React app?
The browser asks the server for the client-side route, and Express has no file or route for it. Add a catch-all after express.static that sends index.html: '/{*splat}' in Express 5, '*' in Express 4.
Why does express.static return Cannot GET /?
A relative folder is resolved against the working directory, not your file. Started from another folder, our app gave Cannot GET /. Use path.join(__dirname, 'public'). Express gives no warning for a missing folder.
Why do I get "Cannot POST" instead of 405?
Express answers an unmatched method with a 404 and "Cannot POST /path", even if a GET route exists for that path. Add the POST route, or send a 405 with an Allow header yourself.
Written with AI assistance. Measured on 2026-10-02 with Express 5.2.1 (router 2.2.0, path-to-regexp 8.4.2, finalhandler 2.1.1) and Express 4.22.3 (path-to-regexp 0.1.13, finalhandler 1.3.2) on Node.js 24.21.0. Requests were made with Node's http module; we read the raw status, headers and body. PlainHeaders is our extension; Express and Node.js are free.