0

php.ini — the settings that matter

Beginner5 min read·php-01-004
performance

Concept

php.ini is PHP's master configuration file. Out of the hundreds of directives it contains, roughly a dozen have a meaningful impact on your application's behavior, security, and performance. Knowing which settings matter — and what to set them to in production vs development — is a mark of a senior PHP engineer.

PHP loads php.ini at startup. Changes require a process restart (for FPM: systemctl restart php8.4-fpm; for CLI the change is immediate on next invocation). You can check the active value of any directive with ini_get('directive_name') at runtime or override it per-script with ini_set() (though some directives like memory_limit can only be lowered at runtime, not raised).

The settings that matter

Memory and execution limits:

  • memory_limit = 128M — Maximum memory a single PHP process can use. Laravel + Eloquent on a large dataset can easily exceed 128M. Set to 256M in production, 512M for queue workers processing large jobs. Use -1 only in CLI scripts where you control the process.
  • max_execution_time = 30 — Seconds before PHP kills the request. CLI defaults to 0 (unlimited). Web requests should be 30–60s; anything longer should be offloaded to a queue job.
  • max_input_time = 60 — Time allowed to parse input (file uploads). Separate from execution time.

Error reporting (the most common misconfiguration):

  • error_reporting = E_ALL — In development, show all errors. In production, use E_ALL & ~E_DEPRECATED & ~E_STRICT.
  • display_errors = OffAlways Off in production. Displaying errors leaks internal paths, database credentials, and logic that attackers exploit.
  • log_errors = OnAlways On. Errors go to the error_log path.
  • error_log = /var/log/php/error.log — Where PHP writes errors.

File uploads:

  • upload_max_filesize = 2M — Max size of a single uploaded file.
  • post_max_size = 8M — Must be larger than upload_max_filesize. Controls total POST body size. If you increase upload_max_filesize, increase this too.
  • max_file_uploads = 20 — Maximum number of files per request.

Sessions:

  • session.save_path — Where PHP saves session files. In production use Redis (session.save_handler = redis).
  • session.cookie_httponly = 1 — Prevents JavaScript from accessing the session cookie (XSS mitigation).
  • session.cookie_secure = 1 — Only send cookie over HTTPS.
  • session.use_strict_mode = 1 — Prevents session fixation attacks.

OPcache (separate opcache.* section):

  • opcache.enable = 1 — Enable OPcache (should be enabled in production).
  • opcache.memory_consumption = 256 — Shared memory for cached opcodes (MB).
  • opcache.validate_timestamps = 0 — In production, don't check if files changed (massive performance win). Set to 1 in development.

Code Example

php
<?php
declare(strict_types=1);

// Read and display important php.ini settings programmatically
// Useful for a health-check endpoint or deployment verification script

$settings = [
    'memory_limit'           => ini_get('memory_limit'),
    'max_execution_time'     => ini_get('max_execution_time') . 's',
    'upload_max_filesize'    => ini_get('upload_max_filesize'),
    'post_max_size'          => ini_get('post_max_size'),
    'display_errors'         => ini_get('display_errors') ? 'ON (danger in prod!)' : 'off',
    'error_reporting'        => ini_get('error_reporting'),
    'session.cookie_httponly'=> ini_get('session.cookie_httponly') ? 'on' : 'OFF (danger!)',
    'opcache.enable'         => ini_get('opcache.enable') ? 'on' : 'off',
    'opcache.validate_timestamps' => ini_get('opcache.validate_timestamps') ? 'on (dev)' : 'off (prod)',
];

foreach ($settings as $key => $value) {
    printf("%-35s %s\n", $key, $value);
}

// Temporarily override memory limit for a memory-intensive operation
// Note: you can only lower the limit at runtime, unless the process has CAP_SYS_RESOURCE
$originalLimit = ini_get('memory_limit');
ini_set('memory_limit', '1G');

// ... process large CSV file ...

ini_set('memory_limit', $originalLimit); // restore

// Convert ini value string to bytes — useful for comparison
function iniToBytes(string $value): int
{
    $value = trim($value);
    $last = strtolower($value[-1]);
    $num = (int) $value;

    return match($last) {
        'g' => $num * 1024 * 1024 * 1024,
        'm' => $num * 1024 * 1024,
        'k' => $num * 1024,
        default => $num,
    };
}

$memoryLimit = iniToBytes(ini_get('memory_limit')); // e.g. 134217728 for 128M
$currentUsage = memory_get_usage(true);
$percentUsed = round(($currentUsage / $memoryLimit) * 100, 1);
echo "Memory: {$percentUsed}% used\n";

Interview Q&A

Q: What is the difference between upload_max_filesize and post_max_size and what happens when they're misconfigured?

upload_max_filesize controls the maximum size of a single uploaded file. post_max_size controls the total size of the entire POST request body, including all uploaded files plus other form fields. The rule is post_max_size must always be larger than upload_max_filesize. If you set upload_max_filesize = 100M but leave post_max_size = 8M, the POST body will be silently truncated at 8MB and $_FILES and $_POST will be empty — PHP won't throw an error, it will just silently discard the upload. This is one of the most confusing silent failures in PHP. Always set post_max_size to at least upload_max_filesize + 2M to account for form fields and overhead.


Q: Why must display_errors always be Off in production?

When display_errors = On, PHP prints the full error message including the file path, line number, SQL queries, stack traces, and potentially variable values directly into the HTTP response body. An attacker who triggers an error (sending malformed input, causing a missing file inclusion, etc.) sees your absolute file paths (revealing server layout), database table names from SQL errors, and PHP version information. This is called information disclosure and is listed in OWASP Top 10. In production, all errors should be written to a log file (log_errors = On) and sent to an error tracking service (Sentry, Bugsnag), never displayed to the user. Laravel's exception handler already respects this through APP_DEBUG=false.


Q: What does opcache.validate_timestamps = 0 actually do and when is it safe to use?

When validate_timestamps is 1 (default), PHP checks the file modification time on every request to see if the cached opcode is stale. This means one stat() syscall per included file per request — on a Laravel app with hundreds of autoloaded files, that adds up. Setting it to 0 tells OPcache to never check the filesystem; the cached opcodes are used permanently until the cache is manually invalidated (via opcache_reset() or FPM restart). This is safe in production because your files don't change after deployment — your deploy script should restart FPM or call opcache_reset(). Never set validate_timestamps = 0 in development because your code changes won't take effect until you manually clear the cache.