# The php.ini Explained: Structure, Levels and Precedence

> The php.ini explained: where PHP looks for its configuration, which levels exist, what INI_SYSTEM and INI_PERDIR mean and which value applies in the end.

Source: https://phpsos.de/en/php/php-ini-erklaert/  
As of: 25/10/2026  
Provider: PHP SOS, PHP emergency help and server security. Immediate help via WhatsApp: https://wa.me/491736494914?text=Hello%20PHP%20SOS%2C%20we%20need%20help%20with%20our%20PHP%20server.%20Company%3A%20%E2%80%A6%20Website%3A%20%E2%80%A6

**In short:** The php.ini is PHP's central configuration file. PHP reads it at startup: once for server modules, on every call for CGI and CLI. In addition there are supplementary files from a scan directory, settings in the FPM pool or in Apache, `.htaccess` or `.user.ini`, and `ini_set()` in the code. Which level may change a value is determined by its change mode, such as `INI_SYSTEM` or `INI_PERDIR`. Values set via `php_admin_value` can no longer be overridden by the application.

## What is the php.ini?

The php.ini is a text file with entries of the form `name = value`. It determines how PHP behaves: how much memory a script may use, whether errors appear in the browser, how sessions work and which extensions are loaded. Comments begin with a semicolon.

According to the PHP manual, the file is read when PHP starts. For server modules such as mod_php this happens only once, when the web server starts; for CGI and CLI, on every call. PHP-FPM reads its configuration at startup and when the service is reloaded. A change without a restart or reload therefore usually has no effect.

Since PHP 8.3, according to the manual, an entry can refer to an environment variable with a fallback value:

```ini
memory_limit = ${PHP_MEMORY_LIMIT}
max_execution_time = ${PHP_MAX_EXECUTION_TIME:-30}
```

## Where does PHP look for the php.ini?

The manual describes a fixed order. First, an explicitly set path applies, such as `PHPIniDir` in Apache or the `-c` option for CGI and CLI. Otherwise PHP builds a search path from:

1. the environment variable `PHPRC`,
2. on Windows, the registry entry `IniFilePath`,
3. the current working directory, except for SAPIs that disable this, such as the CLI,
4. the directory of the PHP binary or module,
5. the path set at compile time with `--with-config-file-path`.

In this search path, PHP first looks for a file `php-SAPI.ini`, for example `php-cli.ini`, and only then for `php.ini`. PHP then reads all `.ini` files from the scan directory, which is defined via `PHP_INI_SCAN_DIR` or at compile time via `--with-config-file-scan-dir`, in alphabetical order. Which files were loaded is shown by `php --ini` or the function `php_ini_scanned_files()`.

```bash
php --ini
php -r 'var_dump(php_ini_loaded_file(), php_ini_scanned_files());'
```

Both commands show the state of the command line. A different file may apply to the website, because every SAPI can have its own configuration; more on this under [SAPI explained](https://phpsos.de/en/php/sapi-erklaert/).

## Change modes: who may set which value?

Not every setting may be set everywhere. The manual assigns a change mode to each directive:

| Mode | Where the value may be set |
|---|---|
| `INI_USER` | in scripts via `ini_set()`, in the Windows registry and in `.user.ini` |
| `INI_PERDIR` | in php.ini, `.htaccess`, `httpd.conf` or `.user.ini` |
| `INI_SYSTEM` | only in php.ini or `httpd.conf` |
| `INI_ALL` | anywhere |

Examples from the manual: `memory_limit` has the mode `INI_ALL` and the default `128M`, `post_max_size` has `INI_PERDIR` and the default `8M`, `upload_max_filesize` has `INI_PERDIR` and the default `2M`. `expose_php` may only appear in the php.ini.

According to the manual, `.user.ini` files are read only by CGI and FastCGI SAPIs, which includes PHP-FPM. PHP searches for them from the script's directory upwards to the document root and by default re-reads them at most every 300 seconds (`user_ini.cache_ttl`). Under mod_php, `.htaccess` takes over this role.

## Which value applies in the end? The levels in order of precedence

In simplified terms: the later a level takes effect, the more likely its value prevails, provided the change mode allows it.

1. **php.ini** of the respective SAPI
2. **Supplementary files** from the scan directory, alphabetically
3. **Server configuration:** FPM pool with `php_value` or `php_admin_value`, Apache VirtualHost
4. **Directory level:** `.htaccess` with mod_php, `.user.ini` with FPM and CGI
5. **Runtime:** `ini_set()` in the application code

The exception is decisive: according to the manual, values set via `php_admin_value` or `php_admin_flag` cannot be overridden with `ini_set()`. This is how admins lock security-relevant settings against the application. This is described in detail under [php_admin_value: locking settings against overriding](https://phpsos.de/en/blog/php-admin-value-sperren/).

## The php.ini after PHP2Shell: what has changed

The interim measures from php.net for PHP2Shell are almost all INI settings: `memory_limit=-1` locked against overriding, a limited `post_max_size`, `session.upload_progress.enabled=0` and, on PHP 8.5, `fatal_error_backtraces=0`. php.net stresses that `session.upload_progress.enabled` should not be set via `ini_set()` or `.user.ini` but in php.ini, the FPM pool or the server configuration, followed by a restart of all workers. The full list is under [Immediate measures if you cannot patch yet](https://phpsos.de/en/php2shell/sofortmassnahmen-ohne-patch/).

With the corrected versions 8.5.12, 8.4.27, 8.3.36 and 8.2.35, according to the manual, `memory_limit` has also changed: it is now a soft limit. The new setting `memory_hard_limit_percent` defines a hard limit. Without its own value, it is twice `memory_limit`, or, for more than `1G`, that value plus `1G`. If it is exceeded, the process ends immediately with exit status 137. How to choose both limits sensibly is explained in [Setting memory_limit correctly](https://phpsos.de/en/blog/memory-limit-richtig-setzen/).

> **Tip:**
> According to the manual: if a PHP version does not recognise `memory_hard_limit_percent`, it is affected by CVE-2026-109618 and CVE-2026-109619. For distributions with backports, the package changelog additionally documents the state.

## How does PHP SOS help?

- **Determine effective values:** For each SAPI we read out which files are loaded and which value applies in the end.
- **Clean up the levels:** We remove contradictory entries from panels, pools, `.htaccess` and `.user.ini`.
- **Set locks:** As part of [server hardening](https://phpsos.de/en/leistungen/server-haertung/) we fix security-relevant values via `php_admin_value`.
- **Provide evidence:** We document settings and restarts so that the state can be proven later.


## Frequently asked questions

**Where is the php.ini located?**  
That depends on the operating system, package and SAPI. On Debian and Ubuntu there are separate folders per version and SAPI, such as /etc/php/8.3/fpm/php.ini and /etc/php/8.3/cli/php.ini. On the command line, php --ini shows the loaded files; for the website, php_ini_loaded_file() in a temporary test file helps.

**In which order are the files in the conf.d folder read?**  
According to the PHP manual, PHP reads all files ending in .ini in the scan directory in alphabetical order. Later files override values from earlier ones. That is why many files carry numbers such as 20- or 99- in their names.

**Why does my change to the php.ini have no effect?**  
Common reasons: the file of a different SAPI was changed, the service was not restarted, a value in the FPM pool, in the Apache configuration or in a .user.ini overrides it, or a hosting panel rewrites the file on the next save.

**What is memory_hard_limit_percent?**  
A new setting that, according to the PHP manual, was introduced with the security releases of 21 October 2026. It defines a hard memory limit as a percentage of memory_limit. The manual points out that a PHP version that does not know this setting is affected by the PHP2Shell flaws.

## Sources

- [PHP Manual: The Configuration File](https://www.php.net/manual/en/configuration.file.php)
- [PHP Manual: Where a Configuration Setting May Be Set](https://www.php.net/manual/en/configuration.changes.modes.php)
- [PHP Manual: .user.ini Files](https://www.php.net/manual/en/configuration.file.per-user.php)
- [PHP Manual: Core Settings](https://www.php.net/manual/en/ini.core.php)
