php.ini & Configuration
php.ini is the master configuration file that PHP reads the moment it starts up, before a single line of your code runs. It controls things your scripts cannot change on their own: how much memory a request may consume, whether errors are printed to the screen or only written to a log, how large an uploaded file is allowed to be, and how long a script may run before PHP kills it. Most "works on my machine but not on the server" bugs that have nothing to do with your code trace back to a php.ini difference — so knowing where it lives, how to inspect it, and how to override it safely is a core PHP skill, not an ops-only concern.
Finding the active php.ini
A single machine can easily have several php.ini files sitting on disk — one shipped with an installer, one from a package manager, one left over from an old version. The only way to know which one PHP is actually using is to ask PHP itself.
Inspecting the active configuration
php --ini
Configuration File (php.ini) Path: /etc/php/8.3/cli
Loaded Configuration File: /etc/php/8.3/cli/php.ini
Scan for additional .ini files in: /etc/php/8.3/cli/conf.d
Additional .ini files parsed: /etc/php/8.3/cli/conf.d/10-opcache.ini,
/etc/php/8.3/cli/conf.d/20-mysqli.iniLoaded Configuration File is the one file that wins overall. The Scan for additional .ini files in directory is a folder of smaller, per-extension .ini snippets (things like mysqli.ini or opcache.ini) that get merged in afterward, usually in alphabetical order — later files can override earlier ones. From inside a running script you can get the same answer with php_ini_loaded_file() and php_ini_scanned_files().
Directives you will touch the most
error_reporting— a bitmask of which error levels PHP acts on at all (e.g.E_ALLfor everything, orE_ALL & ~E_DEPRECATEDto silence deprecation notices). This decides what gets reported; it does not decide where it goes.display_errors— whether reported errors are printed directly into the HTTP response / terminal output. Independent fromerror_reporting: you can report everything but display nothing.log_errorsanderror_log— whether reported errors are written to a log file instead, and which file to write them to.memory_limit— the maximum RAM a single PHP request may allocate before it is killed with a fatalAllowed memory size exhaustederror. Set to-1for unlimited, which you should basically never do on a public server.upload_max_filesize— the largest single file PHP will accept via amultipart/form-dataupload.post_max_size— the largest total size of a POST request body, including all uploaded files combined. Must be greater than or equal toupload_max_filesize, or uploads silently fail.max_execution_time— seconds a script may run before PHP aborts it with a fatal timeout error. Does not count time spent waiting on external I/O like a slow database query in every configuration, which is a common source of confusion.
A representative slice of php.ini
error_reporting = E_ALL display_errors = Off log_errors = On error_log = /var/log/php/error.log memory_limit = 256M upload_max_filesize = 20M post_max_size = 25M max_execution_time = 30
Overriding settings per directory: .htaccess
On Apache with the older mod_php module, you can override certain directives for a specific folder without touching the global php.ini at all, using php_value (for string/numeric directives) and php_flag (for boolean on/off directives) inside a .htaccess file.
.htaccess — folder-level overrides (mod_php only)
php_value upload_max_filesize 50M php_value post_max_size 55M php_flag display_errors Off
Overriding settings per pool: PHP-FPM
PHP-FPM manages one or more "pools" of worker processes, each configured by its own file (commonly www.conf or a file under a pool.d/ directory). Directives are set there with the php_admin_value / php_admin_flag or php_value / php_flag prefixes, and take effect only for requests handled by that pool.
/etc/php/8.3/fpm/pool.d/www.conf
[www] ; ...pool definition above... php_admin_value[memory_limit] = 512M php_admin_value[upload_max_filesize] = 50M php_admin_value[post_max_size] = 55M php_admin_flag[display_errors] = off
The _admin_ variants are locked — a script cannot override them later with ini_set(). The plain php_value/php_flag variants set a new default that user-level code is still allowed to change at runtime, if the directive permits it. FPM must be restarted (not just reloaded) for pool-level changes to take effect: systemctl restart php8.3-fpm.
Overriding settings at runtime: ini_set()
Some directives can be changed from inside a running script with ini_set(), but only for the remainder of that request — the change never touches the file on disk and never affects other requests. Whether a given directive is eligible depends on its declared "changeable mode": directives marked PHP_INI_USER or PHP_INI_ALL in the PHP manual can be set this way; ones marked PHP_INI_SYSTEM or PHP_INI_PERDIR cannot — those require php.ini, a pool file, or .htaccess.
debug-this-script.php
<?php
// Only affects this one request, only for directives PHP allows at runtime
ini_set('display_errors', '1');
ini_set('error_reporting', E_ALL);
ini_set('memory_limit', '512M');
// memory_limit is PHP_INI_ALL, so this works even though it looks like
// a "system" setting — but max_execution_time cannot be raised this way
// on every SAPI, and some hosts lock it down further regardless.Recommended settings: development vs. production
Directive | Development | Production |
|---|---|---|
| On | Off |
|
|
|
| On or Off (developer preference) | Off |
| On | On |
|
| A tight, measured value (e.g. |
expose_php controls whether PHP adds an X-Powered-By: PHP/8.3.x header to every response. It has no effect on how your application behaves — it only affects how much version information you hand to anyone scanning your server for known vulnerabilities in a specific PHP release.
A note on upload size directives
upload_max_filesize and post_max_size are frequently misconfigured as a pair. Raising only upload_max_filesize while leaving post_max_size smaller (or equal) means the upload is rejected before your code even sees it — PHP populates $_FILES as empty and $_POST as empty too, with no exception thrown, which looks indistinguishable from the user simply not selecting a file unless you specifically check $_SERVER['CONTENT_LENGTH'] against post_max_size.
A correctly paired upload configuration
upload_max_filesize = 20M post_max_size = 25M ; leaves headroom for other form fields