Skip to main content

MySQL: Error "No space left on device" en /tmp

Síntomas del problema

Los usuarios reportan errores similares a estos en cPanel, phpMyAdmin y otras aplicaciones:

Can't create/write to file '/tmp/#sql_911_0.MAI' (Errcode: 28 "No space left on device")
Se experimentó un error mientras se obtenían los datos: Can't create/write to file '/tmp/#sql_1234_0.MAI'

Diagnóstico paso a paso

Verificar espacio en disco

df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1        20G  8.5G   10G  46% /
tmpfs           2.0G  144K  2.0G   1% /tmp

Salida para indicar que todo es normal

Verificar inodos (la causa real)

df -i
Filesystem      Inodes   IUsed   IFree IUse% Mounted on
/dev/sda1      1310720  145230 1165490   11% /
tmpfs           524288  524271      17  100% /tmp

Observación: La salida indica el agotamiento al 100% de los inodos.

Identificar los archivos problemáticos

Al intentar listar /tmp con ls, obtendremos:

ls /tmp
-bash: /bin/ls: Argument list too long

Por eso utilizamos find:

find /tmp -maxdepth 1 -name 'sess*' | wc -l

Resultado atípico: 288789 (cientos de miles de archivos)

Causa raíz

Acumulación masiva de archivos de sesión PHP (sess_*) que no se eliminan automáticamente, causando agotamiento de inodos en /tmp.

Solución inmediata

find /tmp -maxdepth 1 -type f -name 'sess*' -delete

¿Por qué usar find? Porque rm sess* falla con "Argument list too long" cuando hay miles de archivos.

Prevención

Configuración automática de limpieza

Añadir al crontab de root:

# Limpieza diaria de archivos temporales antiguos (>24h)
0 3 * * * find /tmp -type f -name "sess_*" -mtime +1 -delete >/dev/null 2>&1

Script para aplicar cambios PHP en todas las versiones de cPanel

Contexto: En cPanel cada versión PHP tiene su propio archivo de configuración:

/opt/cpanel/ea-php81/root/etc/php.ini
/opt/cpanel/ea-php82/root/etc/php.ini
/opt/cpanel/ea-php83/root/etc/php.ini

Script automático para todas las versiones:

#!/bin/bash
# fix_php_gc_all_versions.sh
# Script para corregir configuración de garbage collector en todas las versiones PHP de cPanel

echo "=== Corrigiendo configuración PHP Garbage Collector ==="
echo "Fecha: $(date)"
echo

# Buscar todas las versiones de PHP instaladas de forma eficiente
PHP_VERSIONS=$(find /opt/cpanel -maxdepth 1 -name "ea-php*" -type d | grep -E "ea-php[0-9]+" | sort -V)

if [ -z "$PHP_VERSIONS" ]; then
    echo "❌ No se encontraron versiones de PHP en cPanel (/opt/cpanel/ea-php*)"
    exit 1
fi

echo "Versiones de PHP encontradas:"
echo "$PHP_VERSIONS" | sed 's/.*ea-php/- PHP /' | sed 's/$//'
echo

for php_dir in $PHP_VERSIONS; do
    php_ini="$php_dir/root/etc/php.ini"
    version=$(basename "$php_dir" | sed 's/ea-php//')
    
    echo "--- Procesando PHP $version ---"
    
    if [ ! -f "$php_ini" ]; then
        echo "⚠️  No existe: $php_ini"
        continue
    fi
    
    # Crear backup
    cp "$php_ini" "$php_ini.backup.$(date +%Y%m%d_%H%M%S)"
    echo "✅ Backup creado"
    
    # Mostrar configuración actual
    current_prob=$(grep -E "^session\.gc_probability\s*=" "$php_ini" | cut -d'=' -f2 | tr -d ' ')
    current_div=$(grep -E "^session\.gc_divisor\s*=" "$php_ini" | cut -d'=' -f2 | tr -d ' ')
    
    echo "   Configuración actual: gc_probability=$current_prob, gc_divisor=$current_div"
    
    # Aplicar cambios
    sed -i 's/^session\.gc_probability\s*=.*/session.gc_probability = 1/' "$php_ini"
    sed -i 's/^session\.gc_divisor\s*=.*/session.gc_divisor = 1000/' "$php_ini"
    
    # Verificar cambios
    new_prob=$(grep -E "^session\.gc_probability\s*=" "$php_ini" | cut -d'=' -f2 | tr -d ' ')
    new_div=$(grep -E "^session\.gc_divisor\s*=" "$php_ini" | cut -d'=' -f2 | tr -d ' ')
    
    echo "   Nueva configuración: gc_probability=$new_prob, gc_divisor=$new_div"
    echo "   ✅ PHP $version actualizado"
    echo
done

echo "=== Resumen ==="
echo "✅ Cambios aplicados a todas las versiones PHP"
echo "⚠️  Los cambios se aplicarán en nuevas sesiones PHP"
echo

# Comando de verificación
echo "Verificar cambios:"
echo "grep -E 'session\.gc_(probability|divisor)' /opt/cpanel/ea-php*/root/etc/php.ini"

Version actualizada del script se mantiene en Git Lab Castris - Utilidades

Uso del script:

chmod +x fix_php_gc_all_versions.sh
./fix_php_gc_all_versions.sh

Análisis de la configuración PHP problemática

Configuración actual encontrada en servidores cPanel:

session.gc_probability = 0    ; ⚠️ PROBLEMA: Numerador en 0
session.gc_divisor = 0        ; ⚠️ PROBLEMA: Denominador en 0
session.gc_maxlifetime = 1440 ; ✅ Correcto: 24 minutos (1440 segundos)

Explicación del problema: El garbage collector de PHP funciona con esta fórmula:

  • Probabilidad de limpieza = gc_probability / gc_divisor

Con la configuración actual: 0 / 0 = indefinidoGarbage collector completamente desactivado

Qué significa cada parámetro:

  • gc_probability: Numerador de la probabilidad (cuántas veces de cada X)
  • gc_divisor: Denominador de la probabilidad (el total X)
  • gc_maxlifetime: Tiempo en segundos después del cual una sesión se considera "basura"

Ejemplos de probabilidades:

  • 1/100 = 1% → 1 de cada 100 peticiones ejecuta limpieza
  • 1/1000 = 0.1% → 1 de cada 1000 peticiones ejecuta limpieza
  • 0/0 = desactivado → Nunca se ejecuta limpieza automática

Configuración PHP corregida

Para servidores de producción (recomendado):

session.gc_probability = 1    ; Cambiar de 0 a 1
session.gc_divisor = 1000     ; Cambiar de 0 a 1000 (0.1% probabilidad)
session.gc_maxlifetime = 1440 ; Mantener actual

Para servidores de desarrollo/bajo tráfico:

session.gc_probability = 1    ; Cambiar de 0 a 1
session.gc_divisor = 100      ; Cambiar de 0 a 100 (1% probabilidad)
session.gc_maxlifetime = 1440 ; Mantener actual

Justificación de los valores:

  • Producción (1/1000): Menor impacto en rendimiento, suficiente con alto tráfico
  • Desarrollo (1/100): Mayor frecuencia de limpieza necesaria con poco tráfico

Monitoreo de inodos

Script para alertas:

#!/bin/bash
#
# check_tmp_inodes.sh
# Checks the inode usage on /tmp and sends an alert if it exceeds a threshold.
#

# Ensure the script stops if any command fails
set -euo pipefail

# --- CONFIGURATION ---
# Path to the main configuration file
# Check repo for crons/.check_tmp_inodes.cfg file demo
# Not edit this file, copy the crons/.check_tmp_inodes.cfg file to /root/.check_tmp_inodes.cfg
CONFIG_FILE="/root/.check_tmp_inodes.cfg"

# --- FUNCTIONS ---

# Function to log errors and exit
# Usage: error_exit "Error message"
error_exit() {
    echo "ERROR: $1" >&2
    exit 1
}

# --- MAIN SCRIPT ---

# 1. Check if the configuration file exists and load it
if [ ! -f "$CONFIG_FILE" ]; then
    error_exit "Configuration file not found at $CONFIG_FILE"
fi

# Load variables from the configuration file
# shellcheck source=/dev/null
source "$CONFIG_FILE"

# 2. Verify that the necessary variables are defined
if [ -z "${EMAIL_TO-}" ] || [ -z "${THRESHOLD-}" ]; then
    error_exit "EMAIL_TO and THRESHOLD variables must be defined in $CONFIG_FILE"
fi

# 3. Get the current inode usage
# 'tail -n1' is used to be more robust on systems where 'df' might have more than 2 lines
USAGE=$(df -i /tmp | tail -n1 | awk '{print $5}' | sed 's/%//')

if ! [[ "$USAGE" =~ ^[0-9]+$ ]]; then
    error_exit "Could not retrieve a numeric value for inode usage. Got: '$USAGE'"
fi

# 4. Compare usage with the threshold and send an alert if necessary
echo "INFO: Current inode usage on /tmp: ${USAGE}% (Alert threshold: >${THRESHOLD}%)"

if [ "$USAGE" -gt "$THRESHOLD" ]; then
    # Build the message body
    HOSTNAME=$(hostname -f)
    BODY="ALERT on server: $HOSTNAME

Inode usage in the /tmp directory has exceeded the ${THRESHOLD}% threshold.

Current usage: ${USAGE}%

It is recommended to check the contents of /tmp to free up inode space.
"
    # Send the email
    echo "$BODY" | mail -s "$EMAIL_SUBJECT" "$EMAIL_TO"
    
    echo "ALERT: Threshold exceeded. Notification email sent to $EMAIL_TO."
else
    echo "INFO: Inode usage is within normal limits."
fi

exit 0

Este script se mantiene en Gitlab Castris - Utilidades

Configuración de directorios temporales seguros

Problema con la configuración actual

  • /home/user/tmp → enlace simbólico a /tmp (cPanel)
  • Todos los usuarios comparten el mismo espacio de inodos

Solución recomendada

Configurar directorios temporales individuales:

// En configuración PHP por usuario
session.save_path = "/home/USERNAME/temporal"
upload_tmp_dir = "/home/USERNAME/temporal"

Monitoreo continuo

Comando para verificar estado actual

echo "Espacio: $(df -h /tmp | awk 'NR==2 {print $5}')"
echo "Inodos: $(df -i /tmp | awk 'NR==2 {print $5}')"
echo "Archivos sess: $(find /tmp -maxdepth 1 -name 'sess*' 2>/dev/null | wc -l)"

Señales de alerta temprana

  • Uso de inodos > 80% en /tmp
  • Más de 10,000 archivos sess_*
  • Quejas de usuarios sobre lentitud en aplicaciones web

Severidad: Crítica - Afecta disponibilidad de MySQL y aplicaciones web
Impacto: Todos los usuarios del servidor
Tiempo de resolución: 2-5 minutos con la solución inmediata

Aviso

Esta documentación y su contenido, no implica que funcione en tu caso o determinados casos. También implica que tienes conocimientos sobre lo que trata, y que en cualquier caso tienes copias de seguridad. El contenido el contenido se entrega, tal y como está, sin que ello implique ningún obligación ni responsabilidad por parte de Castris

Si necesitas soporte profesional puedes contratar con Castris soporte profesional.