Skip to main content

Xdebug on a Server: Step Debugging and Profiling PHP Code

PHP · 29.09.2026

Xdebug is a PHP extension for step debugging and profiling code. It intercepts script execution, shows the call stack on an error, and can measure the execution time of every function. Xdebug must not be enabled in production: it slows execution down several times over and opens an extra attack surface, so install it only on staging or in a developer container.

Why use Xdebug when there is error_log

A plain error_log shows what crashed but not why a variable ended up holding the wrong value. Xdebug lets you set a breakpoint on a specific line, inspect every variable in the current scope, and step through the code right inside an IDE. For analyzing fatals that already happened without stopping the process, see the article on parsing PHP error logs.

A separate mode is profiling. It does not stop the code but records the execution time of every function into a cachegrind-format file, which is then opened in KCachegrind or Webgrind to find the slowest spot in a request.

Installing on a server with Ubuntu and Debian

Xdebug is installed as a regular PECL extension on top of an already installed PHP. The extension version must match the PHP-FPM version:

sudo apt install php8.3-dev php-pear
sudo pecl install xdebug
echo "zend_extension=xdebug.so" | sudo tee /etc/php/8.3/mods-available/xdebug.ini
sudo phpenmod xdebug
sudo systemctl restart php8.3-fpm

If a server runs several PHP versions at once, Xdebug needs to be installed for each one separately — see the article on multiple PHP versions on one server.

Configuring step debug mode

Since Xdebug 3 the configuration became explicit: the working mode is set with a single directive instead of a dozen flags.

SettingValuePurpose
xdebug.modedebugenables step debugging
xdebug.client_hostIDE IPwhere to send the debug signal
xdebug.client_port9003port the IDE listens on
xdebug.start_with_requesttriggerdebug only on a flag in the request

The trigger value on the last setting matters: without it, Xdebug tries to connect to the IDE on every request and noticeably slows down even staging.

Common connection problems

The debugger almost always fails to connect to the IDE for one of these reasons:

  • The server firewall blocks outbound traffic on port 9003 — add an allow rule for the staging environment.
  • The IDE listens on the wrong port: PhpStorm defaults to 9003, but an old guide may point to 9000, which conflicts with FastCGI.
  • The server path and the IDE path do not match through path mapping — in that case breakpoints simply never trigger.
  • Xdebug and OPcache with JIT are both loaded in php.ini at the same time — combining them cuts the benefit of both and confuses profiling.

Profiling a slow request

To find the bottleneck in one specific handler, turn on profile mode selectively, through a URL parameter, without touching the global config:

xdebug.mode=profile
xdebug.start_with_request=trigger
xdebug.output_dir=/var/log/xdebug
# request example:
# https://staging.example.com/checkout?XDEBUG_PROFILE=1

The cachegrind.out.* file shows a call tree with the time of every function. Often it turns out the slowdown is not PHP itself but an extra database query inside a loop — visible right away from the number of repeats of the same function.

Checklist for safe use

Xdebug is a development tool, not part of production infrastructure. Stick to this order:

  1. Install Xdebug only on staging or locally; in production keep xdebug.mode=off or do not load the extension at all.
  2. Use start_with_request=trigger so debugging does not turn on for every random request.
  3. Delete old cachegrind files after profiling: they can pile up to several gigabytes in a single day.
← Back to Knowledge Base Ask Support