Skip to content

Commit 063e8a3

Browse files
nginx: allow /.well-known/* to reach the app (RFC 8615)
The catch-all dotfile block (`location ~ /\.`) was 404'ing every request whose path started with `/.well-known/` before nginx ever forwarded it to PHP. That broke OIDC discovery, OAuth authorization- server metadata, security.txt, and ACME http-01 challenges — anything RFC 8615 says belongs in the well-known namespace. Symptom that surfaced this: id.cbox.systems/.well-known/jwks.json returned `<center>nginx</center>` 404 even though Laravel had the route registered (`oauth.jwks → JwksController`). Vault's OIDC backend can't be configured without id's discovery endpoint reachable, so this is a hard prerequisite for vault bootstrap. Fix: * Add `location ^~ /.well-known/` BEFORE the regex catch-all in both `default.conf` and `default-rootless.conf`. The `^~` modifier short-circuits regex evaluation when the prefix matches, so the dotfile block stops winning for this specific path. Apps that don't register a /.well-known/* route still get a Laravel 404 (404-from-app), not a 403-from-nginx. * Three new test-security.sh assertions: - /.well-known/openid-configuration must NOT return 403 - response body must NOT be the nginx default-error page - /.env must STILL be blocked (regression guard against the well-known allow over-broadening into other dotfiles) Verified via `docker run nginx:1.27-alpine nginx -t` against both configs — syntax clean.
1 parent c0c488d commit 063e8a3

3 files changed

Lines changed: 63 additions & 0 deletions

File tree

php-fpm-nginx/common/default-rootless.conf

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,21 @@ server {
163163
# SECURITY: Block sensitive files and directories
164164
# ─────────────────────────────────────────────────────────────────────────
165165

166+
# RFC 8615 — /.well-known/ is the reserved namespace for site-wide
167+
# service metadata: OIDC discovery, OAuth authorization-server
168+
# metadata, security.txt, ACME challenges, change-password, etc.
169+
# Apps register handlers (e.g. Laravel: `Route::get('.well-known/...')`),
170+
# but the catch-all dotfile block below would otherwise 404 every
171+
# request before the app sees it.
172+
#
173+
# `^~` makes this a prefix match that wins over the regex catch-all
174+
# below — nginx prefers `^~` matches and skips regex evaluation
175+
# when one matches. Place BEFORE the `/\.` block; ordering of
176+
# regex blocks matters but `^~` short-circuits regardless.
177+
location ^~ /.well-known/ {
178+
try_files $uri $uri/ /index.php?$query_string;
179+
}
180+
166181
# Block hidden files (.env, .git, .htaccess, .svn, etc.)
167182
location ~ /\.(env|git|svn|htaccess|htpasswd|gitignore|gitattributes|dockerignore) {
168183
deny all;

php-fpm-nginx/common/default.conf

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,21 @@ server {
162162
# SECURITY: Block sensitive files and directories
163163
# ─────────────────────────────────────────────────────────────────────────
164164

165+
# RFC 8615 — /.well-known/ is the reserved namespace for site-wide
166+
# service metadata: OIDC discovery, OAuth authorization-server
167+
# metadata, security.txt, ACME challenges, change-password, etc.
168+
# Apps register handlers (e.g. Laravel: `Route::get('.well-known/...')`),
169+
# but the catch-all dotfile block below would otherwise 404 every
170+
# request before the app sees it.
171+
#
172+
# `^~` makes this a prefix match that wins over the regex catch-all
173+
# below — nginx prefers `^~` matches and skips regex evaluation
174+
# when one matches. Place BEFORE the `/\.` block; ordering of
175+
# regex blocks matters but `^~` short-circuits regardless.
176+
location ^~ /.well-known/ {
177+
try_files $uri $uri/ /index.php?$query_string;
178+
}
179+
165180
# Block hidden files (.env, .git, .htaccess, .svn, etc.)
166181
location ~ /\.(env|git|svn|htaccess|htpasswd|gitignore|gitattributes|dockerignore) {
167182
deny all;

tests/e2e/scenarios/test-security.sh

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,39 @@ else
137137
log_fail "composer.lock is accessible! (HTTP $CODE)"
138138
fi
139139

140+
# RFC 8615 — /.well-known/ MUST reach the app, NOT be denied at nginx.
141+
# The test app may not have a route registered (→ 404 from PHP), but
142+
# anything other than 403 means nginx forwarded it, which is what
143+
# OIDC discovery, ACME, security.txt, etc. all rely on. A 403 here
144+
# means the catch-all dotfile block re-broke; a 404 from nginx (the
145+
# default error page, not Laravel's) also means it didn't reach PHP
146+
# — distinguish by checking the response body.
147+
CODE=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:8101/.well-known/openid-configuration" 2>/dev/null)
148+
if [ "$CODE" = "403" ]; then
149+
log_fail "/.well-known/openid-configuration is denied at nginx (HTTP 403) — RFC 8615 violation, OIDC discovery breaks"
150+
else
151+
log_success "/.well-known/openid-configuration is forwarded to app (HTTP $CODE)"
152+
fi
153+
154+
# /.well-known/ paths MUST forward to PHP — confirm via response body.
155+
# Laravel's 404 page is HTML with Tailwind/Inertia content; nginx's
156+
# raw 404 is the bare "<center>nginx</center>" page. If we see the
157+
# nginx default-error body, the path was NOT forwarded.
158+
BODY=$(curl -s "http://localhost:8101/.well-known/openid-configuration" 2>/dev/null)
159+
if echo "$BODY" | grep -q "<center>nginx</center>"; then
160+
log_fail "/.well-known/* hit nginx's default error page — the catch-all dotfile block is winning"
161+
else
162+
log_success "/.well-known/* requests reach the app (no nginx default-error body)"
163+
fi
164+
165+
# Confirm /.env STILL blocked — regression guard for the well-known fix.
166+
CODE=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:8101/.env" 2>/dev/null)
167+
if [ "$CODE" = "403" ] || [ "$CODE" = "404" ]; then
168+
log_success ".env stays blocked after well-known allow (HTTP $CODE)"
169+
else
170+
log_fail ".env became accessible — well-known allow over-broadened the rule! (HTTP $CODE)"
171+
fi
172+
140173
# ═══════════════════════════════════════════════════════════════════════════
141174
# TEST 4: Directory Blocking
142175
# ═══════════════════════════════════════════════════════════════════════════

0 commit comments

Comments
 (0)