Graceful Shutdown
OxPHP handles SIGTERM and SIGINT so that in-flight requests complete before the process exits. This matters for zero-downtime deployments and rolling updates in container orchestration.
Signal handling
OxPHP responds to two shutdown signals:
| Signal | Source | Behavior |
|---|---|---|
SIGTERM |
Container orchestrators, docker stop, kill |
Initiates graceful shutdown |
SIGINT |
Terminal Ctrl+C | Initiates graceful shutdown |
Both signals trigger the same shutdown sequence. Only the first signal is needed. The server begins draining immediately.
Shutdown sequence
When a shutdown signal is received, OxPHP follows this sequence:
- Stop accepting new connections — the server stops accepting new TCP connections on the main port. PHP workers continue running to process in-flight requests.
- Wind down live connections — HTTP/2 clients receive a
GOAWAYframe and idle HTTP/1.1 keep-alive connections are closed, so clients move to a healthy instance instead of multiplexing new requests into the dying one. Open streams are ended promptly and cleanly — any response that has started flushing chunked output counts, finite downloads as well as SSE — see Server-Sent Events. - Drain in-flight requests — ordinary active requests are left alone to finish with their full responses. The server checks for completion every 100ms. The internal health/metrics server remains available throughout the drain, so readiness probes continue to work.
- Enforce the drain deadline — requests still running after
DRAIN_TIMEOUT_SECONDSare cancelled (theirregister_shutdown_function()callbacks still run) and given ~2 seconds to unwind before the server proceeds. - Flush plugins — access-log entries and APM spans buffered during the drain window are flushed.
- Shut down async pool — the background async task pool is stopped.
- Abort the internal server — the health/metrics server is stopped after the drain completes.
- Exit — the process exits with status code 0.
PHP workers are stopped explicitly during step 1 — the executor's shutdown() is called as part of stopping the main server, which signals worker threads to exit after finishing any in-progress request.
Configuration
| Variable | Default | Description |
|---|---|---|
DRAIN_TIMEOUT_SECONDS |
25 |
Maximum seconds in-flight requests get to complete before being cancelled; the process exits within ~2 seconds after the deadline. The default leaves headroom for the post-deadline unwind and telemetry flush inside Kubernetes' default 30-second termination grace period |
Set DRAIN_TIMEOUT_SECONDS to accommodate your slowest expected request:
- API servers with fast responses:
10–15seconds - Applications with file uploads or long queries:
30–60seconds - Worker mode with background processing: match your longest expected operation
Kubernetes
In Kubernetes, the shutdown flow during a rolling update is:
- Kubernetes sends
SIGTERMto the pod. - The pod is removed from the Service endpoint list.
- OxPHP drains in-flight connections within
DRAIN_TIMEOUT_SECONDS, then cancels stragglers and exits within ~2 more seconds. - If the pod is still running after
terminationGracePeriodSeconds, Kubernetes sendsSIGKILL.
Set terminationGracePeriodSeconds above DRAIN_TIMEOUT_SECONDS + 2 so the drain — including the post-deadline unwind and telemetry flush — completes before the forced kill:
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
terminationGracePeriodSeconds: 45
containers:
- name: oxphp
image: ghcr.io/oxphp/oxphp:0.10.0
env:
- name: DRAIN_TIMEOUT_SECONDS
value: "30"Pre-stop hook
If your service receives traffic from external load balancers that propagate endpoint changes slowly, add a pre-stop hook to delay the shutdown sequence:
lifecycle:
preStop:
exec:
command: ["sleep", "5"]This gives the load balancer time to remove the pod from its target list before OxPHP stops accepting connections.
Docker
Docker sends SIGTERM when you run docker stop. The default Docker stop timeout is 10 seconds, after which Docker sends SIGKILL.
To give OxPHP enough time to drain, increase the stop timeout:
docker stop --time 45 my-oxphp-containerOr set it in your Compose file:
services:
oxphp:
image: ghcr.io/oxphp/oxphp:0.10.0
stop_grace_period: 45s
environment:
DRAIN_TIMEOUT_SECONDS: "30"Log messages
During a graceful shutdown, OxPHP emits structured log messages you can monitor:
Successful drain:
{"level":"INFO","message":"Received shutdown signal, draining connections"}
{"level":"INFO","message":"Draining in-flight connections","active_connections":3}
{"level":"INFO","message":"All connections drained"}
{"level":"INFO","message":"Server stopped"}Drain deadline reached:
{"level":"INFO","message":"Received shutdown signal, draining connections"}
{"level":"WARN","message":"Drain timeout reached, cancelling in-flight requests","remaining_connections":1}
{"level":"INFO","message":"All connections drained"}
{"level":"INFO","message":"Server stopped"}If you regularly see the "Drain timeout reached" warning, increase DRAIN_TIMEOUT_SECONDS or investigate long-running requests using the oxphp_request_duration_us histogram.
See also
- Health Checks — readiness probes and shutdown interaction
- Configuration Reference — all environment variables including
DRAIN_TIMEOUT_SECONDS - Metrics —
oxphp_active_connectionstracks connections during drain