skip to content

Step Debugging Setup

Step debugging connects PHP to an IDE over DBGp on port 9003, per request or via the XDEBUG_TRIGGER cookie. Interviewers ask why a breakpoint never hits, especially inside containers.

on this pageshow

explore

questions

5

With Xdebug 3, what must you configure to step-debug PHP code, and which side opens the debugging connection?

level: juniorimportance: must knowfreq 55%

answer

  1. default mode is develop
  2. xdebug.mode=debug
  3. PHP calls the IDE, not the reverse
  4. DBGp over TCP port 9003
  5. client_host defaults to localhost

basics

~10 s

Set xdebug.mode=debug, have the IDE listen for DBGp connections on port 9003, and start a session with a trigger; Xdebug, running inside PHP, opens the connection to the IDE at xdebug.client_host:xdebug.client_port.

solid answer

~40 s

Xdebug's default `xdebug.mode` is `develop`, which only enables the development helpers, so step debugging needs `xdebug.mode=debug` (or a list such as `develop,debug`) in a php.ini file read at process start. The IDE then starts **listening**, and when a request carries a trigger, Xdebug **connects out** to the IDE using the DBGp protocol over TCP, to `xdebug.client_host` (default `localhost`) on `xdebug.client_port` (default `9003`). The IDE never connects into PHP; it waits for PHP to call. With the default `xdebug.start_with_request=default`, debug mode behaves as `trigger`, so nothing happens until a request carries `XDEBUG_TRIGGER` or `XDEBUG_SESSION`, or code calls `xdebug_break()`. When PHP and the IDE run on the same machine, those defaults are all you need.

code

ini · 6 lines
ini
; conf.d/99-xdebug.ini
zend_extension=xdebug
xdebug.mode=develop,debug
xdebug.client_host=localhost   ; default: IDE on the same machine
xdebug.client_port=9003        ; default since Xdebug 3
xdebug.log=/tmp/xdebug.log      ; record connection attempts

go deeper

for a junior

Recall that step debugging needs xdebug.mode=debug, that the IDE listens on port 9003, and that Xdebug starts the connection.

for a middle

Explain why the default mode is develop, why the direction of the connection matters for client_host, and why debug mode waits for a trigger.

for a senior

Show you can reason about which ini file each SAPI loads, verify the active mode with xdebug_info(), and reach the IDE from remote PHP.

for a principal

Set team defaults: debugging off unless triggered, a documented shared config, and no Xdebug in production images.

## What step debugging is **Step debugging** means pausing a running PHP script at a breakpoint, inspecting variables, and executing it line by line from an IDE. **Xdebug** is the PHP extension that makes the engine stoppable, and it speaks **DBGp**, an open debugger protocol that most PHP IDEs and editors implement. Two roles are involved: - the **debugger engine**, Xdebug inside the PHP process; - the **debugging client**, the IDE or editor plugin that shows the code and sends "step over" or "evaluate" commands. ## Step 1: turn on debug mode Xdebug 3 enables features through a single setting, `xdebug.mode`. Its default is **`develop`**, which gives you the improved `var_dump()` and error output but **no step debugging**. ```ini ; 99-xdebug.ini (loaded when PHP starts) zend_extension=xdebug xdebug.mode=debug ; or develop,debug to keep the helpers ``` `xdebug.mode` can only be set in files read when the PHP process starts, such as `php.ini` or a `conf.d` file. It is **not** honoured in `.htaccess`, `.user.ini` or a PHP-FPM pool's `php_admin_value`. After changing it, restart PHP-FPM or the web server, because a running worker keeps the value it started with. ## Step 2: understand who connects to whom This is the part that surprises people used to attaching a debugger to a process: 1. The IDE opens a **listening socket**, which is its "start listening for debug connections" button. 2. A request starts in PHP, and Xdebug decides whether to debug it (Step 3). 3. If so, **Xdebug connects out** to `xdebug.client_host:xdebug.client_port`. 4. The IDE accepts, sets its breakpoints over DBGp, and the request runs until it hits one. | Setting | Default | Meaning | |---|---|---| | `xdebug.client_host` | `localhost` | where the IDE is listening | | `xdebug.client_port` | `9003` | the port the IDE listens on | | `xdebug.connect_timeout_ms` | `200` | how long Xdebug waits for the IDE to accept | Because the connection goes **from PHP to the IDE**, the machine running the IDE must accept an incoming connection on 9003. When PHP runs elsewhere, whether a VM, a remote server or a container, `client_host` must be an address **as seen from PHP** that reaches the IDE. ### Where client_host should point | PHP runs on | Typical `xdebug.client_host` | |---|---| | the same machine as the IDE | `localhost` (the default) | | a VM or remote server | the IDE machine's address as seen from that server | | a container on your laptop | a name that reaches the host, such as `host.docker.internal` | | a shared server used by several developers on one network | `xdebug.discover_client_host=1`, which connects back to the requesting address | The last option has no filter: anyone who can reach the web server can make it connect back to them, so it belongs on trusted networks only. ## Step 3: decide which requests are debugged `xdebug.start_with_request` decides when Xdebug tries to connect. Its default value, `default`, means **`trigger`** in debug mode: Xdebug only connects when the request carries a trigger, such as an `XDEBUG_TRIGGER` or `XDEBUG_SESSION` cookie, GET or POST variable, or environment variable, or when the code calls `xdebug_break()`. Setting it to `yes` makes every request try. ## Checking that it works - `xdebug_info()` shows an HTML diagnostics page, like `phpinfo()` for Xdebug, with the active modes, every setting, and any connection errors. - `xdebug_info('mode')` returns the enabled modes as an array, for example `['debug']`. - Setting `xdebug.log=/tmp/xdebug.log` records every connection attempt. ## Common first-time mistakes - Leaving `xdebug.mode` at `develop` and wondering why the IDE never reacts. - Editing the CLI `php.ini` while the web server's PHP-FPM reads a different one; check with `xdebug_info()` from a web request. - Expecting the IDE to connect to PHP, and opening port 9003 on the server instead of the IDE's machine. - Following an Xdebug 2 tutorial that uses `xdebug.remote_enable` and port 9000, which Xdebug 3 no longer uses.

  • Why can you not set xdebug.mode in a .user.ini file or with php_admin_value?
    Xdebug decides which features to hook into the engine when the PHP process starts, so `xdebug.mode` is only read from ini files loaded at startup. Per-directory `.user.ini` files and FPM pool `php_admin_value` entries are applied later, per request or per pool, which is too late for Xdebug to act on them.
  • Your IDE is listening, xdebug.mode=debug is set, yet nothing happens on page load. What is the most likely reason?
    The default start mode for debugging is `trigger`, so Xdebug only connects when the request carries `XDEBUG_TRIGGER` or `XDEBUG_SESSION`, as a cookie, GET/POST variable or environment variable, or when code calls `xdebug_break()`. Without a trigger it stays silent by design. Add the trigger, for example with a browser helper extension.

Step debugging with Xdebug is like a callback service: the IDE sits by its phone (listening on 9003), and PHP places the call when a request asks for it. If the IDE is not by the phone, or PHP dials the wrong number, nothing connects, and the IDE could never have called PHP instead.

saying these in an interview costs you the question

  • Installing Xdebug enables step debugging by default
  • The IDE connects into the PHP process on port 9003
  • xdebug.remote_enable=1 turns on the debugger in Xdebug 3
  • Setting xdebug.mode in .user.ini is enough
  • Xdebug connects to the IDE on every request by default
open as a page

In Xdebug 3, how do xdebug.start_with_request=yes and =trigger differ, and how do XDEBUG_TRIGGER and XDEBUG_SESSION start a session?

level: middleimportance: must knowfreq 45%

basics

~20 s

With start_with_request=yes, Xdebug tries to reach the IDE at the start of every request; with trigger, the default for debug mode, it connects only when an XDEBUG_TRIGGER or legacy XDEBUG_SESSION GET, POST, cookie or environment variable is present, or when xdebug_break() runs.

open as a page

Why does Xdebug 3 use port 9003, and which settings replaced Xdebug 2's xdebug.remote_* step-debugging options?

level: middleimportance: should knowfreq 35%

basics

~10 s

Xdebug 3 moved the default debug port from 9000, which PHP-FPM also listens on by default, to 9003, and replaced remote_enable with mode=debug, remote_host/remote_port with client_host/client_port, remote_autostart with start_with_request=yes and remote_connect_back with discover_client_host.

open as a page

How do you use xdebug_info() and xdebug.log to find out why an Xdebug 3 step-debugging session never starts?

level: middleimportance: should knowfreq 30%

basics

~20 s

Call xdebug_info() from the same SAPI to confirm debug mode, the effective settings and recent errors, then set xdebug.log to a writable file; its entries show whether the trigger was found and which host and port Xdebug tried and why it failed.

open as a page

PHP runs in a Docker container and your host IDE never receives an Xdebug connection; how do you configure Xdebug 3 so step debugging works?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Inside a container localhost is the container, so set xdebug.client_host to an address that reaches the host, such as host.docker.internal, keep discover_client_host off, let the IDE accept connections on 9003, and map container paths to local paths in the IDE.

open as a page