Servidor Web

Última revisión: 20 de diciembre de 2021

El Servidor Web

El servidor web es el que se encarga de que un sitio web se sirva, ni más ni menos. Suele ir en conjunción con distintos elementos como PHP para poder procesar los sitios en tiempo real en el lado del servidor, ya que el Servidor Web solo sirve los contenidos; toma un HTML físico o generado y lo envía, recupera una imagen y la envía…

Servidores web hay muchísimos y cada uno tiene ciertas ventajas e inconvenientes. Los más conocidos y habituales son Apache HTTP, nginx y LiteSpeed, en Linux y Microsoft IIS en Windows.

Lo principal de los servidores web es que tengan muy buena integración con sistemas de procesamiento de código como PHP.

Además, hay que analizar que den soporte al menos a HTTP/2.0 y, por defecto, tenerlo activado, lo que significa que necesitaremos un certificado TLS para nuestro dominio.

NOTA: Recuerda comprobar los requisitos de WordPress para los Servidores Web.

La versión de HTTP

En la actualidad existen 3 versiones del protocolo HTTP, que es el sistema por el que se comunica el navegador con el servidor web.

Estas versiones son la 1, la 2 y la 3. La versión 1 ha estado gestionando Internet durante 15 años, y para sacarle partido a los cifrados y certificados TLS, deberíamos usar al menos la versión 2.0.

La versión 1.0 y 1.1 establecieron los elementos básicos que conocemos, como por ejemplo los códigos 2xx, 3xx, 4xx o 5xx, que son los que resuelven si la carga de una web ha sido correcta, hay una redirección, hay un error en la página o un error en el servidor.

La versión 2.0 requiere del uso de un sistema de cifrado si se quiere aprovechar al máximo. Sigue funcionando por el sistema TCP y añade un sistema principal de streams. Esto permite mejorar la paralelización de peticiones, lo que permite que se hagan muchas más peticiones a la vez sin que se sature la conexión.

La versión 3.0 mejora el sistema funcionando por UDP mediante QUIC. Al reducir la cantidad de validaciones en la conexión, se gana algo de velocidad.

La mayor diferencia entre el HTTP2 y el HTTP3 es el protocolo sobre el que funciona. El protocolo TCP tiene sistemas que permiten saber que algo que se manda a un usuario desde un servidor llega a su destino, mientras que UDP no, aunque el protocolo UDP, al no tener estos sistemas de verificación funciona mucho más rápido. Con el sistema QUIC se añaden ciertas medidas de fiabilidad en la interconexión de datos.

Hoy en día en el que las telecomunicaciones son bastante fiables, pierde bastante el sentido de estar validando todo el rato que la información llega según se recibe.

Si quieres saber si un sitio web funciona con HTTP3 se puede usar el sitio http3check.net.

Usar HTTP/2.0 o HTTP/3.0

Una de las primeras decisiones a la hora de optimizar el servidor web es elegir correctamente qué protocolo de HTTP vamos a utilizar, ya que posteriormente a eso se derivarán muchas de las optimizaciones en otros elementos. Esto podría desbloquear, por ejemplo, situaciones con la paralelización o carga de contenidos de CSS o JavaScript.

Actualmente nos encontramos que:

  • Apache HTTP da soporte a HTTP/1.0, HTTP/1.1 (por defecto) y HTTP/2.0.
  • nginx da soporte a HTTP/1.0, HTTP/1.1 y HTTP/2.0 (por defecto).
  • LiteSpeed da soporte a HTTP/1.0, HTTP/1.1, HTTP/2.0 y HTTP/3.0 (por defecto).

En cualquier caso, hay que recordar que se han de abrir los puertos de los cortafuegos 80 (HTTP) para TCP y 443 (HTTPS) para TCP y UDP si queremos que funcionen todas las versiones.

Con esto conseguiremos un primer paso importante en cuanto a la mejora de velocidad de carga del sitio.

Se deberán utilizar sistemas de cifrado TLS 1.2 o TLS 1.3 para el correcto funcionamiento del HTTP/2.0 o HTTP/3.0. Para mayor seguridad, además, se deberían deshabilitar los sistemas de SSL 2.0, SSL 3.0, TLS 1.0 y TLS 1.1.

Activar HTTP/2.0 en Apache HTTPD

Si queremos que nuestro sitio tenga soporte a HTTP/2.0 en Apache HTTPD, deberemos configurar el soporte en el VirtualHost. Para ello deberemos tener al menos la versión 2.4.17.

<VirtualHost *:443>
  Protocols h2 http/1.1
  ServerAdmin example@example.com
  ServerName example.com
  ...
</VirtualHost>

Añadir la línea de protocolos e incluir el h2 es el sistema para dar soporte a esta versión.

Activar HTTP/2.0 en nginx

Si queremos que nuestro sitio tenga soporte a HTTP/2.0 en nginx, deberemos configurar el soporte en el VirtualHost. Para ello deberemos tener al menos la versión 1.9.5.

server {
  listen 443 ssl http2;
  server_name example.com;
  ...
}

En la línea en la que se escucha el puerto añadiremos el http2 es el sistema para dar soporte a esta versión.

Activar HTTP/2.0 en LiteSpeed

En LiteSpeed el protocolo HTTP/2.0 viene activo por defecto, por lo que no es necesario hacer ningún cambio.

Activar HTTP/3.0 en LiteSpeed

Para que el protocolo HTTP/3.0 funcione en LiteSpeed solo es necesario abrir el puerto 443 por UDP (normalmente solo está activo por TCP). Por defecto LiteSpeed lleva activo QUIC, lo que hace que, de forma automática, funcione esta versión del protocolo. Si no está abierto el puerto, pasará a funcionar solo con HTTP/2.0.

Dispones de más información en How to Enable QUIC on LiteSpeed Web Server.

Sistemas de compresión

Una forma de hacer que los sitios web vayan más rápidos es hacer que la velocidad de descarga sea menor. Para eso muchos servidores y navegadores permiten el uso de la compresión gzip / DEFLATE que comprime los contenidos antes de enviarlos y los descomprime al ser recibidos. Existen sistemas más avanzados como Brotli (RFC 7932) pensados para HTTP/2.0 en adelante.

Aunque esto pueda parecer una contradicción, ya que supone cierto tiempo el comprimir y descomprimir los elementos, si le sumamos los sistemas de caché el tiempo de realizar esta acción es menor que el tiempo que tarda en enviarse la información sin comprimir.

Además, hay que tener en cuenta que la compresión principalmente se ha de aplicar a los contenidos textuales (que son los que tienen más posibilidades de comprimirse) por lo que la carga de las páginas es mucho más rápida. Entre estos ficheros podemos encontrar TXT, JS, CSS, HTML o PHP.

Activar compresión en Apache HTTPD

Si queremos configurar la compresión en el sitio, podemos activarlo a diferentes niveles, desde una activación desde el VirtualHost, hasta una activación por sitio desde el .htaccess.

<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/rss+xml
AddOutputFilterByType DEFLATE application/vnd.ms-fontobject
AddOutputFilterByType DEFLATE application/x-font
AddOutputFilterByType DEFLATE application/x-font-opentype
AddOutputFilterByType DEFLATE application/x-font-otf
AddOutputFilterByType DEFLATE application/x-font-truetype
AddOutputFilterByType DEFLATE application/x-font-ttf
AddOutputFilterByType DEFLATE application/x-javascript
AddOutputFilterByType DEFLATE application/xhtml+xml
AddOutputFilterByType DEFLATE application/xml
AddOutputFilterByType DEFLATE font/opentype
AddOutputFilterByType DEFLATE font/otf
AddOutputFilterByType DEFLATE font/ttf
AddOutputFilterByType DEFLATE image/svg+xml
AddOutputFilterByType DEFLATE image/x-icon
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/javascript
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/xml
</IfModule>

Activar compresión en nginx

Si queremos configurar la compresión en el sitio, podemos activarlo a diferentes niveles, desde una activación general, hasta una activación por sitio.

gzip on;
gzip_disable "MSIE [1-6]\.";
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_min_length 1024;
gzip_types application/atom+xml application/geo+json application/javascript application/x-javascript application/json application/ld+json application/manifest+json application/rdf+xml application/rss+xml application/xhtml+xml application/xml font/eot font/otf font/ttf image/svg+xml text/css text/javascript text/plain text/xml;

Automatizar la barra «/» al final de la URL

Gracias a sistemas como el Apache Mod_rewrite tenemos la posibilidad de crear URLs amigables para usuarios y máquinas, pero en muchas ocasiones no se controla correctamente si las URL finalizan con una «/» barra final o no. Hay que tener en cuenta que llevarla o no es totalmente distinta, ya que la URI deja de ser única y genera contenidos duplicados.

Es por esto que existen distintos métodos para que, en muchos casos, se añada o elimine de forma automática dejando al sistema solucionar esta situación. La principal es el uso del Apache Mod_dir con su directiva DirectorySlash, gracias a la cual podremos configurar si queremos la corrección automática o no.

Mantener al día

El servidor web es una pieza del servidor que, con cierta frecuencia, debería mantenerse actualizado.

Tanto Apache HTTPD como nginx utilizan un sistema de versiones mayores y versiones menores que son compatibles. En este sentido, como mínimo, deberíamos mantener actualizada la versión menor siempre al día, ya que suelen incorporar actualizaciones de seguridad, además de algunos cambios en cuanto a funcionalidad.

Recuerda, antes de hacer una actualización, hacer una copia de seguridad de los ficheros de configuración del servidor web.

Tipos de hosting

Hosting Compartido / Hosting Cloud

Cuando hablamos de un alojamiento web compartido o cloud, hablamos de aquellos en los que el usuario no tiene la posibilidad de hacer cambios en su configuración o versión, ya que está gestionado por el proveedor.

En estos casos, la configuración habitual para un WordPress suele ser la de utilizar un servidor Apache HTTPD como servidor web principal, el que ejecuta las consultas PHP, y tener una capa superior con nginx para la optimización de los contenidos estáticos.

Si este es el caso, deberemos optimizar los dos servidores, en la medida de lo posible, para obtener el mayor rendimiento.

En la parte del servidor Apache HTTPD, deberemos hacer los cambios de configuraciones mediante el fichero .htaccess. En él deberemos configurar de código propio de WordPress (al final del fichero) y previamente todo lo que haga referencia a la optimización de contenidos estáticos, compresión gzip y la configuración de caché (según el plugin que estemos utilizando).

Posteriormente, deberemos también configurar el nginx como sistema web-proxy que nos ayudará, sobre todo, a la configuración de los contenidos estáticos. El objetivo es que el nginx sirva aquellos contenidos estáticos directamente, y los que son PHP o no-cacheables los traspase a Apache HTTPD para que se encargue de ello.

Sistemas como cPanel y Plesk también incluyen un sistema similar a este a la hora de servir los contenidos.

Aquellos servidores que tienen la posibilidad de instalar LiteSpeed, suelen servir directamente los contenidos sin pasar por una capa superior. En estos casos suele estar disponible el panel de configuración de LiteSpeed en el que también podemos configurar los VirtualHost. En la medida de lo posible, siempre será mucho mejor configurar la mayor cantidad de elementos a este nivel.

Un detalle a tener en cuenta es que, el fichero .htaccess, por defecto se ejecuta cada vez que hay una petición, por lo que siempre será mejor utilizar una configuración cacheable e intentar no utilizar la carga del .htacess ya que, en sitios con muchas peticiones, no es tan escalable.

Hosting Dedicado / VPS

En el caso de disponer de un servidor con acceso completo, deberíamos intentar instalar un servidor que nos ayude con una mayor actividad. Para esto siempre será mejor el uso de nginx o de LiteSpeed con una configuración óptima (la que viene por defecto no suele serlo).

En los casos en los que se utilice nginx, hay que cargar una configuración específica que nos pueda ayudar con la carga en memoria de toda la configuración y que se aplique a todas las peticiones, sin que el propio WordPress o el usuario final tenga acceso a hacer cambios, ya que el fichero .htaccess no funciona.

Configuración de nginx para WordPress

Esta es una configuración de ejemplo para WordPress en un nginx desde Ubuntu 20.04 LTE.

Por un lado, configuraremos el servidor nginx.

vim /etc/nginx/nginx.conf

Con el siguiente contenido:

user www-data;
pid /run/nginx.pid;
worker_processes auto;
worker_rlimit_nofile 65535;
include /etc/nginx/modules-enabled/*.conf;
events {
  multi_accept on;
  worker_connections 65535;
  use epoll;
}
http {
  charset utf-8;
  sendfile on;
  tcp_nopush on;
  tcp_nodelay on;
  server_tokens off;
  more_clear_headers Server;
  log_not_found off;
  types_hash_max_size 2048;
  client_max_body_size 64m;
  keepalive_timeout 10;
  server_names_hash_bucket_size 128;
  server_names_hash_max_size 1024;
  include /etc/nginx/mime.types;
  default_type application/octet-stream;
  #timeout
  proxy_read_timeout 300;
  proxy_connect_timeout 300;
  proxy_send_timeout 300;
  send_timeout 300;
  client_header_timeout 300;
  client_body_timeout 300;
  fastcgi_read_timeout 300;
  # logging
  access_log /var/log/nginx/access.log;
  error_log /var/log/nginx/error.log;
  # gzip
  gzip on;
  gzip_comp_level 6;
  gzip_proxied any;
  gzip_min_length 256;
  gzip_buffers 16 8k;
  gzip_types application/atom+xml application/geo+json application/javascript application/x-javascript application/json application/ld+json application/manifest+json application/rdf+xml application/rss+xml application/vnd.ms-fontobject application/wasm application/x-web-app-manifest+json application/xhtml+xml application/xml font/eot font/otf font/ttf image/bmp image/svg+xml text/cache-manifest text/calendar text/css text/javascript text/markdown text/plain text/xml text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy;
  gzip_disable "MSIE [1-6].(?!.*SV1)";
  gzip_vary on;
  # more
  include /etc/nginx/conf.d/*.conf;
  include /etc/nginx/sites-enabled/*;
}

Para la configuración de un VirtualHost podemos usar una configuración tal que esta:

#HTTP
server {
  listen 80;
  listen [::]:80;
  server_name example.com www.example.com;
  return 301 https://example.com$request_uri;
  access_log off;
}
#CANONICAL
server {
  listen 443 ssl http2;
  listen [::]:443 ssl http2;
  # SSL
  ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
  ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
  ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
  include /etc/letsencrypt/options-ssl-nginx.conf;
  # SSL OCSP Stapling
  ssl_stapling on;
  ssl_stapling_verify on;
  resolver 208.67.222.222 8.8.8.8 valid=300s;
  resolver_timeout 2s;
  # Security headers
  add_header Referrer-Policy "strict-origin-when-cross-origin" always;
  add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
  add_header X-Frame-Options SAMEORIGIN;
  add_header X-Content-Type-Options nosniff;
  add_header X-XSS-Protection "1; mode=block";
  #logs
  access_log off;
  #CONFIG
  server_name www.example.com;
  return 301 https://example.com$request_uri;
  access_log off;
}
#REAL
server {
  listen 443 ssl http2;
  listen [::]:443 ssl http2;
  # SSL
  ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
  ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
  ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
  include /etc/letsencrypt/options-ssl-nginx.conf;
  # SSL OCSP Stapling
  ssl_stapling on;
  ssl_stapling_verify on;
  resolver 208.67.222.222 8.8.8.8 valid=300s;
  resolver_timeout 2s;
  # Security headers
  add_header Referrer-Policy "strict-origin-when-cross-origin" always;
  add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
  add_header X-Frame-Options SAMEORIGIN;
  add_header X-Content-Type-Options nosniff;
  add_header X-XSS-Protection "1; mode=block";
  #logs
  access_log /var/log/nginx/example.com-access.log combined buffer=64k flush=5m;
  error_log /var/log/nginx/example.com-error.log;
  #CONFIG
  server_name example.com;
  root /webs/example.com;
  index index.php index.html index.htm;
  set $cache_uri $request_uri;
  if ($request_method = POST) {
    set $cache_uri 'null cache';
  }
  if ($query_string != "") {
    set $cache_uri 'null cache';
  }
  if ($request_uri ~* "(/wp-admin/|/xmlrpc.php|/wp-*.php)") {
    set $cache_uri 'null cache';
  }
  if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_logged_in") {
    set $cache_uri 'null cache';
  }
  set $cachefile "/wp-content/cache/supercache/$http_host/$cache_uri/index.html";
  if ($https ~* "on") {
    set $cachefile "/wp-content/cache/supercache/$http_host/$cache_uri/index-https.html";
  }
  location / {
    try_files $cachefile $uri $uri/ /index.php?$args;
  }
  # HIDDEN FILES
  location ~ /.well-known {
    allow all;
  }
  location ~ /.ht {
    deny all;
  }
  location ~ /favicon.(ico|png) {
    log_not_found off;
    access_log off;
  }
  location = /robots.txt {
    allow all;
    log_not_found off;
    access_log off;
  }
  location ~* \.(bmp|bz2|cur|doc|docx|exe|gif|gz|htc|ico|jpeg|jpg|mid|midi|mp3|mp4|ogg|ogv|png|ppt|pptx|rar|rtf|svg|svgz|tar|tgz|wav|webm|webp|xls|xlsx|zip)$ {
    expires max;
    add_header Cache-Control "public";
    log_not_found off;
    access_log off;
  }
  location ~* \.(atom|css|js|rss|xml)$ {
    expires 1d;
    add_header Cache-Control "public";
    log_not_found off;
    access_log off;
  }
  location ~* \.(?:ttf|eot|woff|woff2|otf)$ {
    expires max;
    add_header Cache-Control "public";
    add_header Access-Control-Allow-Origin "*";
    log_not_found off;
    access_log off;
  }
  location ~* readme\.(html|txt) {
    deny all;
  }
  location ~* (licencia|license|LICENSE|olvasdel|lisenssi|liesmich)\.(html|txt) {
    deny all;
  }
  location ~* ^/wp-config {
    deny all;
  }
  location ~* ^/wp-cron\.php {
    deny all;
  }
  location ~* ^/wp-admin/(install|setup-config|upgrade)\.php {
    deny all;
  }
  location ~* ^/wp-admin/maint/repair\.php {
    deny all;
  }
  location ~* ^/wp-links-opml\.php {
    deny all;
  }
  location ~* ^/wp-content/mu-plugins/$ {
    return 404;
  }
  location ~* ^/wp-content/(plugins|themes)/(.+)/$ {
    return 404;
  }
  location ~* ^/wp-content/(?:uploads|files)/.+\.(html|js|php|shtml|swf)$ {
    deny all;
  }
  location ~* ^/wp-content/plugins/.+\.(aac|avi|bz2|cur|docx?|eot|exe|flv|gz|heic|htc|m4a|midi?|mov|mp3|mp4|mpe?g|ogg|ogv|otf|pdf|pptx?|rar|rtf|tar|tgz|tiff?|ttc|wav|wmv|xlsx?|zip) {
    deny all;
  }
  location ~* sftp-config.json {
    deny all;
  }
  location ~* (access|error)_log {
    deny all;
  }
  location ~* installer-log\.txt {
    deny all;
  }
  location ~* ^/wp-content/debug.log {
    deny all;
  }
  location ~* (^#.*#|\.(bak|config|dist|fla|inc|ini|log|psd|sh|sql|sw[op])|~)$ {
    deny all;
  }
  location ~* load-(scripts|styles)\.php {
    deny all;
  }
  # BLOCK XML-RPC
  location ~* /xmlrpc\.php {
    deny all;
  }
  # ROOT PHP
  location ~ .php$ {
    fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
    fastcgi_index index.php;
    fastcgi_buffers 256 16k;
    fastcgi_buffer_size 128k;
    fastcgi_busy_buffers_size 256k;
    fastcgi_temp_file_write_size 256k;
    fastcgi_hide_header X-Powered-By;
    fastcgi_hide_header X-Pingback;
    fastcgi_hide_header Link;
    fastcgi_intercept_errors off;
    fastcgi_split_path_info ^(.+.php)(/.+)$;
    try_files $fastcgi_script_name =404;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_param PHP_ADMIN_VALUE open_basedir=$document_root/:/usr/lib/php/:/tmp/;
    fastcgi_param PATH_INFO $path_info;
    set $path_info $fastcgi_path_info;
    include fastcgi.conf;
  }
}