--- title: "Cómo optimizar el rendimiento de WordPress en Ubuntu 24.04" description: "Acelera WordPress en Ubuntu 24.04 con OPcache, PHP-FPM y MySQL ajustados, caché de objetos Redis y caché de página FastCGI en Nginx, midiendo cada mejora." category: "E-commerce y CMS" language: es source: https://cubepath.com/es/docs/e-commerce-y-cms/guia-completa-de-optimizacion-de-rendimiento-de-wordpress updated: 2026-09-25 --- # Cómo optimizar el rendimiento de WordPress en Ubuntu 24.04 Un WordPress recién instalado genera cada página desde cero: PHP compila el código, lanza decenas de consultas a MySQL y monta el HTML en cada visita. Con unos pocos ajustes en el servidor ese trabajo se reduce drásticamente. En este tutorial optimizarás un WordPress que corre en Ubuntu 24.04 con Nginx, PHP 8.3-FPM y MySQL: activarás OPcache, dimensionarás PHP-FPM y MySQL, añadirás Redis como caché de objetos y una caché de página FastCGI en Nginx, y medirás la mejora. ## Requisitos previos Para seguir esta guía necesitas: - Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath. Los valores de ejemplo están pensados para 4 GB de RAM; ajústalos a tu servidor como se explica en cada paso. - Un usuario no root con privilegios `sudo`. - WordPress funcionando en `/var/www/wordpress` con Nginx, PHP 8.3-FPM y MySQL 8.0 (o MariaDB), con HTTPS. El bloque de servidor de los ejemplos está en `/etc/nginx/sites-available/wordpress` y el dominio es `example.com`: sustitúyelos por los tuyos. - WP-CLI instalado en `/usr/local/bin/wp`. ## Paso 1: Medir el punto de partida Sin una medida inicial no sabrás qué ajuste ha servido. El dato más útil en el servidor es el **TTFB** (tiempo hasta el primer byte), que refleja lo que tarda PHP en generar la página. Mídelo varias veces: ```bash for i in 1 2 3; do curl -o /dev/null -s -w 'TTFB: %{time_starttransfer}s Total: %{time_total}s\n' https://example.com/; done ``` ```output TTFB: 0.842s Total: 0.861s TTFB: 0.795s Total: 0.812s TTFB: 0.810s Total: 0.829s ``` Para ver cómo se comporta con carga concurrente, instala `ab` (Apache Bench) y lanza 200 peticiones con 10 clientes simultáneos: ```bash sudo apt install -y apache2-utils ab -n 200 -c 10 https://example.com/ ``` Apunta el valor de `Requests per second` y `Time per request`. Repetirás estas mismas pruebas al final. > Nota: lanza las pruebas de carga contra tu propio servidor y fuera de horas de tráfico. Para medir la experiencia real del navegador (imágenes, JavaScript, Core Web Vitals) usa además PageSpeed Insights. ## Paso 2: Activar y ajustar OPcache OPcache guarda en memoria el código PHP ya compilado, de modo que no se recompila en cada petición. Viene incluido en PHP 8.3, pero los valores por defecto se quedan cortos para WordPress con muchos plugins. Crea un archivo de configuración propio: ```bash sudo nano /etc/php/8.3/fpm/conf.d/99-wordpress-opcache.ini ``` ```ini opcache.enable=1 opcache.memory_consumption=256 opcache.interned_strings_buffer=16 opcache.max_accelerated_files=20000 opcache.validate_timestamps=1 opcache.revalidate_freq=60 ``` `revalidate_freq=60` hace que PHP compruebe cada 60 segundos si un archivo ha cambiado, en lugar de en cada petición. Tras actualizar un plugin, los cambios pueden tardar hasta un minuto en verse, o reinicia PHP-FPM para aplicarlos al momento. Reinicia PHP-FPM y comprueba que los valores se han cargado: ```bash sudo systemctl restart php8.3-fpm php-fpm8.3 -i | grep -E 'opcache.enable =>|opcache.memory_consumption' ``` ```output opcache.enable => On => On opcache.memory_consumption => 256 => 256 ``` ## Paso 3: Dimensionar PHP-FPM PHP-FPM mantiene un grupo de procesos que atienden las peticiones. Si hay pocos, las peticiones hacen cola; si hay demasiados, el servidor se queda sin memoria y empieza a usar swap, que es mucho peor. El número correcto depende de cuánta memoria usa cada proceso en tu sitio. Mídelo con el sitio en uso: ```bash ps --no-headers -o rss -C php-fpm8.3 | awk '{sum+=$1; n++} END {printf "%d procesos, media %.0f MB\n", n, sum/n/1024}' ``` ```output 6 procesos, media 72 MB ``` Calcula `pm.max_children` como la memoria que puedes dedicar a PHP dividida entre ese consumo medio. En un servidor de 4 GB, reservando unos 1,8 GB para el sistema, MySQL y Redis, quedan unos 2,2 GB para PHP: 2200 / 72 ≈ 30 procesos. Edita el grupo de procesos por defecto: ```bash sudo nano /etc/php/8.3/fpm/pool.d/www.conf ``` Busca y ajusta estas directivas (las de `slowlog` están comentadas; descoméntalas): ```ini pm = dynamic pm.max_children = 30 pm.start_servers = 6 pm.min_spare_servers = 4 pm.max_spare_servers = 10 pm.max_requests = 500 request_slowlog_timeout = 5s slowlog = /var/log/php8.3-fpm-slow.log ``` - `pm.max_requests = 500` recicla cada proceso tras 500 peticiones, lo que evita que una fuga de memoria de un plugin crezca sin límite. - `request_slowlog_timeout` registra la traza de cualquier petición que tarde más de 5 segundos, con la función de PHP en la que estaba. Es la forma más rápida de encontrar el plugin que ralentiza el sitio. Aumenta también el límite de memoria de PHP para WordPress: ```bash sudo nano /etc/php/8.3/fpm/conf.d/99-wordpress.ini ``` ```ini memory_limit = 256M ``` Comprueba la configuración y reinicia: ```bash sudo php-fpm8.3 -t sudo systemctl restart php8.3-fpm ``` ```output NOTICE: configuration file /etc/php/8.3/fpm/php-fpm.conf test is successful ``` Si más adelante ves en `/var/log/php8.3-fpm.log` el aviso `server reached pm.max_children setting`, primero comprueba si hay memoria libre (`free -h`) antes de subir el valor. ## Paso 4: Ajustar MySQL y revisar la base de datos El ajuste que más influye en MySQL es `innodb_buffer_pool_size`: la memoria donde InnoDB guarda las tablas e índices. Si la base de datos cabe entera en ella, las lecturas no tocan el disco. Consulta primero el tamaño de tu base de datos (sustituye `wordpress` por su nombre): ```bash sudo mysql -e "SELECT table_schema, ROUND(SUM(data_length + index_length)/1024/1024) AS size_mb FROM information_schema.tables WHERE table_schema = 'wordpress' GROUP BY table_schema;" ``` ```output +--------------+---------+ | table_schema | size_mb | +--------------+---------+ | wordpress | 420 | +--------------+---------+ ``` Crea un archivo de configuración propio. En MySQL va en `/etc/mysql/mysql.conf.d/`; en MariaDB, en `/etc/mysql/mariadb.conf.d/`: ```bash sudo nano /etc/mysql/mysql.conf.d/99-wordpress.cnf ``` ```ini [mysqld] innodb_buffer_pool_size = 1G slow_query_log = 1 slow_query_log_file = /var/log/mysql/slow.log long_query_time = 1 ``` Con una base de datos de 420 MB, 1 GB deja margen para crecer. Nunca le des más de la mitad de la RAM en un servidor que también ejecuta PHP. El registro de consultas lentas guarda las que tarden más de un segundo. Reinicia MySQL: ```bash sudo systemctl restart mysql sudo mysql -e "SELECT @@innodb_buffer_pool_size/1024/1024 AS buffer_pool_mb;" ``` ```output +----------------+ | buffer_pool_mb | +----------------+ | 1024.00000000 | +----------------+ ``` Tras unos días de tráfico, resume las consultas lentas agrupadas por patrón: ```bash sudo mysqldumpslow -s t -t 10 /var/log/mysql/slow.log ``` ### Revisar las opciones autocargadas WordPress carga en cada petición todas las filas de `wp_options` marcadas como autocargadas. Plugins desinstalados suelen dejar ahí datos que ya nadie usa. Mide el total (WordPress 6.6 cambió los valores de la columna `autoload`, por eso la consulta incluye los antiguos y los nuevos): ```bash sudo -u www-data wp --path=/var/www/wordpress db query "SELECT ROUND(SUM(LENGTH(option_value))/1024) AS autoload_kb FROM wp_options WHERE autoload IN ('yes','on','auto','auto-on');" ``` Si supera 1 MB, lista las opciones más grandes para ver qué plugin las creó: ```bash sudo -u www-data wp --path=/var/www/wordpress db query "SELECT option_name, ROUND(LENGTH(option_value)/1024) AS kb FROM wp_options WHERE autoload IN ('yes','on','auto','auto-on') ORDER BY LENGTH(option_value) DESC LIMIT 10;" ``` Si una opción pertenece a un plugin que ya no usas, bórrala con `wp option delete nombre_opcion`, siempre después de hacer una copia de la base de datos. Elimina también los transitorios caducados: ```bash sudo -u www-data wp --path=/var/www/wordpress transient delete --expired ``` Si tu prefijo de tablas no es `wp_`, cámbialo en las consultas. ## Paso 5: Añadir Redis como caché de objetos La caché de objetos guarda el resultado de las consultas a la base de datos entre peticiones. Sin ella, WordPress repite las mismas consultas en cada visita. Es especialmente útil para usuarios identificados, el panel de administración y tiendas WooCommerce, que no pueden usar la caché de página. Instala Redis y la extensión de PHP: ```bash sudo apt install -y redis-server php-redis ``` Limita la memoria de Redis y haz que descarte las claves menos usadas cuando se llene, en lugar de devolver errores: ```bash sudo nano /etc/redis/redis.conf ``` Busca las directivas `maxmemory` y `maxmemory-policy` (están comentadas) y déjalas así: ```ini maxmemory 256mb maxmemory-policy allkeys-lru ``` Reinicia Redis y PHP-FPM, y comprueba que Redis responde: ```bash sudo systemctl restart redis-server php8.3-fpm redis-cli ping ``` ```output PONG ``` Define un prefijo para las claves de este sitio (evita colisiones si varios WordPress comparten el mismo Redis) e instala el plugin **Redis Object Cache**: ```bash sudo -u www-data wp --path=/var/www/wordpress config set WP_REDIS_PREFIX 'example_' sudo -u www-data wp --path=/var/www/wordpress plugin install redis-cache --activate sudo -u www-data wp --path=/var/www/wordpress redis enable ``` `wp redis enable` copia el archivo `object-cache.php` en `wp-content`, que es el que conecta WordPress con Redis. Comprueba el estado: ```bash sudo -u www-data wp --path=/var/www/wordpress redis status ``` ```output Status: Connected Client: PhpRedis (v5.3.7) Drop-in: Valid ``` Navega unos minutos por el sitio y comprueba que Redis acierta en la mayoría de las lecturas: ```bash redis-cli info stats | grep -E 'keyspace_(hits|misses)' ``` ```output keyspace_hits:18452 keyspace_misses:1210 ``` ## Paso 6: Configurar la caché de página FastCGI en Nginx La caché de página es el cambio con más impacto: Nginx guarda el HTML generado y lo sirve directamente a los visitantes anónimos sin llamar a PHP ni a MySQL. Hay que excluir con cuidado todo lo que sea personal: usuarios identificados, el panel, formularios enviados por POST y, si usas WooCommerce, carrito y checkout. Crea el directorio de caché: ```bash sudo install -d -o www-data -g www-data /var/cache/nginx/wordpress ``` La zona de caché y las reglas de exclusión van en el contexto `http`. Crea un archivo en `conf.d`, que Nginx incluye automáticamente: ```bash sudo nano /etc/nginx/conf.d/wordpress-cache.conf ``` ```nginx fastcgi_cache_path /var/cache/nginx/wordpress levels=1:2 keys_zone=WORDPRESS:100m max_size=1g inactive=60m use_temp_path=off; fastcgi_cache_key "$scheme$request_method$host$request_uri"; # 1 = no usar la caché para esta petición map $request_method $skip_cache_method { default 1; GET 0; HEAD 0; } map $query_string $skip_cache_args { default 1; "" 0; } map $request_uri $skip_cache_uri { default 0; ~*^/wp-admin/ 1; ~*^/wp-(login|cron|signup)\.php 1; ~*^/xmlrpc\.php 1; ~*^/wp-json/ 1; ~*^/(cart|checkout|my-account)/ 1; } map $http_cookie $skip_cache_cookie { default 0; ~*wordpress_logged_in_ 1; ~*wp-postpass_ 1; ~*comment_author_ 1; ~*woocommerce_items_in_cart 1; } ``` Si tu tienda usa otros slugs para el carrito o la cuenta (por ejemplo `/carrito/` y `/finalizar-compra/`), añádelos a `$skip_cache_uri`. Ahora activa la caché en el bloque `location` de PHP de tu sitio: ```bash sudo nano /etc/nginx/sites-available/wordpress ``` Sustituye el bloque `location ~ \.php$` por este: ```nginx location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_pass unix:/run/php/php8.3-fpm.sock; fastcgi_cache WORDPRESS; fastcgi_cache_valid 200 301 60m; fastcgi_cache_valid 404 1m; fastcgi_cache_bypass $skip_cache_method $skip_cache_args $skip_cache_uri $skip_cache_cookie; fastcgi_no_cache $skip_cache_method $skip_cache_args $skip_cache_uri $skip_cache_cookie; fastcgi_cache_use_stale error timeout updating http_500 http_503; fastcgi_cache_lock on; add_header X-Cache-Status $upstream_cache_status always; } ``` - `fastcgi_cache_bypass` y `fastcgi_no_cache` se saltan la caché si cualquiera de las variables vale 1. - Nginx no guarda por defecto las respuestas que incluyen `Set-Cookie`, lo que añade una protección extra frente a cachear contenido personal. - `fastcgi_cache_use_stale` sirve la copia anterior si PHP falla o mientras se regenera. > Importante: si tienes cabeceras `add_header` a nivel de `server` (por ejemplo cabeceras de seguridad), Nginx deja de aplicarlas en cualquier `location` que defina su propio `add_header`. Repítelas dentro de este bloque o muévelas a un *snippet* que incluyas en ambos sitios. Comprueba la sintaxis, recarga y pide la portada dos veces: ```bash sudo nginx -t && sudo systemctl reload nginx curl -s -o /dev/null -D - https://example.com/ | grep -i x-cache-status curl -s -o /dev/null -D - https://example.com/ | grep -i x-cache-status ``` ```output x-cache-status: MISS x-cache-status: HIT ``` Comprueba también que el panel nunca se cachea: ```bash curl -s -o /dev/null -D - https://example.com/wp-login.php | grep -i x-cache-status ``` ```output x-cache-status: BYPASS ``` ### Purgar la caché al publicar Sin purga, un artículo editado seguiría mostrándose en su versión antigua hasta 60 minutos. El plugin **Nginx Helper** borra de la caché las páginas afectadas cada vez que publicas o editas contenido. Indícale dónde está la caché e instálalo: ```bash sudo -u www-data wp --path=/var/www/wordpress config set RT_WP_NGINX_HELPER_CACHE_PATH '/var/cache/nginx/wordpress/' sudo -u www-data wp --path=/var/www/wordpress plugin install nginx-helper --activate ``` En **Ajustes > Nginx Helper**, marca **Enable Purge**, elige **nginx Fastcgi cache** y como método de purga **Delete local server cache files**. Este método calcula el nombre del archivo a partir de la misma clave `$scheme$request_method$host$request_uri` que has configurado, por eso no debes cambiar `fastcgi_cache_key`. Para vaciar toda la caché a mano, por ejemplo tras cambiar de tema: ```bash sudo find /var/cache/nginx/wordpress -type f -delete ``` ## Paso 7: Comprimir y cachear los archivos estáticos Ubuntu activa gzip en Nginx pero solo para HTML. Amplíalo a CSS, JavaScript, JSON y SVG: ```bash sudo nano /etc/nginx/conf.d/gzip.conf ``` ```nginx gzip_vary on; gzip_proxied any; gzip_comp_level 5; gzip_min_length 256; gzip_types text/plain text/css text/xml application/javascript application/json application/xml application/rss+xml image/svg+xml; ``` Las imágenes y fuentes ya están comprimidas y no se incluyen. Para que el navegador guarde los estáticos y no los vuelva a pedir en cada visita, añade este bloque dentro del `server` de tu sitio, junto a los demás `location`: ```bash sudo nano /etc/nginx/sites-available/wordpress ``` ```nginx location ~* \.(?:css|js|mjs|jpg|jpeg|png|gif|webp|avif|svg|ico|woff2?)$ { expires 30d; access_log off; try_files $uri =404; } ``` WordPress añade `?ver=` a las URL de CSS y JS de temas y plugins, así que al actualizarlos los navegadores descargan la versión nueva aunque la anterior siga en caché. Recarga y comprueba las cabeceras de un archivo CSS de tu tema: ```bash sudo nginx -t && sudo systemctl reload nginx curl -s -o /dev/null -D - -H 'Accept-Encoding: gzip' https://example.com/wp-includes/css/dist/block-library/style.min.css | grep -iE 'content-encoding|cache-control' ``` ```output content-encoding: gzip cache-control: max-age=2592000 ``` ## Paso 8: Mover WP-Cron al cron del sistema Por defecto WordPress comprueba sus tareas programadas en cada visita, lo que añade trabajo a peticiones de usuarios reales y, con la caché de página, puede no ejecutarse nunca porque PHP apenas recibe visitas. Desactívalo: ```bash sudo -u www-data wp --path=/var/www/wordpress config set DISABLE_WP_CRON true --raw ``` Y lánzalo cada cinco minutos desde el cron del sistema: ```bash sudo nano /etc/cron.d/wordpress ``` ```text */5 * * * * www-data /usr/local/bin/wp --path=/var/www/wordpress cron event run --due-now --quiet ``` Comprueba que las tareas se ejecutan correctamente a mano: ```bash sudo -u www-data wp --path=/var/www/wordpress cron event run --due-now ``` ```output Success: Executed a total of 4 cron events. ``` ## Paso 9: Medir de nuevo Repite las pruebas del paso 1: ```bash for i in 1 2 3; do curl -o /dev/null -s -w 'TTFB: %{time_starttransfer}s Total: %{time_total}s\n' https://example.com/; done ab -n 200 -c 10 https://example.com/ ``` ```output TTFB: 0.021s Total: 0.024s TTFB: 0.018s Total: 0.020s TTFB: 0.019s Total: 0.021s ``` Las páginas servidas desde la caché de Nginx bajan de cientos de milisegundos a unas decenas, y las peticiones por segundo de `ab` se multiplican. Para las páginas que no se cachean (panel, carrito, usuarios identificados) la mejora viene de OPcache, Redis y el ajuste de MySQL: compruébalo midiendo, por ejemplo, `https://example.com/wp-login.php`. ## Solución de problemas - **Los usuarios identificados ven contenido de otros o el carrito vacío**: alguna ruta personal se está cacheando. Revisa `X-Cache-Status` de esa URL, que debe ser `BYPASS`, y añade su ruta o su cookie a los `map` del paso 6. - **`502 Bad Gateway` tras cambiar PHP-FPM**: revisa `sudo journalctl -u php8.3-fpm` y `/var/log/php8.3-fpm.log`. Suele ser un error de sintaxis en `www.conf`; `sudo php-fpm8.3 -t` lo indica. - **MySQL no arranca tras el cambio**: `innodb_buffer_pool_size` es mayor que la memoria disponible. Bájalo y revisa `sudo journalctl -u mysql`. - **`wp redis status` muestra `Not connected`**: comprueba que `redis-server` está activo y que la extensión está cargada con `php-fpm8.3 -m | grep redis`. ## Conclusión Has medido tu WordPress, has activado OPcache, has dimensionado PHP-FPM y MySQL según tu memoria real, has añadido Redis como caché de objetos y una caché de página FastCGI con purga automática, y has comprobado la mejora con los mismos datos del principio. Como siguientes pasos, revisa con el *slowlog* de PHP-FPM y el de MySQL qué plugins siguen siendo lentos, sirve las imágenes en WebP o AVIF con un tamaño adecuado, y valora una CDN delante de Nginx si tu público está repartido por varios continentes.