Skip to content

fix: return HTTP 503 on bootstrap error for /health endpoint#1961

Merged
kayjoosten merged 3 commits intomainfrom
fix/1936-health-endpoint-503-on-error
Apr 9, 2026
Merged

fix: return HTTP 503 on bootstrap error for /health endpoint#1961
kayjoosten merged 3 commits intomainfrom
fix/1936-health-endpoint-503-on-error

Conversation

@kayjoosten
Copy link
Copy Markdown
Contributor

Summary

  • Wraps the env validation block in public/index.php in a try-catch(\Throwable) so misconfiguration errors (e.g. missing/short APP_SECRET) return HTTP 503 instead of a PHP fatal error page with HTTP 200
  • Response format matches the openconext/monitor-bundle DOWN convention: {"status":"DOWN","message":"..."}
  • Adds error_log() so bootstrap failures still appear in server logs
  • Adds functional test coverage for the public /health route

Test Plan

  • Set APP_ENV=prod and APP_SECRET=short in devconf .env, restart engine container, hit GET https://engine.dev.openconext.local/health — expect HTTP 503 with {"status":"DOWN","message":"APP_SECRET must be at least 32 characters long in production."}
  • Revert .env, restart, hit /health again — expect HTTP 200 {"status":"UP"}
  • Run ./vendor/bin/phpunit --configuration=./tests/phpunit.xml --testsuite=functional --filter=MonitorControllerTest — expect 3 tests passing

Closes #1936

Uncaught exceptions thrown during bootstrap (before Symfony starts)
produced a PHP fatal error page with HTTP 200. Narrow the try-catch
to only the env validation block so monitoring systems receive a 503
JSON response consistent with the monitor-bundle DOWN format, without
interfering with response sending or kernel termination.

Closes #1936
@kayjoosten kayjoosten requested a review from johanib April 1, 2026 09:47
Copy link
Copy Markdown
Contributor

@johanib johanib left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did some digging. Your change seems the best solution 👍
Not really nice or elegant. But it works and I think its the only way.

One suggestion in the code, and maybe it's a good idea to also intercept errors in the regular fallback handler. Otherwise, in some scenario's the fallback might hijack errors, and do a 301 redirect instead of returning a 500 on the health endpoints..

Index: src/OpenConext/EngineBlockBundle/EventListener/FallbackExceptionListener.php
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/OpenConext/EngineBlockBundle/EventListener/FallbackExceptionListener.php b/src/OpenConext/EngineBlockBundle/EventListener/FallbackExceptionListener.php
--- a/src/OpenConext/EngineBlockBundle/EventListener/FallbackExceptionListener.php	(revision 1cdd470e6c39b94a9a7344e3a471010344487772)
+++ b/src/OpenConext/EngineBlockBundle/EventListener/FallbackExceptionListener.php	(date 1775571383912)
@@ -21,6 +21,7 @@
 use EngineBlock_Exception;
 use OpenConext\EngineBlockBridge\ErrorReporter;
 use Psr\Log\LoggerInterface;
+use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\RedirectResponse;
 use Symfony\Component\HttpKernel\Event\ExceptionEvent;
 use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
@@ -76,6 +77,15 @@
             $exception->getMessage()
         ));
 
+        $path = $event->getRequest()->getPathInfo();
+        if (in_array($path, ['/health', '/internal/health', '/info', '/internal/info'], true)) {
+            $event->setResponse(new JsonResponse(
+                ['status' => 'DOWN', 'message' => $exception->getMessage()],
+                JsonResponse::HTTP_SERVICE_UNAVAILABLE
+            ));
+            return;
+        }
+
         if ($exception instanceof EngineBlock_Exception) {
             $this->errorReporter->reportError($exception, 'Caught Unhandled EngineBlock_Exception');
         } else {

public/index.php Outdated
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe include everything up to and including Request::createFromGlobals() into the try?
Wdyt?

…outes

The fallback exception listener redirected all unhandled exceptions to
the feedback page, which would return a 301 instead of an error status
for the health and info endpoints. Return a JSON 503 response instead
for these paths, consistent with the monitor-bundle DOWN format.
@kayjoosten kayjoosten requested a review from johanib April 8, 2026 11:03
Include all pre-output bootstrap steps in the try-catch so that
kernel boot failures (e.g. misconfigured bundles) also return 503
instead of a PHP error page with HTTP 200.
@kayjoosten kayjoosten force-pushed the fix/1936-health-endpoint-503-on-error branch from b781d37 to d7d2fa5 Compare April 9, 2026 11:35
@kayjoosten kayjoosten merged commit e8725a9 into main Apr 9, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

/health endpoint return 200 status even if the call fails

2 participants