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

CauseWhat we sawFix
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 prefixGET /users 404, GET /api/users 200Call 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 200Inside the router use r.get('/users')
express.static('public') started from another folderCannot GET / and Cannot GET /index.htmlexpress.static(path.join(__dirname, 'public'))
Static folder does not exist404 Cannot GET /, no startup errorCheck the path; Express does not warn
SPA refresh on a client-side routeCannot GET /dashboard/settingsCatch-all that sends index.html (section 5)
app.get('*') in Express 5App crashes at startup: Missing parameter name at index 1: *Use '/{*splat}'
strict routing on, URL has trailing slashCannot GET /about/Turn it off (default) or add both routes
case sensitive routing on, wrong caseCannot GET /about for route /AboutTurn it off (default) or fix the link
Wrong method (form POSTs to a GET-only route)404 Cannot POST /loginAdd 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:

RequestExpress 5.2.1Express 4.22.3
POST /login404 Cannot POST /loginsame
PUT /login404 Cannot PUT /loginsame
DELETE /login404 Cannot DELETE /loginsame
HEAD /login200, handled by the GET route, empty bodysame
OPTIONS /login200, Allow: GET, HEAD, text/plain200, 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

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.