fix(caddy): wrap actuator block in handle so it takes precedence over catch-all (#512) #517

Merged
marcel merged 1 commits from fix/issue-512-caddy-actuator-block-handle into main 2026-05-11 17:15:03 +02:00
Owner

Summary

Closes #512.

The (block_actuator) Caddyfile snippet emitted respond @actuator 404 at the top level of each archive vhost. But each vhost also has a catch-all handle { reverse_proxy ... }. Caddy's handle blocks are mutually exclusive — once a handle matches, the request never reaches a top-level respond. The catch-all swallowed /actuator/* and proxied it to the backend, which Spring-Security'd 302 to /login.

Fix

Wrap the snippet body in handle /actuator/* { respond 404 }. Caddy sorts handle blocks by path specificity, so /actuator/* wins over the catch-all and the 404 is actually returned.

Verified locally

  • docker run --rm -v ./infra/caddy/Caddyfile:/etc/caddy/Caddyfile:ro caddy:2 caddy validate → Valid configuration

Test plan after merge

  • cd /opt/familienarchiv && git pull && systemctl reload caddy on the host
  • curl -o /dev/null -w "%{http_code}" https://staging.raddatz.cloud/actuator/health → 404 (was 302)
  • nightly.yml smoke step's /actuator/health → 404 assertion passes
  • Confirm /api/... and / routes are still proxied normally (the new handle block must not shadow anything else — /actuator/* is a strict prefix)

Notes

  • Defense-in-depth invariant from PR #499 / Nora's review (#8333) was effectively unmet on the live stack until now. Curl against staging today: HTTP 302 instead of HTTP 404. After this merge it'll be the documented behavior.

🤖 Generated with Claude Code

## Summary Closes #512. The `(block_actuator)` Caddyfile snippet emitted `respond @actuator 404` at the top level of each archive vhost. But each vhost also has a catch-all `handle { reverse_proxy ... }`. Caddy's `handle` blocks are mutually exclusive — once a `handle` matches, the request never reaches a top-level `respond`. The catch-all swallowed `/actuator/*` and proxied it to the backend, which Spring-Security'd 302 to `/login`. ## Fix Wrap the snippet body in `handle /actuator/* { respond 404 }`. Caddy sorts `handle` blocks by path specificity, so `/actuator/*` wins over the catch-all and the 404 is actually returned. ## Verified locally - [x] `docker run --rm -v ./infra/caddy/Caddyfile:/etc/caddy/Caddyfile:ro caddy:2 caddy validate` → Valid configuration ## Test plan after merge - [ ] `cd /opt/familienarchiv && git pull && systemctl reload caddy` on the host - [ ] `curl -o /dev/null -w "%{http_code}" https://staging.raddatz.cloud/actuator/health` → 404 (was 302) - [ ] `nightly.yml` smoke step's `/actuator/health → 404` assertion passes - [ ] Confirm `/api/...` and `/` routes are still proxied normally (the new `handle` block must not shadow anything else — `/actuator/*` is a strict prefix) ## Notes - Defense-in-depth invariant from PR #499 / Nora's review (#8333) was effectively unmet on the live stack until now. Curl against staging today: `HTTP 302` instead of `HTTP 404`. After this merge it'll be the documented behavior. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
marcel added 1 commit 2026-05-11 17:12:53 +02:00
fix(caddy): wrap actuator block in handle so it takes precedence over catch-all
Some checks failed
CI / Unit & Component Tests (pull_request) Has been cancelled
CI / OCR Service Tests (pull_request) Has been cancelled
CI / Backend Unit Tests (pull_request) Has been cancelled
CI / fail2ban Regex (pull_request) Has been cancelled
CI / Compose Bucket Idempotency (pull_request) Has been cancelled
CI / Unit & Component Tests (push) Has been cancelled
CI / OCR Service Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / fail2ban Regex (push) Has been cancelled
CI / Compose Bucket Idempotency (push) Has been cancelled
2093fc0481
Closes #512.

The previous `(block_actuator)` snippet emitted `respond @actuator 404`
at the top level of each archive vhost. But each vhost also has a
catch-all `handle { reverse_proxy ... }` that matches /actuator/*
too. Caddy's `handle` blocks are mutually exclusive — once one matches,
the request never reaches a top-level `respond`. So /actuator/health
was being proxied to the backend, which 302s to /login.

Wrap the actuator response in its own `handle /actuator/*` block.
Caddy sorts `handle` blocks by path specificity, so /actuator/* wins
over the catch-all and the 404 is actually returned.

Verified with `caddy validate` against the caddy:2 image.

Also unblocks the nightly.yml smoke test's `/actuator/health → 404`
assertion, which has been failing since the first staging deploy.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
marcel merged commit 9686e304c2 into main 2026-05-11 17:15:03 +02:00
marcel deleted branch fix/issue-512-caddy-actuator-block-handle 2026-05-11 17:15:04 +02:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: marcel/familienarchiv#517