Xdebug on demand

My MediaWiki test instance normally runs without Xdebug, since that gives good performance. But when I request a debug session in PHPStorm, Xdebug is automatically enabled.

I don’t often share config snippets since they are so specific to the way I have things set up. You will most likely have to adapt this to your situation. But at least it should help you to know that conditionally enabling Xdebug is possible.

I use PHP-FPM which, unlike Apache mod-php, allows you to run multiple versions of PHP. I use Apache to detect Xdebug’s cookie or query string. Then I have a separate instance of PHP-FPM just for Xdebug, with a separate php.ini.

I’m using Xdebug 3.0. I upgraded from Xdebug 2 for this project since I figured there is no point putting a lot of effort into an Xdebug 2 installation which I would almost certainly have to upgrade within a year.

I have a file called /etc/apache2/php.conf along the lines of:

<FilesMatch ".+\.php$">
	<If "%{HTTP_COOKIE} =~ /XDEBUG_SESSION/ || %{QUERY_STRING} =~ /XDEBUG_SESSION_START=/ || %{REQUEST_URI} =~ /intellij_phpdebug_validator/">
		SetHandler "proxy:unix:/run/php/php8.0-fpm-xdebug.sock|fcgi://localhost"
	</If>
	<Else>
		SetHandler "proxy:unix:/run/php/php8.0-fpm.sock|fcgi://localhost"
	</Else>
</FilesMatch>

I include that into any VirtualHost directives that need PHP execution.

<VirtualHost *:443>
	ServerName test.internal
	...
	Include php.conf
</VirtualHost>

The non-Xdebug instance of PHP-FPM is installed in the usual way, in my case using OndÅ™ej Surý’s PPA. I copied the systemd service file for PHP-FPM from /lib/systemd/system/php8.0-fpm.service to /etc/systemd/system/php-fpm-xdebug.service and changed the paths to avoid conflicts:

[Unit]
Description=The PHP 8.0 FastCGI Process Manager (with xdebug)
Documentation=man:php-fpm8.0(8)
After=network.target

[Service]
Type=notify
ExecStart=/usr/sbin/php-fpm8.0 --nodaemonize --fpm-config /etc/php/8.0/fpm-xdebug/php-fpm.conf -c /etc/php/8.0/fpm-xdebug
ExecStartPost=-/usr/lib/php/php-fpm-socket-helper install /run/php/php-fpm-xdebug.sock /etc/php/8.0/fpm-xdebug/pool.d/www.conf 80
ExecStopPost=-/usr/lib/php/php-fpm-socket-helper remove /run/php/php-fpm-xdebug.sock /etc/php/8.0/fpm-xdebug/pool.d/www.conf 80
ExecReload=/bin/kill -USR2 $MAINPID

[Install]
WantedBy=multi-user.target

/etc/php/8.0/fpm-xdebug is the configuration directory for the new instance of PHP-FPM, initially based on a copy of /etc/php/8.0/fpm. To avoid loading Xdebug into the non-Xdebug instance, I commented out the zend_extension line in /etc/php/8.0/mods-available/xdebug.ini , since the package has a bug which recreates the conf.d symlinks if you delete them.

I don’t usually use the default php.ini, I just wipe it after package installation and start with an empty file, since that makes it easier to see which defaults I’ve overridden. You lose the documentation comments, but I’m able to read the manual, so that’s fine.

My /etc/php/8.0/fpm-xdebug/php.ini has:

zend_extension = xdebug.so
xdebug.mode = debug
xdebug.client_host = 169.254.1.1

Replace 169.254.1.1 with the address of the host on which your IDE runs, as seen by the container. I am using systemd-nspawn with bridged networking, which allows PHP to connect to the host using link-local addresses.

A few more little things to avoid conflicts with the main PHP-FPM instance. Like php.ini, I started with empty files, not default files. /etc/php/8.0/fpm-xdebug/php-fpm.conf contains only:

[global]
pid = /run/php/php8.0-fpm-xdebug.pid
error_log = /var/log/php8.0-fpm-xdebug.log

include=/etc/php/8.0/fpm-xdebug/pool.d/*.conf

And /etc/php/8.0/fpm-xdebug/pool.d/www.conf:

[www]
user = www-data
group = www-data
listen = /run/php/php8.0-fpm-xdebug.sock
listen.owner = www-data
listen.group = www-data
pm = dynamic
pm.max_children = 5
pm.start_servers = 1
pm.min_spare_servers = 1
pm.max_spare_servers = 3

I think that’s it. Now it should just be a matter of

systemctl daemon-reload
systemctl start php-fpm-xdebug
systemctl enable php-fpm-xdebug
systemctl reload apache2

To debug a web request, set a breakpoint, then in Run > Edit Configurations, create a “PHP Web Page” configuration. The URL you use here does not have to be the exact one you want to debug, it just has to be in the same cookie domain. Then Run > Debug… and select the configuration. This will spawn a browser tab which activates Xdebug with the XDEBUG_SESSION_START query string parameter. Xdebug modifies the response to set the XDEBUG_SESSION cookie.

Since XDebug 3.1, the cookie has no expiry time set, but you can append XDEBUG_SESSION_STOP=1 to the query string to cause it to remove its session cookie. With no session cookie, you will automatically be back to PHP without Xdebug.

You can get PHPStorm to run command-line scripts via SSH, but I find it’s most convenient to run them in the usual way, with the PHPStorm option “Listen for PHP Debug Connections” enabled.

I use a shell script wrapper along the lines of:

XDEBUG_TRIGGER=1 PHP_IDE_CONFIG=serverName=test.internal \
  php -dzend_extension=xdebug.so -dxdebug.mode=debug -dxdebug.client_port=9000 \
  -dxdebug.client_host=169.254.1.1 "$@"

The serverName configuration variable here is necessary to select the right path mappings — it must correspond to the name of the server in PHPStorm’s File > Settings > PHP > Servers.

3 Comments

  1. It’s an old blog post, but i always thought it was odd there was no good way of activating xdebug on demand, since it is heavy on systems resources even if set to not start with a request (i.e.trigger).

    I run my setup on apache, and you can’t modify xdebug.mode other than from php.ini, so i just modified xdebug.c and recompiled xdebug, which is quite trivial on ubuntu, using phpize and the tutorial on the xdebug website. For anyone interested just find the line:

    PHP_INI_ENTRY( “xdebug.mode”, “develop”, PHP_INI_SYSTEM, OnUpdateMode)

    Change PHP_INI_SYSTEM to PHP_INI_ALL. That’s all. And *don’t* run this on a public facing website of course! To use it, set xdebug.mode in .htaccess or with init_set(). Or as i did in apache2.conf using a conditional, since i use a debug extension using chrome.

    php_value xdebug.mode debug

    Obviously the rest of the configuration is done in php.ini, but with xdebug.mode set to Off as a default.

  2. It would seem i was jumping the gun on this. It did work out nicely in my initial testing (single page). However when trying it out on some real life (wordpress) projects it failed miserably. You may remove my comments if you wish, since they don’t serve a purpose.

Leave a Reply to PK Cancel reply

Your email address will not be published. Required fields are marked *