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:
-
the SAPI-specific location: the
-coption of the CLI and CGI, orPHPIniDirfor Apache; -
the
PHPRCenvironment variable; -
on Windows, the
IniFilePathregistry values; -
the current working directory (except for the CLI);
-
the web server’s directory (for SAPI modules) or PHP’s own directory (Windows);
-
the directory compiled in with
--with-config-file-path(typically/etc/php/8.5/cli/on Debian packages or/usr/local/etc/phpin 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.
Source: The configuration file and Command line options
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.
Source: The configuration file and List of php.ini sections
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 |
|---|---|
|
in scripts with |
|
in |
|
in |
|
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.
Source: .user.ini files and How to change configuration settings
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.
Source: ini_set, ini_get_all and ini_parse_quantity
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 |
|---|---|---|---|
|
|
|
which errors are reported at all (see Errors and Exceptions) |
|
|
|
print errors in the output; never |
|
|
|
where reported errors go |
|
|
|
maximum memory per script; |
|
|
|
maximum script execution time in seconds; always |
|
|
as needed |
request body and per-file upload limits |
|
|
|
default time zone for date functions |
|
|
|
|
|
|
set to |
adds an |
|
|
|
|
|
— |
as needed |
restrict filesystem access and forbid functions (hardening, see Web Security Fundamentals) |
|
|
|
which superglobals are filled, and the order of |
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.
Source: extension_loaded and get_loaded_extensions
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 installcommand, which is part of PEAR. It downloads the source, runsphpize,./configureandmake, and prints the line to add tophp.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.
Source: Introduction to PIE, PIE on GitHub and Introduction to PECL
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 |
|---|---|
|
the |
|
the built-in development server, |
|
PHP-FPM, the FastCGI process manager used behind Nginx, Apache ( |
|
the plain CGI/FastCGI binary |
|
Apache’s |
|
FrankenPHP, a Go application server embedding PHP, with an optional worker mode |
|
embedding PHP in another C program |
|
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_globalsandmagic_quotes_gpcwere removed in PHP 5.4; code that checksget_magic_quotes_gpc()fails on PHP 8, where the function no longer exists.track_errorsand$php_errormsgwere removed in 8.0 (useerror_get_last()). -
short_open_tagstill exists but defaults toOffin 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_phpas the default. Most books configure Apache withmod_php. PHP-FPM behind a web server, or an application server such as FrankenPHP, is now the norm, and some settings (.user.inirather than.htaccess) differ accordingly.
References
-
PHP Manual — Runtime Configuration: the configuration file, .user.ini files, where a setting may be set, how to change settings
-
PHP Manual — php.ini directives: list, core directives, sections
-
ini_set, ini_get, ini_get_all, ini_parse_quantity, ini_restore
-
PHP Manual — Command line options (
--ini,-m,--ri) -
Docker Hub — the official php image (
docker-php-ext-install)