Current situation: PHP2Shell. Two critical PHP vulnerabilities (CVSS 9.2) allow malicious code without login. Patches since 21 October 2026.What to do now →

php_admin_value: Locking Settings Against Overrides

A security setting is of little use if the application changes it again with one line of code. With php_admin_value and php_admin_flag you fix values for PHP-FPM and Apache bindingly.

Last updated 6 min readHardening and configuration

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.

; /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.

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.

<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.

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.

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
// 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. The complete interim measures are under Immediate measures if you cannot patch yet.

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, 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

Immediate contact

Every hour counts. Write to us now.

Briefly describe your system, PHP version and observation. We will get back to you, clarify the situation and work remotely until the incident is closed and documented.

Immediate help