Configuration and Extensions

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against PHP 8.5, checked on 2026-10-05. It explains where PHP reads its configuration from, how settings can be changed at each level, the directives worth knowing, and how extensions — the C modules that provide most of PHP’s functions and classes — are installed and loaded.

The php.ini file

PHP’s configuration lives in php.ini, a plain INI file of directive = value lines. It is read when PHP starts: once per process for long-running SAPIs such as PHP-FPM or Apache’s module (so changes need a reload), and on every invocation for the CLI.

Where PHP looks for it

PHP searches for php.ini in this order and uses the first one found:

  1. the SAPI-specific location: the -c option of the CLI and CGI, or PHPIniDir for Apache;

  2. the PHPRC environment variable;

  3. on Windows, the IniFilePath registry values;

  4. the current working directory (except for the CLI);

  5. the web server’s directory (for SAPI modules) or PHP’s own directory (Windows);

  6. the directory compiled in with --with-config-file-path (typically /etc/php/8.5/cli/ on Debian packages or /usr/local/etc/php in the Docker image).

If a file named php-SAPI.ini exists, such as php-cli.ini, it is used instead of php.ini for that SAPI. After the main file, PHP parses every *.ini file of the scan directory (set at compile time or with the PHP_INI_SCAN_DIR environment variable) in alphabetical order. Distributions use it for one file per extension, for example /etc/php/8.5/cli/conf.d/20-intl.ini.

php --ini shows what was actually loaded, and PHP 8.5 added --ini=diff to list only the settings that differ from the built-in defaults:

$ php --ini
Configuration File (php.ini) Path: "/usr/local/etc/php"
Loaded Configuration File:         (none)
Scan for additional .ini files in: "/usr/local/etc/php/conf.d"
Additional .ini files parsed:      /usr/local/etc/php/conf.d/docker-php-ext-sodium.ini

# Use the production defaults shipped with PHP
$ cp /usr/local/etc/php/php.ini-production /usr/local/etc/php/php.ini

$ php --ini=diff

PHP ships two templates: php.ini-development (shows every error, enables assertions) and php.ini-production (hides errors from output, logs them, disables assertions). Start from the production one on servers.

Syntax

; Comments start with a semicolon. Section headers like [PHP] are ignored,
; except [PATH=...] and [HOST=...] sections, which apply only to that path or host (CGI/FPM).
[PHP]
memory_limit = 256M              ; quantities accept K, M and G suffixes
display_errors = Off             ; booleans: On/Off, true/false, yes/no, 1/0
error_reporting = E_ALL          ; constants and bitwise expressions are allowed
include_path = ".:/usr/share/php"

; Environment variables -- with a fallback value since PHP 8.3
max_execution_time = ${PHP_MAX_EXECUTION_TIME:-30}

; Extensions
extension = intl
zend_extension = opcache

Using ${VAR} in php.ini is a common way to configure containers: the image ships one php.ini, and each environment sets variables. The :-default fallback syntax requires PHP 8.3 or later.

Changing settings: the INI modes

Not every directive can be changed everywhere. Each one has a changeable mode, given in the Changeable column of the manual’s directive tables:

Mode Where the directive can be set

INI_USER

in scripts with ini_set(), and in the Windows registry

INI_PERDIR

in php.ini, .htaccess, httpd.conf or .user.ini

INI_SYSTEM

in php.ini or httpd.conf only

INI_ALL

anywhere

memory_limit is INI_ALL, so a script can raise it; upload_max_filesize and post_max_size are INI_PERDIR, because the request body has already been read before your script runs; disable_functions, open_basedir (when tightening is not enough) and extension are system-level.

Per-directory configuration: .user.ini and Apache

With CGI/FastCGI SAPIs, including PHP-FPM, PHP reads .user.ini files (the name is set by user_ini.filename) from the directory of the requested script up to the document root. Only INI_PERDIR and INI_USER directives are honoured, and the files are re-read every user_ini.cache_ttl seconds (300 by default):

; public/.user.ini -- picked up by PHP-FPM, not by the CLI
upload_max_filesize = 20M
post_max_size = 24M

With Apache’s mod_php, the same is done with php_value/php_flag lines in httpd.conf or .htaccess (php_admin_value and php_admin_flag set values that ini_set() cannot override). PHP-FPM pools have equivalent php_value[...] and php_admin_value[...] entries, shown in The PHP Request Model and Servers.

Reading and changing settings at runtime

<?php

declare(strict_types=1);

// ini_get() returns the current value as a string (or false if the directive does not exist).
var_dump(ini_get('memory_limit'));                 // string(4) "128M"

// ini_set() returns the OLD value on success, false on failure.
var_dump(ini_set('memory_limit', '256M'));         // string(4) "128M"
var_dump(ini_get('memory_limit'));                 // string(4) "256M"

// INI_PERDIR directives cannot be changed from a script.
var_dump(ini_set('upload_max_filesize', '1G'));    // bool(false)

// ini_parse_quantity() (PHP 8.2) converts shorthand quantities to bytes.
var_dump(ini_parse_quantity('256M'));              // int(268435456)

// All settings of one extension, with their global value, local value and access mode.
$date = ini_get_all('date');
print_r($date['date.timezone']);

ini_restore('memory_limit');                       // back to the php.ini value
var_dump(php_ini_loaded_file());                   // path of php.ini, or false

access is a bitmask of the modes above: INI_USER (1), INI_PERDIR (2), INI_SYSTEM (4), so 7 means INI_ALL. Changes made with ini_set() last until the end of the script or request.

Core directives worth knowing

The manual’s appendix lists every core directive (ini.core) and the complete list (ini.list). The ones you meet most often:

Directive Development Production Meaning

error_reporting

E_ALL

E_ALL & ~E_DEPRECATED

which errors are reported at all (see Errors and Exceptions)

display_errors

On

Off

print errors in the output; never On in production

log_errors, error_log

On

On, a file or syslog

where reported errors go

memory_limit

128M

128M

maximum memory per script; -1 means unlimited

max_execution_time

30

30

maximum script execution time in seconds; always 0 (unlimited) for the CLI

post_max_size, upload_max_filesize

8M, 2M

as needed

request body and per-file upload limits

date.timezone

UTC

UTC

default time zone for date functions

zend.assertions

1

-1

assert() is compiled in (1) or removed entirely (-1)

expose_php

On

set to Off

adds an X-Powered-By: PHP/8.5.x header

opcache.enable, opcache.validate_timestamps

1, 1

1, often 0

see OPcache, JIT and Performance

open_basedir, disable_functions

 — 

as needed

restrict filesystem access and forbid functions (hardening, see Web Security Fundamentals)

variables_order, request_order

GPCS, GP

GPCS, GP

which superglobals are filled, and the order of $_REQUEST

The values in the second and third columns are those of the php.ini-development and php.ini-production templates shipped with PHP 8.5.

Extensions

Almost every function and class in PHP comes from an extension. Some are always compiled in (Core, standard, date, pcre, random, Reflection, SPL, hash, json and, since 8.5, uri); others are bundled with PHP’s source but optional (mbstring, intl, pdo_mysql, sodium, gd, opcache, …); others live outside php-src and are installed separately (xdebug, redis, mongodb, imagick, apcu, …).

Listing and checking extensions

$ php -m                    # loaded extensions
[PHP Modules]
Core
ctype
curl
date
...
[Zend Modules]
Zend OPcache

$ php --ri mbstring         # an extension's settings
$ php --re random | head    # its functions, classes and constants
<?php

declare(strict_types=1);

var_dump(extension_loaded('mbstring'));   // bool(true)
var_dump(extension_loaded('intl'));       // bool(false) in the bare Docker image

// Fail early with a clear message instead of an "undefined function" error later.
if (!extension_loaded('intl')) {
    fwrite(STDERR, "The intl extension is required: install php-intl or run docker-php-ext-install intl\n");
}

print_r(array_slice(get_loaded_extensions(), 0, 5));

In a Composer project, declare required extensions in composer.json ("ext-intl": "*") so that installation fails on a server where they are missing; see Composer and Packages.

Loading extensions

Shared extensions are .so files (.dll on Windows) in the directory named by extension_dir. They are enabled in php.ini or a scan-directory file:

; conf.d/20-intl.ini
extension = intl            ; the "php_" prefix and the file suffix can be omitted

; Zend extensions hook into the engine itself (OPcache, Xdebug) and use zend_extension
zend_extension = xdebug

The dl() function, which loads an extension at runtime, only works in the CLI and embed SAPIs and is disabled in most builds; configure extensions in ini files instead.

Installing extensions: packages, Docker helpers, PECL and PIE

There are four common ways to get an extension that is not loaded yet:

  • Operating system packages: apt install php8.5-intl, dnf install php-intl, apk add php85-intl.

  • The Docker image helpers for bundled extensions: docker-php-ext-install intl pdo_pgsql.

  • PECL (the PHP Extension Community Library) with the pecl install command, which is part of PEAR. It downloads the source, runs phpize, ./configure and make, and prints the line to add to php.ini. PEAR is in maintenance mode and PECL is being phased out in favour of PIE.

  • PIE (the PHP Installer for Extensions), the PHP Foundation’s replacement for PECL. It is distributed as a PHAR, finds extensions on Packagist (extension packages have "type": "php-ext"), builds them from source on Linux and macOS, downloads binaries on Windows, and enables them in the right ini file:

# Install PIE (it is a single PHAR file)
curl -fL --output /usr/local/bin/pie https://github.com/php/pie/releases/latest/download/pie.phar
chmod +x /usr/local/bin/pie

# Install extensions from Packagist
pie install xdebug/xdebug
pie install mongodb/mongodb-extension
pie install phpredis/phpredis

# See what is installed through PIE for the current PHP
pie show

PIE needs the PHP development headers (phpize), a C compiler and the libraries the extension depends on, just like PECL. Run in a project directory, pie install with no argument checks the ext-* requirements of composer.json and offers to install each missing extension.

phpinfo()

phpinfo() outputs every setting, loaded extension, environment variable and build option, as HTML from a web SAPI or as text from the CLI (where it is equivalent to php -i). It is the quickest way to see which php.ini a web server actually uses, which is often different from the CLI’s:

<?php

declare(strict_types=1);

// Only the "Configuration" and module sections, not the whole report.
phpinfo(INFO_GENERAL | INFO_CONFIGURATION);

Never leave a phpinfo() page reachable on a production server: it discloses paths, versions and environment variables, including secrets passed through the environment.

Source: phpinfo

SAPIs: how PHP is embedded

A SAPI (Server Application Programming Interface) is the layer that connects the engine to the outside world: where input comes from and where output goes. The same script can run under several SAPIs, and some behaviour depends on which one is used.

SAPI (PHP_SAPI) Used for

cli

the php command: scripts, workers, tools; no time limit, display_errors writes to STDOUT, $argv is set

cli-server

the built-in development server, php -S

fpm-fcgi

PHP-FPM, the FastCGI process manager used behind Nginx, Apache (mod_proxy_fcgi) or Caddy

cgi-fcgi

the plain CGI/FastCGI binary php-cgi

apache2handler

Apache’s mod_php module, running inside the web server’s processes

frankenphp

FrankenPHP, a Go application server embedding PHP, with an optional worker mode

embed

embedding PHP in another C program

phpdbg

the interactive debugger

<?php

declare(strict_types=1);

// PHP_SAPI and php_sapi_name() return the same string.
$sapi = PHP_SAPI;

$mode = match (true) {
    $sapi === 'cli' => 'command line',
    $sapi === 'cli-server' => 'built-in server',
    str_contains($sapi, 'fpm') => 'PHP-FPM',
    default => "other SAPI ({$sapi})",
};

echo "Running under: {$mode}\n";    // Running under: command line

The web SAPIs, the request lifecycle they implement and their configuration (FPM pools, Nginx, FrankenPHP worker mode) are covered in The PHP Request Model and Servers. The CLI SAPI is covered in Command-Line PHP.

Source: php_sapi_name and PHP_SAPI

Where older books and tutorials are outdated

  • Removed directives. safe_mode, register_globals and magic_quotes_gpc were removed in PHP 5.4; code that checks get_magic_quotes_gpc() fails on PHP 8, where the function no longer exists. track_errors and $php_errormsg were removed in 8.0 (use error_get_last()).

  • short_open_tag still exists but defaults to Off in the shipped ini files; do not depend on <? tags.

  • PECL and PEAR. Older books install extensions and libraries through PEAR. Use Composer for libraries and PIE (or the system packages) for extensions.

  • mod_php as the default. Most books configure Apache with mod_php. PHP-FPM behind a web server, or an application server such as FrankenPHP, is now the norm, and some settings (.user.ini rather than .htaccess) differ accordingly.