# php_admin_value: Locking Settings Against Overrides

> php_admin_value and php_admin_flag explained: how to lock PHP values in the FPM pool and Apache against ini_set(), .htaccess and .user.ini, and verify it.

Source: https://phpsos.de/en/blog/php-admin-value-sperren/  
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:** `php_admin_value` and `php_admin_flag` set PHP settings in the PHP-FPM pool or in the Apache configuration so that applications can change them neither via `ini_set()` nor via `.htaccess`. php.net demands exactly this for the PHP2Shell interim measures, for example for `memory_limit=-1`. After setting, the service must restart, and the value should be verified in the web context.

## Why “set” does not mean “locked”

In the days after 21 October 2026, many administrators wrote php.net's interim measures into their php.ini. One detail of the recommendation was often lost: `memory_limit=-1` is supposed to be set so that applications cannot override it. php.net expressly names `php_admin_value` in the FPM pool or in the Apache configuration for this.

The reason is simple. Many CMS, shops and frameworks set their own values, such as a higher memory limit for imports. If the value is only in php.ini, the application overrides it on the next request. The protective measure then exists on paper but not in the running process.

This is not just about PHP2Shell. Every hardening measure, from disabled error output to upload limits, is only as strong as its weakest level.

## Who may change PHP settings at all

PHP knows four change modes. Every setting has one of them, and it determines in which places it can be set. The meanings come from the PHP manual.

| Mode | Where the value can be set | Examples |
|---|---|---|
| `INI_USER` | in the script via `ini_set()` and in `.user.ini` | few runtime values |
| `INI_PERDIR` | php.ini, `.htaccess`, `httpd.conf`, `.user.ini` | `post_max_size`, `session.upload_progress.enabled` |
| `INI_SYSTEM` | only php.ini or `httpd.conf` | `file_uploads`, `disable_functions` |
| `INI_ALL` | anywhere, also via `ini_set()` | `memory_limit`, `display_errors`, `open_basedir` |

`INI_ALL` values are the dangerous ones above all: they can be changed from any script. `INI_PERDIR` values cannot be changed via `ini_set()`, but they can via an `.htaccess` with mod_php or a `.user.ini` with PHP-FPM. Anyone with write permissions in the web root, including injected code, can create such files.

## php_admin_value in the FPM pool and in Apache

With PHP-FPM, the values are in the pool file. The PHP manual states: values set with `php_admin_value` and `php_admin_flag` cannot be overridden via `ini_set()`. For on/off values use `php_admin_flag`, for all others `php_admin_value`.

```ini
; /etc/php/8.3/fpm/pool.d/seite1.conf (excerpt)
[seite1]
user = seite1
group = seite1
listen = /run/php/seite1.sock

php_admin_value[memory_limit] = -1
php_admin_value[post_max_size] = 32M
php_admin_value[upload_max_filesize] = 24M
php_admin_flag[session.upload_progress.enabled] = off
php_admin_flag[display_errors] = off
php_admin_value[error_log] = /var/log/php/seite1-fehler.log
php_admin_value[open_basedir] = /var/www/seite1/:/tmp/seite1/
```

According to php.net, `memory_limit=-1` belongs in the configuration only together with a memory limit via container or cgroup. How this works with systemd is covered in the article [Setting memory limits with systemd and cgroups](https://phpsos.de/en/blog/speichergrenzen-systemd-cgroups/).

With Apache and mod_php, the directives belong in the server configuration or the VirtualHost. According to the PHP manual they are not allowed in `.htaccess`, and once set, neither `.htaccess` nor `ini_set()` can change them.

```apache
<VirtualHost *:443>
    ServerName www.example.de
    DocumentRoot /var/www/seite1/public
    <IfModule php_module>
        php_admin_value memory_limit -1
        php_admin_value post_max_size 32M
        php_admin_value upload_max_filesize 24M
        php_admin_flag session.upload_progress.enabled off
        php_admin_flag display_errors off
    </IfModule>
</VirtualHost>
```

Two subtleties from the manual: to delete a previously set value, use `none` as the value. And PHP constants such as `E_ALL` have no meaning in the Apache configuration; for `error_reporting` you must give the numeric value there.

## Which values you should lock

Not every setting needs to be locked. It makes sense for everything that concerns security or serves as an interim measure:

1. php.net's PHP2Shell interim values: `memory_limit`, `post_max_size`, `upload_max_filesize`, `session.upload_progress.enabled` and, on PHP 8.5, `fatal_error_backtraces`.
2. Error output and logging: `display_errors`, `display_startup_errors`, `log_errors`, `error_log`.
3. File access and isolation: `open_basedir`, `upload_tmp_dir`, `session.save_path`.
4. Network access from PHP: `allow_url_include` and, if the application does not need it, `allow_url_fopen`.
5. Blocked functions via `disable_functions`, which in the pool, according to the manual, add to the php.ini list rather than replace it. Details in the guide [Using disable_functions correctly](https://phpsos.de/en/ratgeber/disable-functions/).

For `session.upload_progress.enabled`, locking is particularly important because the value could otherwise be switched back on via a `.user.ini`. More on this under [Disabling session.upload_progress: correctly and permanently](https://phpsos.de/en/blog/session-upload-progress-abschalten/).

## Checking that the lock takes effect

After every change, restart the service and check the value in the web context. A short test file, deleted again immediately, shows whether the pool holds the value even when the application tries to change it:

```php
<?php
// Test file: put it in place briefly, call it once via the domain, delete it immediately
header('Content-Type: text/plain');
$vorher = ini_get('memory_limit');
$versuch = @ini_set('memory_limit', '256M');
echo "memory_limit: $vorher\n";
echo 'Änderung per ini_set() ', ($versuch === false ? 'blockiert' : 'MÖGLICH'), "\n";
```

The expected output is `-1` and “blockiert” (German for “blocked”; the test file's output text is in German). If the output says “MÖGLICH” (“possible”), the value is not set via `php_admin_value` or the service has not been restarted yet. Repeat the check for every pool and every PHP version.

> **Tip:**
> Keep the locked values in one central place, for example in a pool template or your configuration management. Hosting panels sometimes overwrite hand-edited pool files the next time they save; there the values belong in the fields provided by the panel.

How PHP arranges its configuration levels overall is explained in [php.ini explained: structure, levels and precedence](https://phpsos.de/en/php/php-ini-erklaert/). The complete interim measures are under [Immediate measures if you cannot patch yet](https://phpsos.de/en/php2shell/sofortmassnahmen-ohne-patch/).

## Conclusion: only locked is truly set

The php.net interim measures for PHP2Shell have brought a distinction into focus that was often overlooked before: for many settings, a value in php.ini is only a suggestion, a value in `php_admin_value` a requirement. For convenience values such as the time zone, the difference is irrelevant. For security settings and interim measures, it decides whether the measure takes effect in live operation at all.

The effort is manageable: a handful of lines per pool or VirtualHost, a restart, a short check. Keep the list of locked values small and justified so that developers can understand why their application may not change a value. And maintain the list in your templates so that new websites inherit it automatically.

## How does PHP SOS help?

- **Read out the levels:** For each website, we determine which values from php.ini, pool, Apache, `.htaccess` or `.user.ini` actually apply.
- **Set locks:** We enter security-relevant values and the PHP2Shell interim measures as `php_admin_value` or `php_admin_flag` and restart the services.
- **Test the application:** We check whether the application runs without errors with the locked values and evaluate the error log.
- **Maintain templates:** As part of [server hardening](https://phpsos.de/en/leistungen/server-haertung/), we carry the values over into pool templates and documentation.


## Frequently asked questions

**What is the difference between php_value and php_admin_value?**  
Both set a PHP setting. Values from php_value can be changed again later by the application, for example via ini_set() or .htaccess. According to the PHP manual, values from php_admin_value and php_admin_flag are locked against ini_set(), and with Apache also against .htaccess.

**Can I write php_admin_value into an .htaccess file?**  
No. According to the PHP manual, php_admin_value is not allowed in .htaccess files. It belongs in the Apache server configuration or the VirtualHost, and with PHP-FPM in the pool file.

**What happens if an application tries to change a locked value?**  
The ini_set() call fails and returns false; the locked value remains in place. Well-programmed applications cope with this. After the changeover, still check the error log and the most important functions.

**Do I have to restart after a change in the pool?**  
Yes. PHP-FPM reads the pool files at startup or when the service is reloaded. With Apache and mod_php, the same applies to the server configuration. Without a restart the old value keeps running.

**Can disable_functions be extended in the pool?**  
Yes, with one peculiarity: according to the PHP manual, disable_functions in the FPM pool does not override the values from php.ini but adds to them. The pool can therefore block further functions but cannot release any.

## Sources

- [PHP manual: FPM configuration](https://www.php.net/manual/en/install.fpm.configuration.php)
- [PHP manual: Changing configuration](https://www.php.net/manual/en/configuration.changes.php)
- [PHP manual: Modes for changing settings](https://www.php.net/manual/en/configuration.changes.modes.php)
- [php.net: Security announcements](https://www.php.net/)
