Optimización de Web Vitals desde el Lado del Servidor

Los Core Web Vitals son las métricas que Google usa para medir la experiencia de usuario y que influyen directamente en el posicionamiento en buscadores. Aunque muchas optimizaciones se realizan en el cliente, la configuración del servidor Linux tiene un impacto crítico en el TTFB (Time to First Byte), el LCP (Largest Contentful Paint) y el CLS (Cumulative Layout Shift), métricas que determinan si tu sitio pasa o falla la evaluación de Google.

Requisitos Previos

  • Servidor Linux con Nginx o Apache
  • Acceso a la configuración del servidor web
  • PHP-FPM, Node.js u otro runtime de aplicación (según el stack)
  • Redis o Memcached para caché (recomendado)
  • Acceso root o sudo

Reducción del TTFB

El TTFB (Time to First Byte) debe ser inferior a 800ms. Para reducirlo:

# Medir el TTFB actual del servidor
curl -w "TTFB: %{time_starttransfer}s\nTotal: %{time_total}s\n" \
  -o /dev/null -s https://tudominio.com/

# Desglose completo del tiempo
curl -w "DNS: %{time_namelookup}s\nConexión TCP: %{time_connect}s\nTLS: %{time_appconnect}s\nPrimer byte: %{time_starttransfer}s\nTotal: %{time_total}s\n" \
  -o /dev/null -s https://tudominio.com/

Optimización de PHP-FPM (principal causa de TTFB alto):

# Calcular el número óptimo de procesos PHP-FPM
# RAM disponible para PHP / Memoria media por proceso
# Ejemplo: 2GB disponibles / 30MB por proceso = ~66 procesos máximo

# Editar la configuración del pool PHP-FPM
sudo nano /etc/php/8.2/fpm/pool.d/www.conf

# Configuración optimizada para servidor con 4GB RAM
pm = dynamic
pm.max_children = 50        # Máximo de procesos simultáneos
pm.start_servers = 10       # Procesos al arrancar
pm.min_spare_servers = 5    # Mínimo de procesos en espera
pm.max_spare_servers = 20   # Máximo de procesos en espera
pm.max_requests = 500       # Peticiones por proceso antes de reiniciar

# Habilitar OPcache de PHP (reduce drásticamente el TTFB)
sudo nano /etc/php/8.2/fpm/conf.d/10-opcache.ini
; Configuración OPcache para máximo rendimiento
opcache.enable=1
opcache.memory_consumption=256
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0    ; Desactivar en producción (más rápido)
opcache.revalidate_freq=0
opcache.save_comments=1
opcache.enable_cli=0
sudo systemctl restart php8.2-fpm

Estrategias de Caché del Servidor

# Caché de FastCGI en Nginx (para aplicaciones PHP)
sudo mkdir -p /var/cache/nginx/fastcgi

# Añadir la zona de caché en nginx.conf (bloque http)
sudo tee /etc/nginx/conf.d/cache-zones.conf << 'EOF'
# Zona de caché FastCGI: 10MB para metadatos, 1GB de almacenamiento
fastcgi_cache_path /var/cache/nginx/fastcgi
    levels=1:2
    keys_zone=FASTCGI:10m
    max_size=1g
    inactive=60m
    use_temp_path=off;
EOF
# /etc/nginx/sites-available/tudominio.conf - Configuración de caché FastCGI
server {
    listen 443 ssl http2;
    server_name tudominio.com;

    # Clave de caché basada en el método HTTP, host y URI
    set $skip_cache 0;

    # No cachear peticiones POST
    if ($request_method = POST) {
        set $skip_cache 1;
    }

    # No cachear URLs con parámetros de query
    if ($query_string != "") {
        set $skip_cache 1;
    }

    # No cachear páginas específicas (admin, carrito, etc.)
    if ($request_uri ~* "/wp-admin|/wp-login|/cart|/checkout|/account") {
        set $skip_cache 1;
    }

    # No cachear usuarios logueados (cookie de sesión presente)
    if ($http_cookie ~* "wordpress_logged_in|PHPSESSID|user_session") {
        set $skip_cache 1;
    }

    location ~ \.php$ {
        fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

        # Configuración del caché FastCGI
        fastcgi_cache FASTCGI;
        fastcgi_cache_key "$scheme$request_method$host$request_uri";
        fastcgi_cache_valid 200 301 302 60m;  # Cachear respuestas 60 minutos
        fastcgi_cache_bypass $skip_cache;
        fastcgi_no_cache $skip_cache;

        # Añadir cabecera para ver si la respuesta viene del caché
        add_header X-Cache-Status $upstream_cache_status;
    }
}

Caché con Redis para aplicaciones Node.js:

# Instalar Redis
sudo apt install -y redis-server

# Configuración de Redis para caché de aplicación
sudo tee /etc/redis/redis.conf.d/cache.conf << 'EOF'
# Política de eliminación cuando se llena la memoria
maxmemory 512mb
maxmemory-policy allkeys-lru

# Deshabilitar persistencia para caché pura (mejor rendimiento)
save ""
appendonly no
EOF

sudo systemctl restart redis

Compresión de Respuestas

# /etc/nginx/conf.d/compression.conf - Compresión optimizada

# Gzip para compatibilidad universal
gzip on;
gzip_comp_level 5;        # Nivel 5 es el mejor balance velocidad/compresión
gzip_min_length 256;      # No comprimir respuestas menores de 256 bytes
gzip_proxied any;
gzip_vary on;
gzip_types
    application/atom+xml
    application/javascript
    application/json
    application/ld+json
    application/manifest+json
    application/rss+xml
    application/vnd.geo+json
    application/vnd.ms-fontobject
    application/x-font-ttf
    application/x-web-app-manifest+json
    application/xhtml+xml
    application/xml
    font/opentype
    image/bmp
    image/svg+xml
    image/x-icon
    text/cache-manifest
    text/css
    text/plain
    text/vcard
    text/vnd.rim.location.xloc
    text/vtt
    text/x-component
    text/x-cross-domain-policy;

# Brotli (mejor compresión que gzip, soportado en Nginx con módulo adicional)
# Instalar: sudo apt install libnginx-mod-brotli
brotli on;
brotli_comp_level 6;
brotli_types text/plain text/css application/json application/javascript text/xml application/xml image/svg+xml;

Priorización de Recursos

# Resource Hints via HTTP/2 Server Push o Link preload
# Insertar cabeceras de preload para recursos críticos

server {
    location / {
        # Precarga de recursos críticos
        add_header Link "</css/critical.css>; rel=preload; as=style";
        add_header Link "</fonts/main.woff2>; rel=preload; as=font; crossorigin";
        add_header Link "</js/app.js>; rel=preload; as=script";

        try_files $uri $uri/ /index.php$is_args$args;
    }

    # Caché agresiva para recursos estáticos inmutables
    location ~* \.(css|js|woff2|woff|ttf|ico|png|jpg|jpeg|gif|svg|webp)$ {
        expires 1y;
        add_header Cache-Control "public, immutable, max-age=31536000";
        access_log off;
    }
}

Optimización de la Respuesta del Servidor

# Configuraciones de Nginx para reducir la latencia

http {
    # Habilitar sendfile para servir ficheros estáticos más rápido
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;

    # Timeouts optimizados
    keepalive_timeout 65;
    keepalive_requests 1000;   # Peticiones por conexión keep-alive
    client_header_timeout 15;
    client_body_timeout 15;
    send_timeout 30;

    # Buffer sizes optimizados
    client_body_buffer_size 128k;
    client_max_body_size 50m;
    client_header_buffer_size 1k;
    large_client_header_buffers 4 16k;
    output_buffers 2 32k;
    postpone_output 1460;

    # Resolución DNS cacheada para proxys
    resolver 1.1.1.1 8.8.8.8 valid=300s;
    resolver_timeout 5s;
}

Optimización del kernel Linux para tráfico web:

sudo tee /etc/sysctl.d/99-web-performance.conf << 'EOF'
# Incrementar el tamaño de la cola de conexiones pendientes
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535

# Reutilizar y reciclar conexiones TIME_WAIT más rápido
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15

# Aumentar el buffer de red para mayor throughput
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 87380 16777216

# BBR congestion control para mejor rendimiento en redes variables
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
EOF

sudo sysctl -p /etc/sysctl.d/99-web-performance.conf

# Verificar que BBR está activo
sysctl net.ipv4.tcp_congestion_control

Monitorización de Web Vitals

# Instalar Lighthouse CLI para medir Web Vitals desde el servidor
npm install -g lighthouse

# Ejecutar análisis completo
lighthouse https://tudominio.com \
  --output json \
  --output-path /tmp/lighthouse-report.json \
  --chrome-flags="--headless --no-sandbox"

# Ver solo las métricas principales
lighthouse https://tudominio.com \
  --output json \
  --quiet \
  --chrome-flags="--headless --no-sandbox" \
  | jq '.audits | {
      lcp: .["largest-contentful-paint"].numericValue,
      fid: .["max-potential-fid"].numericValue,
      cls: .["cumulative-layout-shift"].numericValue,
      ttfb: .["server-response-time"].numericValue,
      fcp: .["first-contentful-paint"].numericValue
    }'

# Script de monitorización continua de TTFB
cat > /usr/local/bin/monitor-ttfb.sh << 'SCRIPT'
#!/bin/bash
URL="${1:-https://tudominio.com}"
THRESHOLD=800  # ms

TTFB=$(curl -w "%{time_starttransfer}" -o /dev/null -s "$URL")
TTFB_MS=$(echo "$TTFB * 1000" | bc | cut -d'.' -f1)

echo "$(date '+%Y-%m-%d %H:%M:%S') TTFB: ${TTFB_MS}ms URL: $URL"

if [ "$TTFB_MS" -gt "$THRESHOLD" ]; then
    echo "ALERTA: TTFB superior al umbral de ${THRESHOLD}ms"
fi
SCRIPT

chmod +x /usr/local/bin/monitor-ttfb.sh

# Ejecutar cada 5 minutos
echo "*/5 * * * * root /usr/local/bin/monitor-ttfb.sh >> /var/log/ttfb.log 2>&1" \
  | sudo tee /etc/cron.d/monitor-ttfb

Solución de Problemas

TTFB alto a pesar de las optimizaciones:

# Identificar el componente lento
time curl -o /dev/null -s https://tudominio.com/

# Verificar el tiempo de respuesta de PHP-FPM
sudo grep "request_slowlog_timeout" /etc/php/8.2/fpm/pool.d/www.conf

# Habilitar slow log de PHP-FPM (peticiones > 1 segundo)
request_slowlog_timeout = 1
slowlog = /var/log/php-fpm/slow.log

# Ver las peticiones lentas
sudo tail -f /var/log/php-fpm/slow.log

Caché FastCGI no funciona:

# Verificar que el directorio de caché existe y tiene permisos correctos
ls -la /var/cache/nginx/fastcgi/
sudo chown -R www-data:www-data /var/cache/nginx/fastcgi/

# Comprobar la cabecera de estado del caché
curl -I https://tudominio.com/ | grep X-Cache-Status
# HIT = viene del caché, MISS = generado en vivo, BYPASS = saltado

Conclusión

La optimización de los Core Web Vitals desde el servidor es una estrategia de alto impacto que complementa las optimizaciones del lado del cliente. Un TTFB bajo gracias a la caché FastCGI y OPcache, la compresión adecuada de respuestas y la priorización de recursos críticos mediante preload son los pilares de un servidor que pasa con nota la evaluación de Google. Monitoriza regularmente las métricas con Lighthouse o el Chrome User Experience Report para identificar regresiones antes de que afecten al posicionamiento.