Getting Xdebug in PHPStorm with Docker to Run Stably
AI generated
IDE
{ }
PHPStorm · Xdebug 3 · Docker · Debugging · Magento
Getting Xdebug in PHPStorm with Docker
to run stably

Xdebug with Docker is not a one-click setup: host.docker.internal, port 9003, path mappings, the correct xdebug.mode, and PHPStorm server configuration all have to line up. This article systematically puts every piece into the right position, for stable breakpoints in Magento 2 and other PHP projects.

17 min read xdebug.ini · host-gateway · port 9003 · path mappings · breakpoints Xdebug 3.x · PHPStorm 2024+ · Docker · Magento 2.4 · PHP 8.4

1. How Xdebug, Docker, and PHPStorm work together

To configure Xdebug with Docker and PHPStorm stably, you need to understand the communication architecture. Xdebug is a PHP extension that runs inside the PHP container. When PHP reaches a breakpoint, Xdebug initiates a TCP connection back to the IDE, not the other way around. That means the container needs to be able to reach the developer machine (the host). PHPStorm listens on a port (9003 by default in Xdebug 3) for incoming Xdebug connections.

This direction of the connection is the core of the Docker problem. Inside the container, the host is not automatically reachable. On macOS and Windows, Docker Desktop provides host.docker.internal, a DNS name that points to the host IP. On Linux, this name does not exist by default. There it has to be defined explicitly via extra_hosts in the compose configuration: host.docker.internal:host-gateway. host-gateway is a Docker-internal alias for the gateway IP of the Docker bridge network, which is typically the host's IP within the container network.

2. xdebug.ini: the complete configuration for Docker

The Xdebug configuration is defined in a separate xdebug.ini file that gets included as a PHP extension configuration. The most important settings for Xdebug 3 in Docker: xdebug.mode=debug enables the step debugger. xdebug.start_with_request=yes starts Xdebug automatically on every PHP request. xdebug.client_host=host.docker.internal specifies the address Xdebug connects to. xdebug.client_port=9003 is the default port for Xdebug 3 (Xdebug 2 used 9000).

The setting xdebug.start_with_request=yes means Xdebug starts on every request, even when PHPStorm is not waiting for a connection. That leads to a brief timeout on every request when PHPStorm is not listening. An alternative is xdebug.start_with_request=trigger: Xdebug only starts when a special cookie, header, or GET parameter is present. That is better for day-to-day performance, but requires browser extensions or manual headers for the trigger.


<?php
// docker/phpfpm/xdebug.ini - Xdebug 3 for Docker + PHPStorm
/*
[xdebug]
; Core settings
xdebug.mode=debug
xdebug.start_with_request=yes
xdebug.client_port=9003
xdebug.client_host=host.docker.internal
xdebug.idekey=PHPSTORM

; Logging (disable in production, enable for troubleshooting)
xdebug.log_level=0
; xdebug.log=/tmp/xdebug.log

; Magento-specific: increase nesting level
xdebug.max_nesting_level=512

; Performance: disable coverage when not needed
; Set to 'debug,coverage' only when running PHPUnit with coverage

; For trigger-mode (better performance, needs browser extension):
; xdebug.start_with_request=trigger
; xdebug.trigger_value=PHPSTORM
*/

// Verify Xdebug is loaded in container:
// bin/cli php -v | grep Xdebug
// Expected: Xdebug v3.x.x, Copyright (c) ...

// Check which settings are active:
// bin/cli php -r "var_dump(xdebug_info());"

3. Linux-specific issue: configuring host-gateway

The Linux-specific problem with Xdebug and Docker is the most common obstacle for PHP developers who switch from macOS to Linux or use Linux as their development platform. The Mark Shust Docker setup has the file compose.dev-linux.yaml for this case, which contains Linux-specific configurations. Alternatively, you can add the extra_hosts flag directly to the main compose.yaml if only Linux is used as the development platform.

After adding extra_hosts: - "host.docker.internal:host-gateway" to the phpfpm service, the container needs to be restarted. To verify: inside the container, run ping host.docker.internal or curl -v telnet://host.docker.internal:9003 while PHPStorm is currently waiting for connections. A response (even a connection refusal, as long as PHPStorm is not listening) shows that DNS resolution works. If the ping fails, extra_hosts is not configured correctly.


<?php
// compose.dev-linux.yaml - Linux-specific Xdebug fix
/*
services:
  phpfpm:
    extra_hosts:
      - "host.docker.internal:host-gateway"
    environment:
      PHP_IDE_CONFIG: "serverName=mironsoft-docker"
      XDEBUG_CONFIG: "client_host=host.docker.internal"

# Use with: docker compose -f compose.yaml -f compose.dev-linux.yaml up -d

# Alternative: add directly to compose.yaml if Linux-only setup

# Verification steps:
# 1. Start containers: bin/start (or docker compose up -d)
# 2. Enter container: bin/bash
# 3. Test connectivity: ping -c 1 host.docker.internal
# 4. Should resolve to Docker bridge IP (172.17.0.1 or similar)
# 5. Enable PHPStorm debug listening (phone icon in toolbar)
# 6. Test: nc -zv host.docker.internal 9003
#    Should show: Connection to host.docker.internal 9003 port [tcp/*] succeeded
*/

4. PHPStorm server configuration and path mappings

On the PHPStorm side, two configuration areas are relevant for Xdebug: the debug settings and the server configuration. The debug settings are found under Settings → PHP → Debug: set the Xdebug port to 9003, enable "Can accept external connections". The server configuration under Settings → PHP → Servers defines how PHPStorm maps file paths from the Xdebug protocol onto local files.

The server configuration: Settings → PHP → Servers → +. Name: mironsoft-docker (must match the value in PHP_IDE_CONFIG=serverName=mironsoft-docker). Host: localhost. Port: 80. Debugger: Xdebug. Enable "Use path mappings". Then map the local project directory /home/mir/development/mironsoft/src to the container path /var/www/html. This mapping step is critical: without it, PHPStorm does not open a local file when a breakpoint is hit, and instead shows a "Cannot open file" error.


<?php
// PHPStorm Server Configuration Summary
// Settings → PHP → Servers

/*
Server Settings:
- Name: mironsoft-docker
  (matches PHP_IDE_CONFIG="serverName=mironsoft-docker" in container)
- Host: localhost
- Port: 80 (or 443 for HTTPS)
- Debugger: Xdebug

Path Mappings (Use path mappings ✓):
Local Path                                    → Absolute path on server
/home/mir/development/mironsoft/src           → /var/www/html

Settings → PHP → Debug:
- Debug port: 9003
- ✓ Can accept external connections
- ✓ Force break at first line when no path mapping specified
- ✓ Force break at first line when a script is outside the project

// Environment variable in container (compose.yaml):
// environment:
//   PHP_IDE_CONFIG: "serverName=mironsoft-docker"

// PHPStorm uses PHP_IDE_CONFIG to auto-select the server
// when a new Xdebug session arrives
*/

5. Enabling debug listening and testing the first breakpoints

Once all settings are configured, the workflow for a debugging session is: put PHPStorm into "Listen" mode (phone icon in the top toolbar or Run → Start Listening for PHP Debug Connections). Then open the Magento page that should be debugged in the browser. If xdebug.start_with_request=yes is active, Xdebug connects automatically. If trigger mode is active, the Xdebug cookie or header must be set (via a browser extension like "Xdebug Helper").

On the first successful connection, PHPStorm either shows the Incoming Connection dialog (if PHPStorm doesn't know which server to use) or halts directly at the first breakpoint. If the dialog appears, select the server there and path mappings can be confirmed directly. A common first test: set a breakpoint in index.php (the Magento entry point) and open any frontend page. If PHPStorm halts there, the entire setup is working correctly.

6. CLI debugging: debugging Magento commands

Xdebug works not only for web requests but also for CLI commands. This is especially useful for Magento, because many core processes run as CLI commands: setup:upgrade, indexer:reindex, data migration, and custom CLI commands. To debug a CLI command, PHP_IDE_CONFIG has to be set in the shell environment and Xdebug has to be enabled for CLI.

The Mark Shust setup has the script bin/debug-cli enable for this, which activates the necessary PHP configuration for CLI debugging. Alternatively, set the XDEBUG_SESSION environment variable directly: XDEBUG_SESSION=PHPSTORM bin/magento cache:flush. PHPStorm has to be in listen mode. The CLI process inside the container then connects to PHPStorm via host.docker.internal:9003. Path mappings have to be correct, identical to those for web requests.

7. Xdebug and performance: enable only when needed

Xdebug carries a significant performance overhead. With Xdebug enabled (even without an active debugging session), PHP runs slower: the overhead from instrumenting every function call and checking for debug sessions is, depending on the application, 10 to 50 percent more execution time. For Magento 2, which is resource-intensive to begin with, that is especially noticeable: pages load noticeably slower, CLI commands take longer.

The recommended strategy: don't leave Xdebug permanently enabled. The Mark Shust setup has bin/xdebug enable and bin/xdebug disable, scripts that enable or disable Xdebug in the container by changing the PHP configuration without a container restart. Alternatively, use trigger mode: xdebug.start_with_request=trigger. Then PHP runs without overhead until an explicit trigger cookie or header is set. The browser extension "Xdebug Helper" makes setting the trigger possible with a single click.

8. Common errors and their solutions, compared

Symptom Cause Solution Verification
PHPStorm is listening, but the breakpoint is never hit host.docker.internal is not resolvable (Linux) extra_hosts: host.docker.internal:host-gateway Inside the container: ping host.docker.internal
PHPStorm does not open a file at the breakpoint Path mappings are missing or wrong Settings → PHP → Servers → Path Mappings Local: /src → Container: /var/www/html
Xdebug connects, but the wrong server PHP_IDE_CONFIG serverName is missing PHP_IDE_CONFIG=serverName=mironsoft-docker Incoming Connection dialog shows server selection
Port 9003 is occupied, connection fails Another process is using port 9003 ss -tulpn | grep 9003, terminate the process PHPStorm: Settings → PHP → Debug → Port
Xdebug is active, but there is no connection PHPStorm is not in listen mode Run → Start Listening for PHP Debug Connections Phone icon in the toolbar is green/active

9. Summary

Configuring Xdebug with Docker and PHPStorm stably requires the correct interplay of four components: the Xdebug INI inside the container (mode, client_host, client_port), Docker network configuration (host-gateway on Linux), PHPStorm server configuration with path mappings, and enabled debug listening mode. When all four parts are correct, debugging works reliably, for web requests, GraphQL requests, and CLI commands alike.

Xdebug with Docker and PHPStorm: the essentials at a glance

xdebug.ini

mode=debug, start_with_request=yes, client_host=host.docker.internal, client_port=9003, max_nesting_level=512.

Linux: host-gateway

extra_hosts: host.docker.internal:host-gateway in the phpfpm service. Restart the container. Verification: ping host.docker.internal inside the container.

PHPStorm Server

Settings → PHP → Servers. Name = serverName in PHP_IDE_CONFIG. Path mapping: local /src → container /var/www/html.

Performance

Enable Xdebug only when needed. bin/xdebug enable|disable or trigger mode. Browser extension for the trigger cookie.

10. FAQ: Xdebug in PHPStorm with Docker

1Why doesn't Xdebug work out of the box on Linux?
No host.docker.internal on Linux without Docker Desktop. Solution: extra_hosts: host.docker.internal:host-gateway in the phpfpm service of compose.yaml.
2Xdebug 2 port 9000 vs. Xdebug 3 port 9003?
Xdebug 3 changed the port to 9003 due to conflicts with PHP-FPM on 9000. PHPStorm and xdebug.ini must consistently point to 9003.
3What is PHP_IDE_CONFIG?
Maps the Xdebug connection to the correct PHPStorm server. Value: serverName=mironsoft-docker. Must match the server name in Settings → PHP → Servers.
4PHPStorm doesn't open a local file at a breakpoint?
Path mappings are missing. Settings → PHP → Servers → Use path mappings. Enter local /src → container /var/www/html.
5Check whether Xdebug is loaded in the container?
php -v | grep Xdebug or php -m | grep xdebug inside the container. php -r 'echo xdebug_info();' for a complete configuration overview.
6start_with_request=yes vs. trigger?
yes: connection attempt on every request. trigger: only on a special cookie/header. Trigger is better for performance, needs a browser extension (Xdebug Helper).
7Debugging Magento CLI commands?
bin/debug-cli enable or XDEBUG_SESSION=PHPSTORM bin/magento command. PHPStorm in listen mode. Path mappings identical to web debugging.
8How much performance overhead does Xdebug have?
10 to 50 percent more execution time. At the upper end for Magento. Enable Xdebug only when needed. bin/xdebug enable|disable or trigger mode.
9Port 9003 already in use, what to do?
ss -tulpn | grep 9003 or lsof -i :9003. Terminate the process or set a consistent alternative port in PHPStorm and xdebug.ini.
10Reconfigure Xdebug after a Docker rebuild?
No, as long as xdebug.ini is correctly embedded in the image. PHPStorm settings remain. Only adjust path mappings if the container path changes.