--- title: "Cómo optimizar WooCommerce en un VPS con Ubuntu 24.04" description: "Acelera WooCommerce en Ubuntu 24.04 ajustando PHP-FPM, OPcache y MariaDB, con caché de objetos en Redis y caché FastCGI de Nginx que respeta carrito y checkout." category: "E-commerce y CMS" language: es source: https://cubepath.com/es/docs/e-commerce-y-cms/optimizacion-de-woocommerce-en-vps updated: 2026-09-25 --- # Cómo optimizar WooCommerce en un VPS con Ubuntu 24.04 Una tienda WooCommerce recién instalada funciona, pero cada visita ejecuta PHP y lanza decenas de consultas a la base de datos, y eso se nota cuando crecen el catálogo o el tráfico. En este tutorial optimizarás una tienda WooCommerce que ya funciona sobre Nginx, PHP 8.3-FPM y MariaDB en Ubuntu 24.04: ajustarás OPcache y PHP-FPM, darás memoria a MariaDB, añadirás caché de objetos con Redis y una caché de página completa con FastCGI de Nginx que no rompe el carrito ni el checkout. Medirás el tiempo de respuesta antes y después para comprobar la mejora. ## Requisitos previos Para seguir esta guía necesitas: - Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath, con al menos 2 GB de RAM (4 GB o más para catálogos grandes). - Un usuario no root con privilegios `sudo`. - WordPress con WooCommerce funcionando sobre Nginx, PHP 8.3-FPM y MariaDB, con HTTPS activo. En la guía, el sitio está en `/var/www/your_domain` y se sirve en `your_domain`: sustituye ambos por tus valores. - [WP-CLI](https://wp-cli.org/) instalado en `/usr/local/bin/wp`. - Una copia de seguridad reciente de los archivos y la base de datos. Todos los comandos `wp` se ejecutan como `www-data`, el usuario de PHP-FPM, para no dejar archivos con otro propietario. ## Paso 1: Medir el punto de partida Antes de cambiar nada, mide el tiempo hasta el primer byte (TTFB) de la portada y de una página de producto. Ejecuta cada medición varias veces y quédate con un valor típico: ```bash curl -o /dev/null -s -w 'TTFB: %{time_starttransfer}s Total: %{time_total}s\n' https://your_domain/ curl -o /dev/null -s -w 'TTFB: %{time_starttransfer}s Total: %{time_total}s\n' https://your_domain/producto/ejemplo/ ``` ```output TTFB: 0.842s Total: 0.861s TTFB: 0.915s Total: 0.934s ``` Apunta estos valores. Sin caché de página, un TTFB por encima de 500 ms en un VPS con recursos libres suele indicar que PHP o la base de datos son el cuello de botella. ## Paso 2: Ajustar OPcache y los límites de PHP OPcache guarda en memoria el código PHP ya compilado. WordPress con WooCommerce y varios plugins carga miles de archivos, y los valores por defecto se quedan cortos. Crea un archivo de configuración propio para PHP-FPM, así no tocas el `php.ini` del paquete: ```bash sudo nano /etc/php/8.3/fpm/conf.d/99-woocommerce.ini ``` ```ini memory_limit = 256M max_execution_time = 120 realpath_cache_size = 4096K realpath_cache_ttl = 600 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 ``` Con `opcache.revalidate_freq = 60`, PHP comprueba cada minuto si los archivos han cambiado, así que las actualizaciones de plugins se aplican solas sin reiniciar el servicio. Reinicia PHP-FPM y comprueba que los valores se han cargado: ```bash sudo systemctl restart php8.3-fpm sudo php-fpm8.3 -i | grep -E '^(memory_limit|opcache.memory_consumption|opcache.max_accelerated_files)' ``` ```output memory_limit => 256M => 256M opcache.max_accelerated_files => 20000 => 20000 opcache.memory_consumption => 256 => 256 ``` ## Paso 3: Dimensionar el pool de PHP-FPM El parámetro clave es `pm.max_children`: el número máximo de procesos PHP simultáneos. Si es demasiado bajo, las peticiones hacen cola; si es demasiado alto, el servidor se queda sin memoria y empieza a usar swap. Calcula cuánto ocupa cada proceso con la tienda en uso (navega un poco por ella antes): ```bash ps --no-headers -o rss -C php-fpm8.3 | awk '{sum+=$1} END {printf "Procesos: %d Media: %.0f MB\n", NR, sum/NR/1024}' ``` ```output Procesos: 6 Media: 78 MB ``` Aplica esta fórmula: memoria disponible para PHP dividida por la media por proceso. En un VPS de 4 GB en el que MariaDB, Redis y el sistema necesitan unos 1,5 GB, quedan unos 2,5 GB para PHP: 2.500 / 80 ≈ 30 procesos. Edita el pool por defecto: ```bash sudo nano /etc/php/8.3/fpm/pool.d/www.conf ``` Localiza estas directivas y ajústalas con tu cálculo. `pm.max_requests` recicla cada proceso tras 500 peticiones para contener fugas de memoria de plugins, y el slowlog registra las peticiones que tardan más de 5 segundos junto con la traza de PHP, lo que te dirá qué plugin es lento: ```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 ``` Comprueba la sintaxis y reinicia el servicio: ```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 en el registro de PHP-FPM aparece el aviso `server reached pm.max_children setting`, aumenta el valor siempre que la memoria lo permita: ```bash sudo grep max_children /var/log/php8.3-fpm.log ``` ## Paso 4: Optimizar MariaDB El parámetro con más impacto en MariaDB es `innodb_buffer_pool_size`, la memoria donde InnoDB mantiene tablas e índices. Lo ideal es que toda la base de datos quepa en él. Consulta primero su tamaño (sustituye `wordpress` por el nombre de tu base de datos): ```bash sudo mariadb -e "SELECT table_schema AS db, ROUND(SUM(data_length + index_length)/1024/1024) AS mb FROM information_schema.tables WHERE table_schema = 'wordpress' GROUP BY table_schema;" ``` ```output +-----------+------+ | db | mb | +-----------+------+ | wordpress | 640 | +-----------+------+ ``` Crea un archivo de configuración propio. En un VPS de 4 GB que comparte servidor con PHP, 1 GB de buffer pool es un buen punto de partida; no pases del 40 % de la RAM total en ese caso: ```bash sudo nano /etc/mysql/mariadb.conf.d/99-woocommerce.cnf ``` ```ini [mysqld] innodb_buffer_pool_size = 1G innodb_log_file_size = 256M innodb_flush_log_at_trx_commit = 2 tmp_table_size = 64M max_heap_table_size = 64M slow_query_log = 1 slow_query_log_file = /var/log/mysql/mariadb-slow.log long_query_time = 1 ``` `innodb_flush_log_at_trx_commit = 2` escribe el registro de transacciones a disco una vez por segundo en lugar de en cada commit. Mejora mucho el rendimiento de escritura a cambio de poder perder como máximo un segundo de transacciones si se cae el servidor; si no aceptas ese riesgo, elimina la línea. El registro de consultas lentas te mostrará las que superen un segundo. Reinicia MariaDB y comprueba el valor: ```bash sudo systemctl restart mariadb sudo mariadb -e "SELECT @@innodb_buffer_pool_size/1024/1024 AS buffer_pool_mb;" ``` ```output +----------------+ | buffer_pool_mb | +----------------+ | 1024.00000000 | +----------------+ ``` Revisa también cuántos datos carga WordPress en cada petición desde la tabla de opciones (ajusta el prefijo `wp_` si usas otro). Por encima de 1 MB conviene investigar qué plugin guarda opciones tan grandes: ```bash sudo mariadb wordpress -e "SELECT ROUND(SUM(LENGTH(option_value))/1024) AS autoload_kb FROM wp_options WHERE autoload IN ('yes','on','auto-on','auto');" ``` ## Paso 5: Activar el almacenamiento de pedidos de alto rendimiento WooCommerce guardaba históricamente los pedidos en las tablas genéricas de entradas de WordPress. El almacenamiento de pedidos de alto rendimiento (HPOS) usa tablas propias con índices adecuados y acelera tanto el checkout como el listado de pedidos. Está activo por defecto en las tiendas creadas con WooCommerce 8.2 o posterior. Si tu tienda es anterior, ve a **WooCommerce** > **Ajustes** > **Avanzado** > **Funcionalidades**, marca la sincronización de pedidos, espera a que termine y después selecciona **Almacenamiento de pedidos de alto rendimiento**. Antes, confirma en esa misma pantalla que todos tus plugins son compatibles; WooCommerce lista los que no lo son. ## Paso 6: Añadir caché de objetos con Redis WordPress repite muchas consultas idénticas en cada petición (opciones, términos, metadatos de productos). Una caché de objetos persistente las guarda en Redis y beneficia sobre todo a las páginas que no pueden cachearse enteras: carrito, checkout, Mi cuenta y el panel de administración. Instala Redis y la extensión de PHP: ```bash sudo apt install -y redis-server php8.3-redis ``` Limita la memoria de Redis y haz que descarte las claves menos usadas al llenarse, el comportamiento adecuado para una caché: ```bash sudo nano /etc/redis/redis.conf ``` Busca y ajusta estas directivas: ```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 ``` Instala y activa el plugin **Redis Object Cache** con WP-CLI: ```bash cd /var/www/your_domain sudo -u www-data wp plugin install redis-cache --activate ``` Abre `wp-config.php` y añade estas líneas justo antes de `/* That's all, stop editing! */`. El prefijo evita colisiones si alojas más de un WordPress en el mismo Redis: ```bash sudo nano /var/www/your_domain/wp-config.php ``` ```php define( 'WP_REDIS_HOST', '127.0.0.1' ); define( 'WP_REDIS_PORT', 6379 ); define( 'WP_REDIS_PREFIX', 'your_domain:' ); ``` Activa el drop-in de caché y comprueba el estado: ```bash sudo -u www-data wp redis enable sudo -u www-data wp redis status ``` ```output Status: Connected Client: PhpRedis Drop-in: Valid ... ``` Tras navegar por la tienda, `redis-cli info keyspace` debería mostrar claves en `db0`. ## Paso 7: Configurar la caché FastCGI de Nginx La caché FastCGI guarda en disco el HTML generado por PHP y lo sirve directamente desde Nginx a los visitantes anónimos, sin ejecutar WordPress. En una tienda hay que excluir todo lo personal: carrito, checkout, Mi cuenta, peticiones POST y los visitantes que tienen productos en el carrito o sesión iniciada. Define la zona de caché en el contexto `http`, en un archivo de `conf.d`: ```bash sudo nano /etc/nginx/conf.d/fastcgi-cache.conf ``` ```nginx fastcgi_cache_path /var/cache/nginx/woocommerce levels=1:2 keys_zone=WOOCOMMERCE:100m max_size=1g inactive=60m; fastcgi_cache_key "$scheme$request_method$host$request_uri"; fastcgi_cache_use_stale error timeout updating http_500 http_503; ``` Ahora edita el bloque de servidor de tu tienda: ```bash sudo nano /etc/nginx/sites-available/your_domain ``` Dentro del bloque `server` que escucha en el puerto 443, antes de los bloques `location`, añade las reglas de exclusión. Incluye las rutas en inglés y las traducidas, porque dependen del idioma con el que se crearon las páginas de WooCommerce; comprueba las tuyas en **WooCommerce** > **Ajustes** > **Avanzado**: ```nginx set $skip_cache 0; if ($request_method = POST) { set $skip_cache 1; } if ($query_string != "") { set $skip_cache 1; } if ($request_uri ~* "/wp-admin/|/wp-login.php|/xmlrpc.php|/wp-json/|/feed/|sitemap(_index)?.xml|/cart/|/checkout/|/my-account/|/carrito/|/finalizar-compra/|/mi-cuenta/") { set $skip_cache 1; } if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_logged_in|woocommerce_items_in_cart|woocommerce_cart_hash|wp_woocommerce_session_") { set $skip_cache 1; } ``` En el bloque `location ~ \.php$` que ya tienes, añade las directivas de caché después de `fastcgi_pass`: ```nginx location ~ \.php$ { include snippets/fastcgi-php.conf; fastcgi_pass unix:/run/php/php8.3-fpm.sock; fastcgi_cache WOOCOMMERCE; fastcgi_cache_valid 200 301 302 10m; fastcgi_cache_bypass $skip_cache; fastcgi_no_cache $skip_cache; add_header X-FastCGI-Cache $upstream_cache_status; } ``` > Nota: en Nginx, un `add_header` dentro de un `location` anula todos los `add_header` definidos a nivel de `server`. Si en el `server` tienes cabeceras de seguridad (por ejemplo `Strict-Transport-Security`), repítelas dentro de este bloque y del de archivos estáticos del paso 9. Una validez de 10 minutos limita el tiempo que un cambio de precio o de stock tarda en verse en las páginas cacheadas. El carrito y el checkout siempre se calculan en PHP, así que el cliente paga siempre el precio correcto. Crea el directorio de caché, comprueba la configuración y recarga Nginx: ```bash sudo mkdir -p /var/cache/nginx/woocommerce sudo chown www-data:www-data /var/cache/nginx/woocommerce sudo nginx -t sudo systemctl reload nginx ``` Comprueba el comportamiento con dos peticiones seguidas a la portada. La primera genera la caché y la segunda debe servirse desde ella: ```bash curl -sI https://your_domain/ | grep -i x-fastcgi-cache curl -sI https://your_domain/ | grep -i x-fastcgi-cache ``` ```output x-fastcgi-cache: MISS x-fastcgi-cache: HIT ``` El carrito nunca debe cachearse: ```bash curl -sI https://your_domain/carrito/ | grep -i x-fastcgi-cache ``` ```output x-fastcgi-cache: BYPASS ``` Haz también una prueba real en una ventana de incógnito: añade un producto al carrito, navega a otra página y confirma que el contador del carrito se mantiene, y completa un pedido de prueba. Si después de publicar un cambio necesitas vaciar la caché al momento, borra su contenido con `sudo find /var/cache/nginx/woocommerce -type f -delete`. ## Paso 8: Mover WP-Cron al cron del sistema Por defecto, WordPress ejecuta sus tareas programadas (y WooCommerce su Action Scheduler: correos, webhooks, pedidos pendientes) aprovechando las visitas de los clientes, lo que ralentiza algunas peticiones. Con la caché de página además apenas llegan visitas a PHP, así que las tareas se retrasan. Desactiva ese mecanismo en `wp-config.php`, junto a las líneas de Redis: ```php define( 'DISABLE_WP_CRON', true ); ``` Programa la ejecución cada 5 minutos en el crontab de `www-data`: ```bash sudo crontab -u www-data -e ``` ```text */5 * * * * cd /var/www/your_domain && /usr/local/bin/wp cron event run --due-now --quiet ``` Comprueba que el comando funciona ejecutándolo a mano: ```bash cd /var/www/your_domain sudo -u www-data wp cron event run --due-now ``` ```output Executed the cron event 'action_scheduler_run_queue' in 0.412s. Success: Executed a total of 3 cron events. ``` En **WooCommerce** > **Estado** > **Acciones programadas**, la pestaña **Pendientes** no debería acumular acciones vencidas. ## Paso 9: Comprimir y cachear los archivos estáticos Deja que el navegador guarde CSS, JavaScript, imágenes y fuentes. Añade este bloque dentro del `server` de tu tienda: ```nginx location ~* \.(css|js|jpg|jpeg|png|gif|webp|avif|svg|ico|woff2?)$ { expires 30d; add_header Cache-Control "public"; access_log off; try_files $uri =404; } ``` Activa la compresión gzip para texto. En `/etc/nginx/nginx.conf`, dentro de `http`, `gzip on;` ya viene activado; descomenta y ajusta las líneas de gzip que trae el archivo para que queden así: ```nginx gzip on; gzip_vary on; gzip_proxied any; gzip_comp_level 5; gzip_types text/plain text/css application/json application/javascript text/xml application/xml image/svg+xml; ``` Comprueba y recarga Nginx, y verifica la compresión: ```bash sudo nginx -t && sudo systemctl reload nginx curl -sI -H 'Accept-Encoding: gzip' https://your_domain/ | grep -i content-encoding ``` ```output content-encoding: gzip ``` Para las imágenes de producto, que suelen ser el mayor peso de la página, súbelas ya redimensionadas o usa un plugin que genere versiones WebP o AVIF. ## Paso 10: Medir de nuevo Repite las mediciones del paso 1 como visitante anónimo: ```bash curl -o /dev/null -s -w 'TTFB: %{time_starttransfer}s Total: %{time_total}s\n' https://your_domain/ curl -o /dev/null -s -w 'TTFB: %{time_starttransfer}s Total: %{time_total}s\n' https://your_domain/producto/ejemplo/ ``` ```output TTFB: 0.041s Total: 0.048s TTFB: 0.038s Total: 0.045s ``` Las páginas servidas desde la caché FastCGI deberían responder en decenas de milisegundos. Para medir las páginas dinámicas, abre el carrito con sesión iniciada y compara el tiempo con el de antes de activar Redis. ## Solución de problemas **El carrito se vacía o muestra productos de otro cliente.** Alguna página personal se está cacheando. Comprueba la cabecera `X-FastCGI-Cache` de esa URL, añade su ruta a la expresión `$request_uri` y vacía la caché. **`wp redis status` muestra `Not connected`.** Comprueba que Redis está en marcha con `systemctl status redis-server` y que la extensión está cargada con `php-fpm8.3 -m | grep redis`. **Errores 502 tras cambiar el pool.** Revisa `sudo journalctl -u php8.3-fpm` y `/var/log/php8.3-fpm.log`. Si hay procesos terminados por falta de memoria (`dmesg | grep -i oom`), baja `pm.max_children`. **MariaDB no arranca tras el cambio.** Revisa `sudo journalctl -u mariadb`. Normalmente el `innodb_buffer_pool_size` es mayor que la memoria libre: redúcelo. ## Conclusión Tu tienda WooCommerce sirve ahora las páginas públicas desde la caché de Nginx, usa Redis para las páginas dinámicas, tiene PHP-FPM y MariaDB dimensionados para tu servidor y ejecuta sus tareas programadas desde el cron del sistema. Como siguientes pasos, revisa cada semana el slowlog de PHP-FPM y el registro de consultas lentas de MariaDB para detectar plugins problemáticos, pon un CDN delante de los archivos estáticos si vendes a varios países y prueba la tienda con carga antes de campañas como el Black Friday.