Why do teams keep the Xdebug 3 extension unloaded on production PHP servers instead of relying on xdebug.mode=off?
answer
- off is close to zero, not zero
- default and fallback are develop
- one environment variable re-enables it
- any trigger value accepted by default
- trace and error output leak data
basics
~20 sWith xdebug.mode=off Xdebug costs little, but a missing or invalid mode means develop, XDEBUG_MODE can switch features on, and any active mode slows every call and can write request data to disk or screen. Not loading it removes those risks.
solid answer
~40 s`xdebug.mode=off` makes Xdebug skip nearly all its work, and the docs describe that as close to zero overhead. The risk is everything around it. If the mode line is missing the default is `develop`, and an invalid value falls back to `develop`, so a config slip puts per-call bookkeeping and verbose error output on every request. The `XDEBUG_MODE` environment variable overrides php.ini, so one stray variable enables profiling or tracing. `profile` defaults to every request, filling the disk, and with an empty `xdebug.trigger_value` anyone who sends `XDEBUG_TRIGGER` can start a profile or trace. Traces, and develop-mode stack traces with `xdebug.show_local_vars`, write argument values such as passwords to files or pages. So production images simply do not ship the extension; you reproduce on staging or use a sampling profiler.
code
bash · 5 lines# production image check: the extension must not be present at all
if php -m | grep -qi '^xdebug$'; then
echo 'Xdebug is loaded in the production image' >&2
exit 1
figo deeper
Recall that Xdebug is a development tool that slows PHP down and should not be installed on production servers.
Explain that xdebug.mode=off is cheap but that the develop default, the fallback and XDEBUG_MODE make a loaded extension easy to switch on.
Show the production view: the per-mode costs, triggers usable by any visitor, sensitive data in traces and error pages, and a build check that keeps the extension out.
Own the policy: separate images for production and development, where slow requests get reproduced, and which always-on profiling tool fills the gap Xdebug leaves.
## What mode=off actually does Xdebug 3 decides its **mode** once, when the PHP process starts. With `xdebug.mode=off`, the extension returns early from its startup and per-request hooks: it registers no opcode handlers, keeps no stack of frames and writes no files. The Xdebug documentation describes this as doing "no work besides checking whether functionality is enabled" and recommends `off` when you want **close to zero** overhead. Xdebug 3's mode system was designed so that overhead is paid only for the features you ask for. So the case for keeping Xdebug out of production is not that `off` is slow. It is that **"loaded but off" is one configuration mistake away from "loaded and on"**, and every "on" state is costly or dangerous on a server that handles real traffic. ## How a loaded Xdebug gets switched back on | Slip | Result | |---|---| | No `xdebug.mode` line in the production ini | Default mode `develop` | | Unrecognised value such as `xdebug.mode=disabled` | Logged as invalid, falls back to `develop` | | `XDEBUG_MODE=profile` left in a service's environment | Overrides php.ini for that process | | A dev ini file copied into the image | Whatever mode developers use, often `debug,develop` | The fallback deserves emphasis: an invalid value does **not** fall back to `off`. And `XDEBUG_MODE` takes precedence over `xdebug.mode` without changing the value `ini_get()` reports, so a check of the ini file alone can look clean. ## What each active mode costs in production - **develop** keeps its own record of every stack frame and hooks error display, so every function call does extra work; error pages gain full stack traces with argument values, and with `xdebug.show_local_vars=1` local variables too. - **profile** defaults to `start_with_request=yes`, so every request writes a Cachegrind file into `xdebug.output_dir`. Under load this fills the disk, and the instrumentation inflates response times across the board. - **trace** writes a line per function call, including arguments (`xdebug.collect_params` is on by default), and, with `trace` in the mode, Xdebug switches off OPcache's optimizer. Trace files are huge and hold passwords, tokens and personal data in plain text. - **debug**, when triggered, makes PHP open a connection to a debugging client, adding a connection attempt (`xdebug.connect_timeout_ms`, 200 ms by default) and handing control of execution to whatever client answers. - **coverage** instruments every executed opcode and is meant only for test runs. ## Triggers are not access control In `trigger` mode Xdebug starts a feature when `XDEBUG_TRIGGER` (or a legacy name) appears in the query string, POST data, a cookie or the environment. With `xdebug.trigger_value` empty — the default — **any value** works. On a public server that means any visitor who adds `?XDEBUG_TRIGGER=1` can make your server write a profile or trace, which is both a denial-of-service lever (disk and CPU) and a data leak if files are ever exposed. A secret `trigger_value` helps on shared staging, but it is a shared password, not authorisation. ## What teams do instead 1. **Build separate images or packages.** The production image never installs the Xdebug package, so no ini file, variable or trigger can activate it. Development and CI images install it with `xdebug.mode=off` and enable features per command with `XDEBUG_MODE`. 2. **Verify it.** A deploy check such as `php -m` listing no Xdebug entry in the production image makes the rule testable. 3. **Reproduce slow requests elsewhere.** Copy the slow request to a staging environment with production-like data and profile it there with a trigger. 4. **Use tools built for production.** Continuous sampling profilers and APM agents are designed to stay on with bounded overhead; choosing and reading them belongs to observability and profiling practice, not to Xdebug. ## The interview answer in one line Xdebug's off mode is cheap, but production should not depend on every server's ini files and environment staying perfect: a missing mode means develop, a bad one means develop, one variable means profile, and any visitor can pull a trigger. Leaving the extension out makes all of that impossible.
- A staging server needs Xdebug for occasional profiling. How do you limit the risk?Keep `xdebug.mode=profile` with `xdebug.start_with_request=trigger`, set a non-empty `xdebug.trigger_value` so only a shared secret starts it, point `xdebug.output_dir` at a directory that is never web-served, and clean it regularly. Keep `trace` and `debug` out of the mode, and never let that image or ini file reach production.
- Your production php.ini says xdebug.mode=off, yet a request produced a cachegrind file. What do you check?Check the environment of the PHP-FPM or CLI process for `XDEBUG_MODE`, which overrides the ini setting without changing what `ini_get()` reports, and check every ini file in the scan directory for a second `xdebug.mode` line. Then take the extension out of the production image so the question cannot come back.
saying these in an interview costs you the question
- An invalid xdebug.mode value falls back to off, so typos are harmless
- xdebug.mode=off still instruments every function call
- Trigger mode means only developers can start a profile
- Trace files only contain function names, so they are safe to leave on disk
- Checking php.ini is enough to know which Xdebug mode a process runs