AI & Agent Dev Bug Sandbox logo
AI & Agent Dev Bug Sandbox
Back to Radar

Middleware Rewrites Are Network-Proxied When Using `Next Start -H 127.0.0.1`, Causing EPROTO 500 Behind TLS-Terminating Proxies

When Next.js is started with `-H 127.0.0.1` behind a reverse proxy that sets `X-Forwarded-Proto: https`, middleware rewrites targeting the same server are incorrectly treated as external and proxied over the network using the `https` scheme against the plain-HTTP port, resulting in `write EPROTO ... wrong version number` and a 500 response. The root cause is an asymmetric normalization of loopback hostnames between the router server's `initUrl` and the URL exposed to middleware.

highConfidence 85%Next.jsAffected V16.2.6Affected V16.3.0-Canary.49

Origin Analysis

The router server builds `initUrl` using the literal value of `-H` (e.g., `127.0.0.1`) and the scheme from `x-forwarded-proto`, producing `https://127.0.0.1:3000` as the origin. In contrast, the URL available inside middleware goes through `NextURL.parseURL()`, which normalizes any loopback hostname (`127.0.0.0/8`, `::1`) to `localhost`. Thus, a rewrite from middleware using `new URL('/b', request.url)` yields `https://localhost:3000/b`. The subsequent `getRelativeURL` comparison only relativizes when origins exactly match, so `localhost` ≠ `127.0.0.1`, the rewrite remains absolute, and the router takes the external-proxy path. `proxyRequest` preserves the `https` scheme, leading to a TLS handshake attempt against the plain HTTP port and the observed EPROTO failure.
1. Clone or create a minimal Next.js app with a middleware file that rewrites `/a` to `/b`: ```ts export default function proxy(request: NextRequest) { if (request.nextUrl.pathname === '/a') { return NextResponse.rewrite(new URL('/b', request.url)); } } ``` 2. Run `npm install && npm run build`. 3. Start the server with an explicit loopback hostname: `npm start -- -H 127.0.0.1 -p 3000`. 4. Simulate a TLS-terminating reverse proxy by sending the `X-Forwarded-Proto: https` header: `curl -i -H 'X-Forwarded-Proto: https' http://127.0.0.1:3000/a`. 5. Observe the 500 response and server log containing `Failed to proxy https://localhost:3000/b Error: write EPROTO ... SSL routines:tls_validate_record_header:wrong version number`. 6. Repeating the request without `-H` or with `-H localhost` does not trigger the failure, confirming the loopback normalization asymmetry.

Fixing Code Block

Edge Case Audit

Testing should cover deployments using `-H 127.0.0.1` with and without `x-forwarded-proto`, as well as IPv6 loopback (`-H ::1`). The normalisation may alter the hostname used in internal logging or in rare cases where a self-rewrite intentionally targets a different loopback address; however, such usage is unlikely and already broken behind TLS proxies. Rollback: simply revert the change to restore previous behavior. Additional testing on Windows and with edge functions is recommended, as loopback address syntax and proxy behavior may vary.

Ecosystem Topology