Finding the active PHP configuration files matters before changing upload limits, enabling extensions, setting time zones, or adjusting error handling because the same host can run different CLI, PHP-FPM, and Apache or CGI runtimes side by side. Identifying the real file set first prevents edits that look correct on disk but never reach the interpreter serving the application.
Each runtime starts from one main php.ini file, may scan one or more directories for extra .ini fragments, and can still be overridden later by runtime-specific layers such as PHP-FPM pool directives, web-server FastCGI headers, Apache php_value rules, or per-directory .user.ini files. The effective configuration is therefore a chain, not a single file path.
The safest workflow is to query the exact runtime that executes the code instead of guessing from package names or service units alone. Check the CLI interpreter directly, use a short-lived probe for the web-facing runtime when needed, and remove that probe immediately after confirming the loaded file, scan directory, and any override layers.
Related: How to check PHP version
Related: How to configure PHP error logging
Related: How to increase the PHP file upload limit
Related: How to create a PHP-FPM pool
$ php --ini Configuration File (php.ini) Path: "/opt/homebrew/etc/php/8.5" Loaded Configuration File: "/opt/homebrew/etc/php/8.5/php.ini" Scan for additional .ini files in: "/opt/homebrew/etc/php/8.5/conf.d" Additional .ini files parsed: (none)
php --ini is the fastest authoritative answer for the current CLI runtime. If Loaded Configuration File reports (none), that interpreter is running with built-in defaults or a custom startup path instead of a readable php.ini.
$ php -i | grep -E '^(Loaded Configuration File|Scan this dir for additional \.ini files|Additional \.ini files parsed|user_ini\.filename|user_ini\.cache_ttl)' Loaded Configuration File => /opt/homebrew/etc/php/8.5/php.ini Scan this dir for additional .ini files => /opt/homebrew/etc/php/8.5/conf.d Additional .ini files parsed => (none) user_ini.cache_ttl => 300 => 300 user_ini.filename => .user.ini => .user.ini
This view is convenient when comparing the loaded CLI runtime with a web-facing runtime because it shows the main file, extra scan path, and per-directory INI settings in one place.
$ php-config --configure-options | tr ' ' '\n' | grep -E -- '--with-config-file-path|--with-config-file-scan-dir' --with-config-file-path=/opt/homebrew/etc/php/8.5 --with-config-file-scan-dir=/opt/homebrew/etc/php/8.5/conf.d
The compiled paths are a fallback, not the final answer. The PHP manual documents php -c, PHPRC, and PHP_INI_SCAN_DIR as runtime overrides that can change what actually loads.
<?php header('Content-Type: text/plain'); printf("SAPI: %s\n", PHP_SAPI); printf("Loaded php.ini: %s\n", php_ini_loaded_file() ?: 'none'); echo "Scanned .ini files:\n"; $files = php_ini_scanned_files(); if ($files === false) { echo "(not reported by runtime)\n"; } elseif ($files === '') { echo "(none)\n"; } else { echo $files; if (substr($files, -1) !== "\n") { echo "\n"; } } printf("user_ini.filename=%s\n", ini_get('user_ini.filename')); printf("user_ini.cache_ttl=%s\n", ini_get('user_ini.cache_ttl')); ?>
Run the temporary script through the same site, virtual host, or PHP-FPM pool that serves the application. php --ini only reports the CLI configuration.
Remove the diagnostic script after confirming the paths because it exposes internal configuration details.
$ curl -s https://app.example.test/php-config-check.php SAPI: fpm-fcgi Loaded php.ini: /etc/php/8.3/fpm/php.ini Scanned .ini files: /etc/php/8.3/fpm/conf.d/10-opcache.ini, /etc/php/8.3/fpm/conf.d/10-pdo.ini, /etc/php/8.3/fpm/conf.d/20-calendar.ini, /etc/php/8.3/fpm/conf.d/20-ctype.ini user_ini.filename=.user.ini user_ini.cache_ttl=300
Confirm the SAPI first. A CLI answer does not prove that the web server is using the same php.ini tree or the same per-directory INI behavior.
$ ls -1 /etc/php/8.3/fpm/conf.d 10-opcache.ini 10-pdo.ini 20-calendar.ini 20-ctype.ini 20-phar.ini 20-tokenizer.ini
Use the exact scan directory reported by php --ini or the temporary script. Within each directory, PHP loads files ending in .ini in alphabetical order, and PHP_INI_SCAN_DIR can replace or extend the default list.
$ sudo grep -REn 'php_(admin_)?(value|flag)\[[^]]+\]' /etc/php/8.3/fpm/pool.d /etc/php-fpm.d 2>/dev/null /etc/php/8.3/fpm/pool.d/app.example.test.conf:54:php_admin_value[error_log] = /var/log/php/app.example.test-error.log
PHP-FPM pool files can override directives for one pool without changing the main php.ini, so the loaded file is not always the last layer that matters.
Related: How to create a PHP-FPM pool
$ sudo grep -REn 'fastcgi_param[[:space:]]+PHP_(ADMIN_)?VALUE|php_(admin_)?(value|flag)' /etc/nginx /etc/apache2 /etc/httpd 2>/dev/null /etc/nginx/sites-enabled/app.example.test.conf:26:fastcgi_param PHP_VALUE "upload_max_filesize=64M post_max_size=64M";
The PHP-FPM manual notes that PHP_VALUE and PHP_ADMIN_VALUE are passed as FastCGI headers. On Apache mod_php, php_value, php_admin_value, php_flag, and php_admin_flag can also override the main file per virtual host or directory.
Related: Locate Apache configuration files
$ find /var/www/app.example.test -name .user.ini -print /var/www/app.example.test/public/.user.ini
The PHP manual says these files are processed only by CGI or FastCGI, starting with the requested script directory and walking upward to the document root. They are ignored when PHP runs as an Apache module.
The default reread interval is 300 seconds unless user_ini.cache_ttl is changed.
$ rm /var/www/app.example.test/public/php-config-check.php
Leaving the probe reachable exposes internal paths, scan directories, and other runtime details that should stay private.
The runtime-discovery steps above are the source of truth, but common packaging layouts still help narrow the search when the expected binary is outside PATH or another team manages the host.
| Packaging or install style | Main file | Additional files |
|---|---|---|
| Debian or Ubuntu packages | /etc/php/<version>/<sapi>/php.ini | /etc/php/<version>/<sapi>/conf.d/*.ini |
| Fedora, RHEL, Rocky, AlmaLinux, or CentOS Stream packages | /etc/php.ini | /etc/php.d/*.ini |
| Homebrew on macOS | /opt/homebrew/etc/php/<version>/php.ini or /usr/local/etc/php/<version>/php.ini | matching /conf.d/*.ini directory |
| Official Windows ZIP build | {PHP directory}\php.ini or the resolved search path | configured scan directory when present |
| XAMPP | {installation directory}/php/php.ini | install-specific scan directory or none |
Packaged Linux hosts often keep separate trees for cli, fpm, and apache2. Treat each SAPI as an independent runtime instead of assuming one /php.ini applies everywhere. PHP-FPM pool overrides are commonly stored under /etc/php/<version>/fpm/pool.d/*.conf on Debian or Ubuntu and under /etc/php-fpm.d/*.conf on many RHEL-family systems.
According to the PHP manual, the runtime first checks for a SAPI-specific location such as Apache PHPIniDir or the CLI and CGI php -c option, then PHPRC, then the Windows registry keys when applicable, then the current working directory except on CLI, then the web-server or PHP binary directory on Windows, and finally the compiled default path.
If a php-<sapi>.ini file exists, such as php-cli.ini or php-apache.ini, PHP uses it instead of the generic php.ini for that runtime. After the main file is loaded, PHP can scan one or more extra directories for .ini fragments, and PHP_INI_SCAN_DIR can replace or extend that list at startup.
Per-directory .user.ini files are useful when the application runs under CGI or FastCGI and the main php.ini is not writable. PHP starts in the directory of the requested script and walks upward until it reaches the document root, so a nearer file can affect only one part of the application tree.
Only directives allowed at INI_PERDIR or INI_USER scope can be set there. The filename defaults to .user.ini, the cache interval defaults to 300 seconds, and setting user_ini.filename to an empty string disables the scan completely. When PHP runs as an Apache module, .user.ini is ignored, so the change must be made in the main configuration or an Apache override instead.