slash-less route URLs 301 to port 14443 and hang until the browser times out #9

Closed
opened 2026-08-17 11:03:08 +00:00 by grenade · 0 comments
Owner

Every route requested without a trailing slash redirects visitors to a port that is not reachable from outside:

$ curl -sSI https://rob.tn/activity | grep -iE '^(HTTP/|location)'
HTTP/2 301
location: https://rob.tn:14443/activity/

$ curl -sSI https://rob.tn/blog  ->  location: https://rob.tn:14443/blog/
$ curl -sSI https://rob.tn/cv    ->  location: https://rob.tn:14443/cv/

Port 14443 does not answer from off-host, so the browser sits on a TCP connect that never completes and only fails after its timeout — 60s+ in practice, then an error page. From the visitor's side it looks like the site is simply hanging.

Cause

location / { try_files $uri $uri/ /index.html; } makes nginx 301 a slash-less directory URL to add the trailing slash. nginx builds that Location as an absolute URL from its own $server_port, and this vhost listens on WEB_LISTEN = 127.0.0.1:14443 ssl proxy_protocol because it sits behind the edge's stream SNI router. So the redirect advertises 14443 rather than the 443 the client actually used.

Not caused by any recent change — it follows from the vhost listening on a shifted port, so it has been true for as long as that arrangement has.

Scope

100% of requests to /activity, /blog, /cv and /project/<source>/<repo> without a trailing slash — external links, bookmarks, typed URLs, crawlers. In-app navigation is unaffected because react-router never round-trips to the server, and / is unaffected because the root needs no directory redirect. That combination is what kept it hidden: the homepage always loads fine.

Fix

absolute_redirect off; at server level, so nginx emits a relative Location: /activity/ and the browser keeps whatever scheme, host and port it used.

Reproduced and verified in isolation with a container listening on 8081 published as 18081:

before:  Location: http://127.0.0.1:8081/activity/    <- nginx's own port
after:   Location: /activity/

port_in_redirect off would also drop the port, but absolute_redirect off is the stronger fix: it stops nginx asserting a scheme and host it cannot know from behind the router.

Every route requested without a trailing slash redirects visitors to a port that is not reachable from outside: ``` $ curl -sSI https://rob.tn/activity | grep -iE '^(HTTP/|location)' HTTP/2 301 location: https://rob.tn:14443/activity/ $ curl -sSI https://rob.tn/blog -> location: https://rob.tn:14443/blog/ $ curl -sSI https://rob.tn/cv -> location: https://rob.tn:14443/cv/ ``` Port 14443 does not answer from off-host, so the browser sits on a TCP connect that never completes and only fails after its timeout — 60s+ in practice, then an error page. From the visitor's side it looks like the site is simply hanging. ## Cause `location / { try_files $uri $uri/ /index.html; }` makes nginx 301 a slash-less directory URL to add the trailing slash. nginx builds that `Location` as an absolute URL from its own `$server_port`, and this vhost listens on `WEB_LISTEN` = `127.0.0.1:14443 ssl proxy_protocol` because it sits behind the edge's stream SNI router. So the redirect advertises 14443 rather than the 443 the client actually used. Not caused by any recent change — it follows from the vhost listening on a shifted port, so it has been true for as long as that arrangement has. ## Scope 100% of requests to `/activity`, `/blog`, `/cv` and `/project/<source>/<repo>` without a trailing slash — external links, bookmarks, typed URLs, crawlers. In-app navigation is unaffected because react-router never round-trips to the server, and `/` is unaffected because the root needs no directory redirect. That combination is what kept it hidden: the homepage always loads fine. ## Fix `absolute_redirect off;` at server level, so nginx emits a relative `Location: /activity/` and the browser keeps whatever scheme, host and port it used. Reproduced and verified in isolation with a container listening on 8081 published as 18081: ``` before: Location: http://127.0.0.1:8081/activity/ <- nginx's own port after: Location: /activity/ ``` `port_in_redirect off` would also drop the port, but `absolute_redirect off` is the stronger fix: it stops nginx asserting a scheme and host it cannot know from behind the router.
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: grenade/moments#9