--- title: "Cómo diagnosticar un sitio web lento en Ubuntu 24.04" description: "Averigua por qué tu web va lenta en Ubuntu 24.04: mide con curl, revisa CPU, memoria y disco, y localiza lo lento en Nginx, PHP-FPM y MySQL con sus registros." category: "Guía de Solución de Problemas" language: es source: https://cubepath.com/es/docs/guía-de-solución-de-problemas/analisis-de-rendimiento-de-sitio-web-lento updated: 2026-09-25 --- # Cómo diagnosticar un sitio web lento en Ubuntu 24.04 Un sitio web lento puede deberse a la red, al servidor web, a la aplicación, a la base de datos o simplemente a un servidor sin recursos. Cambiar ajustes al azar rara vez funciona: lo eficaz es medir dónde se va el tiempo y corregir solo esa parte. En este tutorial diagnosticarás paso a paso una web servida con Nginx, PHP-FPM y MySQL en Ubuntu 24.04, desde la primera medición con `curl` hasta una prueba de carga que confirme la mejora. ## Requisitos previos Para seguir esta guía necesitas: - Un servidor con Ubuntu 24.04 LTS que sirva la web con Nginx, PHP-FPM 8.3 y MySQL 8.0 (una pila LEMP típica, por ejemplo WordPress en un VPS de CubePath). - Un usuario no root con privilegios `sudo`. - El dominio del sitio, `your_domain` en esta guía, apuntando al servidor. - Otra máquina (tu ordenador u otro servidor) desde la que medir y lanzar la prueba de carga. Si usas Apache en lugar de Nginx, el razonamiento es idéntico; en el paso 3 se indica el equivalente. ## Paso 1: Medir dónde se va el tiempo Antes de tocar el servidor, mide una petición desde fuera. `curl` puede desglosar el tiempo de cada fase de la conexión. Ejecútalo desde tu ordenador: ```bash curl -o /dev/null -s -w 'DNS: %{time_namelookup}s\nTCP: %{time_connect}s\nTLS: %{time_appconnect}s\nPrimer byte: %{time_starttransfer}s\nTotal: %{time_total}s\nTamaño: %{size_download} bytes\n' https://your_domain/ ``` ```output DNS: 0.012s TCP: 0.048s TLS: 0.121s Primer byte: 2.874s Total: 2.931s Tamaño: 84213 bytes ``` Los tiempos son acumulados, así que interesa la diferencia entre fases: - **DNS, TCP y TLS altos** (cientos de milisegundos cada uno): el problema está en la red o en la distancia al servidor, no en la aplicación. - **Mucho tiempo entre TLS y primer byte** (en el ejemplo, más de 2,7 segundos): el servidor tarda en generar la página. Es el caso más habitual y el que cubre el resto de la guía. - **Mucho tiempo entre primer byte y total**: la respuesta es muy pesada o la conexión es lenta. Revisa compresión y tamaño de imágenes (paso 6). Repite la medición con un archivo estático, por ejemplo una imagen o `/robots.txt`. Si el estático responde en milisegundos y la página tarda segundos, el cuello de botella está en PHP o en la base de datos, no en Nginx ni en la red. ## Paso 2: Comprobar los recursos del servidor Una aplicación correcta también va lenta en un servidor saturado. Conéctate por SSH y empieza por la carga y el número de CPU: ```bash uptime nproc ``` ```output 10:42:15 up 12 days, 3:10, 1 user, load average: 5.82, 5.40, 4.97 2 ``` Una carga media sostenida por encima del número de CPU (aquí 5,8 con 2 CPU) indica que hay más trabajo del que el servidor puede atender. `vmstat` muestra de qué tipo es: ```bash vmstat 2 5 ``` ```output procs -----------memory---------- ---swap-- -----io---- -system-- -------cpu------- r b swpd free buff cache si so bi bo in cs us sy id wa st 6 0 512000 48120 10240 301220 120 340 95 410 1820 2950 78 12 2 6 2 ``` Fíjate en estas columnas: - `r` mayor que el número de CPU y `us` alto: la CPU está saturada. Busca el proceso responsable con `top` (pulsa `P` para ordenar por CPU). - `si` y `so` distintos de cero de forma continua: el sistema está usando swap porque falta memoria. Esto por sí solo multiplica los tiempos de respuesta. - `wa` alto (más de 10-20): los procesos esperan al disco. - `st` alto: tiempo robado por el hipervisor a una máquina virtual. Si es persistente, el plan del VPS se queda corto para la carga. Comprueba la memoria disponible y qué procesos la consumen: ```bash free -h ps -eo pid,user,rss,comm --sort=-rss | head -n 10 ``` Si `wa` es alto, instala `sysstat` para ver qué disco está ocupado y cuánto: ```bash sudo apt install sysstat iostat -xz 2 3 ``` Una columna `%util` cercana al 100 % o un `r_await`/`w_await` de decenas de milisegundos confirma que el disco es el cuello de botella. En una web suele deberse a MySQL sin memoria suficiente para su caché (paso 5). ## Paso 3: Registrar el tiempo de cada petición en Nginx El registro de acceso por defecto de Nginx no incluye cuánto tarda cada petición. Añade un formato que lo registre. Crea un archivo en `conf.d`, que Nginx carga dentro del bloque `http`: ```bash sudo nano /etc/nginx/conf.d/timing-log.conf ``` ```nginx log_format timing '$request_time $upstream_response_time $status ' '"$request" $remote_addr "$http_user_agent"'; ``` `$request_time` es el tiempo total que Nginx dedicó a la petición y `$upstream_response_time` el que tardó PHP-FPM en responder. Ahora usa ese formato en el bloque `server` de tu sitio, junto a la línea `access_log` existente: ```bash sudo nano /etc/nginx/sites-available/your_domain ``` ```nginx access_log /var/log/nginx/your_domain.timing.log timing; ``` Comprueba la sintaxis y recarga: ```bash sudo nginx -t sudo systemctl reload nginx ``` ```output nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful ``` Deja pasar tráfico real durante un rato y lista las peticiones más lentas. Como el tiempo es el primer campo, basta con ordenar: ```bash sudo sort -rn /var/log/nginx/your_domain.timing.log | head -n 15 ``` ```output 8.412 8.410 200 "GET /tienda/?filtro=precio&orden=asc HTTP/2.0" 198.51.100.24 "Mozilla/5.0 ..." 6.905 6.903 200 "POST /wp-admin/admin-ajax.php HTTP/2.0" 203.0.113.50 "Mozilla/5.0 ..." 0.004 - 200 "GET /wp-content/uploads/logo.png HTTP/2.0" 198.51.100.24 "Mozilla/5.0 ..." ``` Si ambos tiempos son casi iguales, el tiempo se consume en PHP o en la base de datos. Agrupa por URL para saber qué rutas son lentas de forma sistemática y no por un pico puntual: ```bash sudo awk '$2 != "-" {split($5, u, "?"); t[u[1]] += $1; n[u[1]]++} END {for (k in t) printf "%.3f s %5d peticiones %s\n", t[k]/n[k], n[k], k}' /var/log/nginx/your_domain.timing.log | sort -rn | head -n 10 ``` Revisa también cuántas peticiones recibe el sitio por IP. Un único cliente con miles de peticiones (un bot o un rastreador agresivo) puede ser la verdadera causa: ```bash sudo awk '{print $7}' /var/log/nginx/your_domain.timing.log | sort | uniq -c | sort -rn | head -n 10 ``` > Nota: en Apache el equivalente es añadir `%D` (microsegundos) a un `LogFormat` en `/etc/apache2/apache2.conf` y usarlo en el `CustomLog` del sitio. ## Paso 4: Analizar PHP-FPM ### Comprobar si faltan procesos PHP-FPM atiende cada petición con un proceso del pool. Cuando todos están ocupados, las nuevas peticiones esperan en cola y la web se ralentiza aunque la CPU no esté al máximo. PHP-FPM lo avisa en su registro: ```bash sudo grep -i 'max_children' /var/log/php8.3-fpm.log | tail -n 5 ``` ```output [25-Sep-2026 10:31:02] WARNING: [pool www] server reached pm.max_children setting (5), consider raising it ``` El valor por defecto en Ubuntu es `pm.max_children = 5`, escaso para casi cualquier sitio con tráfico. Para calcular cuántos procesos caben, mide la memoria media de cada uno: ```bash ps -C php-fpm8.3 -o rss= | awk '{s += $1; n++} END {printf "%d procesos, media %.0f MB\n", n, s/n/1024}' ``` ```output 6 procesos, media 62 MB ``` Divide la memoria que puedes dedicar a PHP entre esa media. Por ejemplo, en un servidor de 4 GB en el que MySQL y el sistema necesitan 1,5 GB, quedan unos 2,5 GB: 2500 / 62 ≈ 40 procesos. Edita el pool: ```bash sudo nano /etc/php/8.3/fpm/pool.d/www.conf ``` ```ini pm = dynamic pm.max_children = 40 pm.start_servers = 8 pm.min_spare_servers = 4 pm.max_spare_servers = 12 ``` No subas `pm.max_children` por encima de lo que cabe en memoria: si el servidor empieza a usar swap, la web irá más lenta que antes. ### Activar el registro de peticiones lentas El *slowlog* de PHP-FPM guarda la traza de pila de cualquier script que supere un tiempo dado, lo que señala la función exacta que tarda. En el mismo archivo `www.conf`, añade o descomenta: ```ini slowlog = /var/log/php8.3-fpm.slow.log request_slowlog_timeout = 3s ``` Comprueba la configuración y reinicia PHP-FPM: ```bash sudo php-fpm8.3 -t sudo systemctl restart php8.3-fpm ``` ```output [25-Sep-2026 10:45:12] NOTICE: configuration file /etc/php/8.3/fpm/php-fpm.conf test is successful ``` Cuando se produzca una petición lenta, el registro mostrará algo parecido a esto: ```bash sudo tail -n 20 /var/log/php8.3-fpm.slow.log ``` ```output [25-Sep-2026 10:52:40] [pool www] pid 41822 script_filename = /var/www/your_domain/index.php [0x00007f3a1c214a10] mysqli_query() /var/www/your_domain/wp-includes/class-wpdb.php:2351 [0x00007f3a1c214980] _do_query() /var/www/your_domain/wp-includes/class-wpdb.php:2265 [0x00007f3a1c2148e0] query() /var/www/your_domain/wp-includes/class-wpdb.php:3161 ``` Una traza que termina en `mysqli_query()` o `PDO->execute()` apunta a la base de datos; una que termina en `curl_exec()` o `file_get_contents()` apunta a una llamada a un servicio externo que tarda en responder. ### Confirmar que OPcache está activo OPcache guarda el código PHP ya compilado en memoria y viene activado por defecto en el paquete de Ubuntu. Comprueba que el módulo está cargado en PHP-FPM: ```bash ls /etc/php/8.3/fpm/conf.d/ | grep opcache ``` ```output 10-opcache.ini ``` Si no aparece, instala y activa el módulo con `sudo apt install php8.3-opcache` y reinicia `php8.3-fpm`. ## Paso 5: Encontrar consultas lentas en MySQL ### Activar el registro de consultas lentas MySQL puede registrar cada consulta que supere un umbral. Actívalo en caliente, sin reiniciar el servicio: ```bash sudo mysql -e "SET GLOBAL slow_query_log_file = '/var/log/mysql/mysql-slow.log'; SET GLOBAL long_query_time = 1; SET GLOBAL slow_query_log = 'ON';" ``` Para que el ajuste sobreviva a un reinicio, añádelo al archivo de configuración del servidor, en la sección `[mysqld]`: ```bash sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf ``` ```ini slow_query_log = 1 slow_query_log_file = /var/log/mysql/mysql-slow.log long_query_time = 1 ``` Tras un periodo de tráfico, resume las consultas que más tiempo total consumen con `mysqldumpslow`, que agrupa las consultas iguales con distintos valores: ```bash sudo mysqldumpslow -s t -t 5 /var/log/mysql/mysql-slow.log ``` ```output Count: 214 Time=2.41s (515s) Lock=0.00s (0s) Rows=12.0 (2568), wp_user[wp_user]@localhost SELECT * FROM wp_postmeta WHERE meta_key = 'S' AND meta_value = 'S' ``` ### Ver qué se está ejecutando ahora Si la web está lenta en este momento, consulta las consultas en curso: ```bash sudo mysql -e "SHOW FULL PROCESSLIST;" ``` Las filas con un valor alto en `Time` y un estado como `Sending data` o `Waiting for table metadata lock` son las que retienen al resto. ### Analizar una consulta con EXPLAIN Toma una consulta del registro y antepón `EXPLAIN` para ver cómo la ejecuta MySQL: ```bash sudo mysql your_database -e "EXPLAIN SELECT * FROM wp_postmeta WHERE meta_key = 'color' AND meta_value = 'rojo'\G" ``` ```output type: ALL possible_keys: meta_key key: NULL rows: 1843210 Extra: Using where ``` `type: ALL` con `key: NULL` significa que MySQL recorre la tabla entera. Un índice adecuado suele reducir la consulta de segundos a milisegundos. Crea el índice fuera de las horas de más tráfico y con copia de seguridad reciente, porque en tablas grandes puede tardar: ```bash sudo mysql your_database -e "ALTER TABLE wp_postmeta ADD INDEX idx_meta_key_value (meta_key, meta_value(32));" ``` ### Revisar la caché de InnoDB El *buffer pool* de InnoDB guarda en memoria los datos e índices más usados. Su tamaño por defecto es de 128 MB, insuficiente si la base de datos ocupa varios GB. Comprueba el valor actual y el tamaño de tus datos: ```bash sudo mysql -e "SELECT @@innodb_buffer_pool_size/1024/1024 AS buffer_pool_mb;" sudo mysql -e "SELECT table_schema, ROUND(SUM(data_length + index_length)/1024/1024) AS mb FROM information_schema.tables GROUP BY table_schema;" ``` En un servidor que solo aloja esta web, asigna al *buffer pool* lo suficiente para que quepan los datos activos, sin superar el 50-60 % de la RAM cuando comparte máquina con Nginx y PHP-FPM. Añade el valor en la sección `[mysqld]` de `mysqld.cnf`: ```ini innodb_buffer_pool_size = 1G ``` Reinicia MySQL para aplicarlo: ```bash sudo systemctl restart mysql ``` ## Paso 6: Revisar compresión y caché del navegador Las respuestas de texto sin comprimir y los archivos estáticos que el navegador vuelve a descargar en cada visita hacen que la web parezca lenta aunque el servidor responda rápido. Comprueba las cabeceras de una hoja de estilos del sitio (sustituye la ruta por una real, la ves en el código fuente de la página): ```bash curl -s -o /dev/null -D - -H 'Accept-Encoding: gzip' https://your_domain/css/style.css | grep -i -E 'content-encoding|cache-control' ``` Si no aparece `content-encoding: gzip`, Nginx solo está comprimiendo HTML, que es lo único que comprime con la configuración por defecto. El `nginx.conf` de Ubuntu ya incluye `gzip on;`, así que añade el resto de tipos en un archivo propio: ```bash sudo nano /etc/nginx/conf.d/gzip.conf ``` ```nginx gzip_vary on; gzip_proxied any; gzip_comp_level 5; gzip_min_length 1024; gzip_types text/css application/javascript application/json image/svg+xml text/plain text/xml application/xml; ``` Si has descomentado alguna de estas directivas en `/etc/nginx/nginx.conf`, no la repitas aquí: Nginx rechaza las directivas duplicadas. Para que el navegador reutilice los estáticos, añade este bloque dentro del `server` de tu sitio: ```nginx location ~* \.(css|js|png|jpg|jpeg|gif|webp|svg|ico|woff2)$ { expires 30d; access_log off; } ``` Comprueba y recarga: ```bash sudo nginx -t sudo systemctl reload nginx ``` Vuelve a ejecutar el `curl` del principio de este paso: ahora debe mostrar `content-encoding: gzip` y `cache-control: max-age=2592000`. ## Paso 7: Validar con una prueba de carga Una prueba de carga confirma si los cambios han mejorado la capacidad del servidor. Instala Apache Bench en la máquina desde la que vas a medir (no en el propio servidor, para no competir por la CPU): ```bash sudo apt install apache2-utils ``` Lanza 500 peticiones con 20 conexiones simultáneas contra la página que antes era lenta: ```bash ab -n 500 -c 20 https://your_domain/ ``` ```output Concurrency Level: 20 Time taken for tests: 9.842 seconds Complete requests: 500 Failed requests: 0 Requests per second: 50.80 [#/sec] (mean) Time per request: 393.701 [ms] (mean) Percentage of the requests served within a certain time (ms) 50% 371 95% 512 99% 688 100% 802 (longest request) ``` Anota `Requests per second` y los percentiles 95 y 99 antes y después de cada cambio. Mide los cambios de uno en uno para saber cuál ha tenido efecto. > Advertencia: una prueba de carga contra producción afecta a los visitantes reales. Hazla en horas de poco tráfico y empieza con una concurrencia baja. ## Solución de problemas **Nginx devuelve `504 Gateway Time-out`**: PHP-FPM no respondió a tiempo. Revisa el *slowlog* del paso 4 y el registro de consultas lentas del paso 5 para encontrar la petición que tarda. Aumentar `fastcgi_read_timeout` solo oculta el problema. **Nginx devuelve `502 Bad Gateway` bajo carga**: todos los procesos de PHP-FPM están ocupados o se han cerrado por falta de memoria. Busca `max_children` en `/var/log/php8.3-fpm.log` y `Out of memory` en `sudo journalctl -k`. **El servidor usa swap aunque hay poco tráfico**: la suma de `innodb_buffer_pool_size` y `pm.max_children` multiplicado por la memoria de cada proceso PHP supera la RAM. Reduce uno de los dos o amplía el plan. **La web va lenta solo en algunas horas**: compara las horas lentas con el registro de Nginx del paso 3. Una tarea programada (copias de seguridad, `wp-cron`) o un rastreador que hace miles de peticiones suele coincidir con esas horas. ## Conclusión Has medido una petición por fases, comprobado los recursos del servidor, registrado el tiempo de cada petición en Nginx y localizado las partes lentas en PHP-FPM y MySQL, además de validar los cambios con una prueba de carga. Como siguientes pasos, añade una caché de página (FastCGI cache de Nginx o un plugin de caché si usas WordPress), usa Redis como caché de objetos para reducir las consultas a MySQL y configura una monitorización continua que avise cuando el tiempo de respuesta suba.