php.ini para producción: configuración segura y ajustes de rendimiento

La configuración por defecto de PHP no está pensada para producción: muestra errores en pantalla, no limita la memoria de forma agresiva y tiene funciones habilitadas que pueden suponer riesgos. Con unos pocos ajustes en php.ini se pasa de un servidor PHP "que funciona" a uno que resiste en producción real.

Estructura de los ficheros de configuración

# En Debian/Ubuntu con PHP 8.x:
/etc/php/8.x/fpm/php.ini        # PHP-FPM
/etc/php/8.x/cli/php.ini        # PHP CLI
/etc/php/8.x/fpm/conf.d/        # Ficheros de extensiones (incluidos automáticamente)

# Editar y aplicar:
sudo nano /etc/php/8.x/fpm/php.ini
sudo systemctl reload php8.x-fpm

# Verificar configuración activa:
php-fpm8.x -T      # test de configuración
php --ini          # ruta del php.ini activo
php -r "phpinfo();"  # ver todos los valores

Errores: silenciar en pantalla, registrar en log

; php.ini para producción
display_errors     = Off      ; nunca mostrar errores al usuario
display_startup_errors = Off  ; tampoco errores de arranque
log_errors         = On       ; sí registrar en el log
error_reporting    = E_ALL    ; registrar todos los errores (incluidos E_NOTICE, E_DEPRECATED)
error_log          = /var/log/php/error.log

; En desarrollo:
; display_errors = On
; error_reporting = E_ALL
<?php
// Sobrescribir desde código (útil en entornos con un php.ini compartido)
ini_set('display_errors', '0');
ini_set('log_errors', '1');
ini_set('error_log', '/var/log/miapp/php_errors.log');
error_reporting(E_ALL);

Sesiones seguras

; php.ini
session.cookie_httponly = On       ; la cookie no es accesible desde JavaScript
session.cookie_secure   = On       ; solo enviar por HTTPS
session.cookie_samesite = Strict   ; previene CSRF via cookies
session.use_strict_mode = On       ; rechaza IDs de sesión no generados por el servidor
session.use_only_cookies = On      ; no aceptar session_id en la URL (?PHPSESSID=...)
session.gc_maxlifetime  = 1800     ; segundos hasta que una sesión se considera basura
session.name            = SESSID   ; cambia el nombre por defecto (fingerprinting)
<?php
// Regenerar ID después de login para prevenir session fixation
session_start();
session_regenerate_id(true);  // true: elimina la sesión anterior

// Añadir validación adicional
$_SESSION['user_agent'] = hash('sha256', $_SERVER['HTTP_USER_AGENT']);
// Al inicio de cada petición:
if ($_SESSION['user_agent'] !== hash('sha256', $_SERVER['HTTP_USER_AGENT'])) {
    session_destroy();
    header('Location: /login');
    exit;
}

Límites de memoria y tiempo

; php.ini
memory_limit       = 256M   ; ajusta según las necesidades de la app
max_execution_time = 30     ; segundos máximos por petición web
max_input_time     = 60     ; tiempo máximo para parsear input
max_input_vars     = 1000   ; límite de variables de entrada (previene ataques DoS)

; Para scripts CLI de larga duración (imports, exports...)
; ejecuta: php -d memory_limit=1G -d max_execution_time=0 import.php

; Subida de ficheros
file_uploads       = On
upload_max_filesize = 10M
post_max_size       = 12M    ; debe ser mayor que upload_max_filesize
max_file_uploads   = 5

Deshabilitar funciones peligrosas

; php.ini
; Funciones que permiten ejecutar comandos del sistema operativo
; Desactívalas si tu aplicación no las necesita
disable_functions = exec,passthru,shell_exec,system,popen,proc_open,
                    pcntl_exec,pcntl_fork,proc_nice,proc_terminate,
                    show_source,phpinfo

; expose_php: oculta la versión de PHP en las cabeceras HTTP (X-Powered-By)
expose_php = Off

; allow_url_fopen y allow_url_include: solo si las necesitas
allow_url_fopen   = Off   ; deshabilita file_get_contents('http://...')
allow_url_include = Off   ; siempre Off — permite RFI (Remote File Inclusion)

php.ini global vs php_admin_value

En entornos con múltiples vhosts, puedes sobreescribir la configuración de php.ini por vhost en el fichero de configuración del servidor web:

# Apache — en el VirtualHost o .htaccess
php_value  memory_limit "512M"       ; el script puede cambiarlo con ini_set()
php_admin_value memory_limit "512M"  ; el script NO puede cambiarlo — más seguro
php_flag   display_errors Off
php_admin_flag display_errors Off

# Nginx + PHP-FPM — en el pool de PHP-FPM (/etc/php/8.x/fpm/pool.d/www.conf)
php_admin_value[memory_limit] = 512M
php_admin_flag[display_errors] = Off
php_admin_value[error_log]     = /var/log/php/miapp_error.log

Cabeceras de seguridad HTTP desde PHP

<?php
// Añadir en el bootstrap de la aplicación o en el servidor web
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: DENY');
header('X-XSS-Protection: 1; mode=block');
header('Referrer-Policy: strict-origin-when-cross-origin');
header('Permissions-Policy: geolocation=(), microphone=(), camera=()');
header("Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-{$nonce}'");
header('Strict-Transport-Security: max-age=31536000; includeSubDomains');  // solo HTTPS

// Eliminar cabeceras que revelan información
header_remove('X-Powered-By');  // aunque mejor con expose_php=Off

Configuración completa recomendada para producción

; /etc/php/8.x/fpm/php.ini — producción
; Errores
display_errors         = Off
display_startup_errors = Off
log_errors             = On
error_reporting        = E_ALL
error_log              = /var/log/php/error.log

; Sesiones
session.cookie_httponly = On
session.cookie_secure   = On
session.cookie_samesite = Strict
session.use_strict_mode = On
session.use_only_cookies = On

; Rendimiento
memory_limit       = 256M
max_execution_time = 30
max_input_vars     = 1000
realpath_cache_size = 4096k
realpath_cache_ttl  = 120

; OPcache
opcache.enable               = 1
opcache.memory_consumption   = 128
opcache.validate_timestamps  = 0
opcache.max_accelerated_files = 10000
opcache.jit_buffer_size      = 64M
opcache.jit                  = tracing

; Seguridad
expose_php         = Off
allow_url_include  = Off
disable_functions  = exec,passthru,shell_exec,system,popen,proc_open

Verificar la configuración activa

<?php
// Ver solo los valores que difieren de los compilados por defecto
$ini = ini_get_all();
foreach ($ini as $clave => $info) {
    if ($info['global_value'] !== $info['default_value']) {
        echo "$clave: {$info['global_value']}n";
    }
}

// Valores específicos
echo ini_get('memory_limit');      // 256M
echo ini_get('display_errors');    //
echo ini_get('session.cookie_secure');  // 1
  • Nunca copies el php.ini de desarrollo a producción sin revisar: display_errors=On, error_reporting demasiado permisivo o funciones peligrosas habilitadas son los errores más comunes.
  • max_input_vars bajo puede romper formularios grandes: si tienes formularios con muchos campos (o tablas de precios complejas), ajusta este valor o verifica que los datos llegan completos.
  • post_max_size debe ser mayor que upload_max_filesize: si no, la subida de ficheros parece funcionar pero los datos POST quedan truncados.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP