Skip to content

PHP requests return 500 during shutdown_delay after SIGTERM #2644

Description

@nesl247

What happened?

With shutdown_delay 30s, PHP requests return HTTP 500 after SIGTERM while Caddy continues serving static responses. The error is FrankenPHP is not running. This reproduces with an unmodified official image and a trivial PHP script, without a framework or Kubernetes.

Caddy documents that the server continues operating normally during shutdown_delay, before the grace period begins. I expected PHP requests to remain serviceable during that delay, while the health endpoint returns 503.

Reproduced on FrankenPHP 1.12.6 and 1.12.7, PHP 8.5.9, Caddy 2.11.4, linux/arm64, using the Debian Trixie Docker images. The 1.12.7 image digest was sha256:f92d81eb3fe4fd18b35d3d58192b7cc3acc8943817bbf39f2fcf0be02a3916dc.

Reproduction

Save this as Caddyfile:

{
    admin localhost:2019
    auto_https off
    shutdown_delay 30s
    grace_period 300s
    frankenphp {
        num_threads 2
    }
}
:8080 {
    log
    handle /health {
        @stopping vars {http.shutting_down} true
        respond @stopping "stopping" 503
        respond "healthy" 200
    }
    handle /static {
        respond "static-ok" 200
    }
    handle {
        root * /app/public
        php_server
    }
}

Save this as index.php:

<?php
header('Content-Type: text/plain');
echo "php-ok\n";

Start the container:

container=$(docker run -d -p 127.0.0.1::8080 \
  -v "$PWD/Caddyfile:/etc/caddy/Caddyfile:ro" \
  -v "$PWD/index.php:/app/public/index.php:ro" \
  --entrypoint frankenphp \
  dunglas/frankenphp:1.12.7-php8.5.9-trixie \
  run --config /etc/caddy/Caddyfile)
endpoint=$(docker port "$container" 8080/tcp)

Once curl -i "http://$endpoint/" returns 200 and php-ok, send SIGTERM and immediately request all three endpoints:

docker kill --signal=TERM "$container"
curl -i "http://$endpoint/"
curl -i "http://$endpoint/static"
curl -i "http://$endpoint/health"
docker logs "$container"

Observed: PHP returns 500, static returns 200, and health returns 503. In the automated 1.12.7 run, PHP returned 79 sampled 500 responses throughout the 30-second delay; each was followed by a successful static request. The container then exited normally. Cleanup: docker rm -f "$container".

If the first shutdown does not reproduce, repeat with a fresh container; the app-stop order can differ between runs.

Suspected cause and local experiment

FrankenPHPApp.Stop() calls frankenphp.Shutdown() when Caddy is exiting, while Caddy stops its apps without dependency ordering. PHP can therefore stop before the HTTP app finishes its shutdown delay.

I tested moving PHP shutdown to a single process-global caddy.OnExit callback. On both releases, the local patch eliminated these 500s and the process exited cleanly. A 40-second PHP request already running at SIGTERM also completed successfully across the 30-second delay, including after two configuration reloads. That test explicitly allowed 90 seconds of PHP execution time. Worker-mode behavior was not tested.

Is deferring PHP shutdown until after the HTTP app has stopped the appropriate fix here?

Build Type

Docker (Debian Trixie)

Worker Mode

No

Operating System / CPU Architecture

GNU/Linux container on macOS/OrbStack; aarch64.

PHP configuration

Official image defaults, with no custom PHP configuration or additional extensions for the minimal reproduction.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions