7.2.2.2.10. Otros aspectos

7.2.2.2.10.1. Definición mediante mapeo

Si requerimos definir una variable dependiendo del valor de otra, disponemos de la directiva map:

map $host $dominio {
   hostnames;

   default              "desconocido";
   *.example.net        "example";
   *.dominio.org        "dominio";
   ~[^.]+\.otro\.[^.]+  "otro";
}

En este caso, se define la variable $dominio a partir de los valores de la variable $host. Se comprueba si el valor de esta última concuerda con alguna entrada de la izquierda y, si es así, se asigna el valor de la columna derecha correspondiente. Si no hay concordancia, se usa la entrada default. Además, se puede incluir la palabra hostnames para expresar que lo contenido son nombres de máquina y que se entienda la notación con asterisco. Si la lista es larga, puede usarse include:

map $host $dominio {
   hostnames;

   include dominios.txt;
}

y en el fichero /etc/nginx/dominios.txt escribir la lista de dos columnas.

Otro ejemplo (meramente ilustrativo porque para ello ya existe el módulo ngx_http_referer) es el de comprobar si el referer es válido:

map $http_referer $invalid_referer {
   default                 1;

   # Dominios con los que se considera válido el referer.
   "~www.example.net"      0;
   "~example.net"          0;
}

7.2.2.2.10.2. Compresión

Es muy recomendable, para ahorrar ancho de banda configurar el servidor para que comprima aquellos ficheros cuyo ratio de compresión es alto. La configuración predeterminada sólo comprime los documentos HTML, así que creamos un fichero de configuración adicional como /etc/nginx/conf.d/gzip.conf:

# Configuración de la compresión
gzip_comp_level 6;
gzip_static on;
gzip_vary on;
gzip_http_version 1.1;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript image/svg+xml
           application/json application/javascript application/xhtml+xml application/xml+rss;

7.2.2.2.10.3. Depuración

Es obvio que depurar el funcionamiento del servidor pasa por mirar los ficheros de registro. Sin embargo, si realizamos en la configuración reescrituras internas, podremos comprobar que estas no dejan rastro en los logs, ya que su registro está deshabilitado. Para habilitarlo podemos añadir fichero /etc/nginx/conf.d/rewrite.conf:

# cat > /etc/nginx/conf.d/rewrite.conf
rewrite_log on;

pero esto no es suficiente, ya que se registran en el registro de errores con nivel notice, mientras que el nivel predeterminado es error. Consecuentemente, habrá que retocar la directiva error_log que corresponda para cambiar el nivel. Por ejemplo:

error_log  /var/log/nginx/error.log notice;

7.2.2.2.10.4. Servicio tras proxy

Cuando un proxy inverso intermedia interceptando la comunicación entre clientes y servidor, puede actuar de modo transparente (en cuyo caso nuestro nginx será incapaz de reconocerlo) o no. Es en este segundo caso en el que el el proxy web inverso produce distorsiones en la comunicación que debemos tener en cuenta al configurar el servidor web:

  1. El proxy captura la petición del cliente y la reproduce hacia el servidor lo que supone que la comunicación que recibe el servidor no proceda del cliente original, sino del proxy.

  2. Puede darse el caso de que en las comunicaciones cifradas, el extremo de cifrado se traslade del servidor al proxy, a fin de que este entienda la comunicación HTTP y pueda hacer el trabajo que tenga encomendado (cacheo, balanceo, etc.). En ese caso, el servidor recibirá una conexión no segura, pero es conveniente que sepa que originariamente era segura.

Tenga presente que tratar estas cabeceras en el servidor, no es sólo necesario para el correcto funcionakiento del servidor, sino también para el correcto funcionamiento de las aplicaciones que éste ejecute. Por ejemplo, las aplicaciones escritas en PHP consultan la dirección remota a través de $_SERVER["ADDRESS"] y si está activo el protocolo seguro a través de $_SERVER["HTTPS"]. Por tanto, configurar bien el servidor para que ejecute aplicaciones PHP implica que ambos parámetros que pasa el servidor web al intérprete tengan los valores adecuados.

7.2.2.2.10.4.1. Cliente original

Un proxy intermedio que no actúe de forma transparente, hará creer al servidor web que todas las peticiones las recibe de él. En consecuencia, cualquier decisión que queramos tomar en función de quién sea quien nos haga la petición (p.e. si la petición es de un cliente local o uno remoto), será imposible.

Para soslayar este inconveniente los proxies pueblan la cabecera X-Forwarded-For, de manera que el servidor pueda recalcular la dirección que identifica al cliente original y definir la variable $remote_addr, en vez de usar la IP de la conexión (que será la del proxy). Para que nginx sea capaz de aprovechar la información de esta cabecera puede crearse un fichero con contenido semejante a éste que aprovecha el módulo ngx_http_realip_module:

# /etc/nginx/conf.d/realip.conf
set_real_ip_from 127.0.0.1;
set_real_ip_from 10.0.0.0/8;
real_ip_recursive on;
real_ip_header X-Forwarded-For;

La directiva real_ip_header permite indicar cuál es el nombre del campo a analizar, set_real_ip_from las redes y direcciones en que consideramos que hay proxies que alteran la IP original; y real_ip_recursive puede tener dos valores que alteran de distinto modo la IP del cliente (o sea, $remote_addr) cuando la detectada originalmente coincide con alguna de las recogidas en set_real_ip_from:

  • off provoca que se tome incondicionalmente la última incluida en el campo referido por real_ip_header.

  • on, que se tome la última que no esté incluida en las referidas por set_real_ip_from.

Para ilustrarlo supongamos este esquema:

../../../../_images/proxies.png

que genera la siguiente cabecera X-Forwarded-For:

X-Forwarded-For: 80.35.60.114 123.12.21.34 10.35.2.3

y una dirección IP para el cliente que vale 127.0.0.1 originalmente, ya que el proxy que recibe la comunicación en nuestro servidor (haproxy) se comunica con nginx a través de la interfaz de loopback. En este caso, debido a la configuración anterior en conf.d/realip.conf y a que la IP original del cliente está incluida en real_ip_recursive:

7.2.2.2.10.4.2. Protocolo original

Para que el proxy declare el protocolo (HTTPs) con el que recibió la petición se suele usar otra cabecera, X-Forwarded-Proto, con valor https si el protocolo original era HTTPs. Para tenerlo en cuenta en nuestro servidor podemos incluir esta configuración:

# /etc/nginx/conf.d/https.conf
map $http_x_forwarded_proto $_https {
   default  $https;
   https    on;
}

que permite conocer la naturalza original del protocolo usando la variable de usuario $_https en vez de la original $https. Ahora, basta con usar $_https allí donde usábamos antes $https:

server {
   listen   80;

   server_name _;

   root /srv/www;
   try_files $uri $uri/ =404;

   # Necesario si queremos redirigir el tráfico seguro a no seguro
   if ($_https != "on") {
      return 301 https://$host$uri$is_args$args;
   }

}

Además, si el sitio es dinámico y usa PHP, debemos hacérselo al intérprete añadiendo otro bloque a la configuración:

location ~ \.php$ {
   include snippets/fastcgi-php.conf;
   fastcgi_param  HTTPS  $_https if_not_empty;
   fastcgi_pass php;
}

7.2.2.2.10.5. Página de mantenimiento

En ocasiones, podemos requerir ocultar temporalmente uno o varios de los sitios web que nuestro servidor aloja. Podemos optar por distintas estrategias, pero una muy cómoda es configurar nginx de modo que:

  1. No se requiere alterar la configuración del servidor para habilitar o deshabilitar la situación de mantenimiento.

  2. Si en /srv/www/enobras, creamos el archivo enabled, todos los sitios que alojemos pasarán a encontrarse en mantemiento y se mostrará una página que informe de ello.

  3. Si deseamos poner en mantemiento un sitio particular, entonces debemos crear el archivo enabled-nombresitio. Por ejemplo, para mostrar la página de mantenimiento exclusivamente para el sitio info.example.net, debemos crear el archivo enabled-info.

Para lograr este comportamiento debemos:

  1. Crear el directorio /srv/www/enobras con una página de mantenimiento apropiada (p.e. ésta).

  2. Crear el snippet mantenimiento.conf:

    set $enobras "/srv/www/mantenimiento/enobras";
    if (-f "$enobras-$host") {
       set $mantenimiento 1;
    }
    if (-f "$enobras") {
       set $mantenimiento 1;
    }
    # Pero hay que evitar la redirección cuando
    # se intenta el desafío HTML del protocolo ACME.
    if ($uri ~ "/.well-known/") {
       set $mantenimiento 0;
    }
    if ($mantenimiento) {
       return 503;
    }
    
    error_page 503 @mantenimiento;
    
    location /enobras {
       internal;
    }
    
    location @mantenimiento {
       root /srv/www/enobras;
       rewrite ^ /mantenimiento.html break;
    }
    

Listo. Ahora bien, si el mantenimiento exige hacer cambios sobre la página que requieren el acceso a la aplicación, la página de mantenimiento nos impedirá también a nosotros acceder y, por tanto, hacer y probar los cambios. Una solución es habilitar una serie de direcciones IP desde las que habilitar el mantenimiento no tendrá efecto. Para ello necesitamos el módulo geo de nginx. El módulo puede ayudar a identificar los grupos de direcciones IP que queremos que burlen el mantenimiento. Para ello, debe crearse:

# /etc/nginx/conf.d/adminip.conf
geo $adminip {
   default           0;
   127.0.0.0/8       1;
   10.0.0.0/8        1;
   172.16.0.0/12     1;
   192.188.0.0/16    1;
}

que identifica a los clientes que se conectan desde direcciones locales[1]: hecho lo cual, podemos modificar mantenimiento.conf para que, cuando la IP del cliente sea una de ellas, no tenga efecto el estado de mantemiento:

set $enobras "/srv/www/mantenimiento/enobras";
if (-f "$enobras-$host") {
   set $mantenimiento 1;
}
if (-f "$enobras") {
   set $mantenimiento 1;
}
# Pero hay que evitar la redirección cuando
# se intenta el desafío HTML del protocolo ACME.
if ($uri ~ "/.well-known/") {
   set $mantenimiento 0;
}
# o cuando se accede desde una IP administrativa
if ($adminip) {
   set $mantenimiento 0;
}
if ($mantenimiento) {
   return 503;
}

error_page 503 @mantenimiento;

location /enobras {
   internal;
}

location @mantenimiento {
   root /srv/www/enobras;
   rewrite ^ /mantenimiento.html break;
}

Advertencia

El módulo geo no está compilado estáticamente en el ejecutable, por lo que deberá comprobar si con su sabor de nginx tiene instalado el paquete libnginx-mod-http-geoip.

7.2.2.2.10.6. Registros con systemd

nginx escribe directamente sus registros en el fichero que indiquemos en las directivas access_log y error_log. Ahora bien, también da la posibilidad de pasárselos al gestor de registros (systemd en las versiones modernas de debian) y que éste se encargue. Para ello podemos crear el siguiente fichero:

# /etc/nginx/conf.d/logging.conf
log_format  journald  '$host[$remote_addr] - $request - $status $body_bytes_sent "$http_user_agent"';
access_log  syslog:server=unix:/dev/log,facility=local7,severity=info,nohostname journald;
error_log   syslog:server=unix:/dev/log,facility=local7,severity=error,nohostname;

Advertencia

Además, habra qué comentar las directivas correspondientes presentes en /etc/nginx/nginx.conf, porque de lo contrario seguirán escribiéndose registros en los ficheros ahí definidos:

##
# Logging settings
##

#access_log /var/log/nginx/access.log;
#error_log /var/log/nginx/access.log;

De este modo, los registros de acceso y error aparecerían al ejecutar:

# journalctl -u nginx

Para los de acceso se ha redefinido el formato, a fin de que aparezca el nombre del dominio al que se accede (por si manejamos varios dominios virtuales) y no la hora y fecha, ya que de esto último se encarga el propio gestor. Desgraciadamente para los de error no es posible.

Si además queremos que los registros en su formato tradicional no se apunten en /var/log/syslog, sino en otro fichero, entonces debemos crear el fichero /etc/rsyslog.d/nginx.conf con el siguiente contenido:

# cat > /etc/rsyslog.d/nginx.conf
local7.=info    /var/log/nginx/access.log
local7.err      /var/log/nginx/error.log
local7.*        &
# invoke-rc.d rsyslog restart

Nota

Como las líneas contienen el nombre del dominio, los registros de acceso es posible separarlos en distintos archivos dependiendo tal nombre. Véase cómo escribir las reglas para rsyslog

Nota

Como hemos colocado los registros dentro de /var/log/nginx que es donde debian espera encontrar los logs de nginx, no tenemos que preocuparnos de las rotaciones, ya que existe /etc/logrotate.d/nginx que se encarga de ello.

7.2.2.2.10.7. Caché de paquetes

Cuando se requiere actualizar muchos ordenadores de la red local, la descarga de paquetes puedo suponer un gran tráfico de red. Una solución para evitarlo crear un repositorio completo, pero por lo general sólo se instalan una ínfima parte de los paquetes de la distribución con que descargamos y sincronizamos muchos paquetes que no son totalmente inútiles.

Una solución alternativa es crear un proxy[2] caché para almacenar los paquetes de descargados; asi sólo la primera instación requerirá descargarlos de internet y las siguientes se limitarán a obtenerlos del proxy. nginx nos permite hacerlo como por otro lado nos lo permitiría squid).

La idea es modificar el fichero sources.list de manera que las líneas que se escriben así:

deb http://ftp.fr.debian.org/debian/ stretch  main

pasen a estar escritas así:

deb http://debian-cache.example.net/ftp.fr.debian.org/debian/ stretch  main

es decir, la línea es igual pero insertando el nombre de nuestro proxy al nombre de la máquina del reposito de debian. De este modo, el gestor de paquetes contactará con nuestro nginx y éste será capaz de conocer cuál es el repositorio y la ruta al recurso, simplemente transformando el primer directorio en el nombre del repositorio.

Para ello lo primero es crear un directorio de almacenamiento para las descargas:

# mkdir -p /var/cache/nginx
# chown www-data /var/cache/nginx

Y ahora definir cómo es esta caché en /etc/nginx/conf.d/cache.conf:

proxy_cache_path /var/cache/nginx/debcache
                 levels=1:2
                 keys_zone=debcache:8m
                 max_size=500m
                 inactive=14d
                 use_temp_path=off;

cuyo contenido supone que:

  • Almacenaremos todos los datos en /var/cache/nginx/debcache.

  • La caché la podremos referir luego como debcache.

  • Dispondrá de un tamaño máximo de 500 MB. Si se supera este tamaño, para respetar este límite, se empezarán a eliminar contenidos empezando por aquellos cuyo tiempo de acceso es más antiguo.

  • Los contenidos permanecerán 14 días almacenados. Superado este tiempo, se eliminarán.

Por último debemos crear el dominio virtual (que hemos llamado debian-cache.example.net) con esta configuracion:

server {
   listen  80;
   server_name   debian-cache.example.net;
   server_tokens off;
   
   location ~ ^/(?P<servidor>[^/]+).*$ {
      rewrite  ^/[^/]+(.*)$ $1 break;

      proxy_pass  http://$servidor;
      resolver 8.8.8.8;  # U otro servidor DNS más apropiado.
      proxy_http_version 1.1;
      proxy_set_header Host      $servidor;
      proxy_set_header X-Real-IP $remote_addr;

      proxy_cache debcache;
      proxy_cache_valid 8h;
      proxy_cache_valid 404 10m;
      proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
      proxy_cache_lock on;

      location ~ \.deb$ {
         rewrite  ^/[^/]+(.*)$ $1 break;
         proxy_pass  http://$servidor;

         proxy_cache_valid 14d;
      }
   }
}

Actúa como proxy entre el repositorio de debian y el cliente usando la caché. Ahora bien, almacenar durante mucho tiempo la lista de paquetes no es muy recomendable, porque si un paquete se actualiza en el repositorio, pero nuestra lista no, el cliente intentará obtener una versión antigua del paquete y se encontrará con un error 404. Por ello configuramos del siguiente modo:

  • Los ficheros cacheados que no son paquetes debian se considerarán inválidos después de 8 horas, o sea, una jornada de trabajo. Si se detectan muchos errores 404 como consecuencia de la desincronización con el repositorio, se puede disminuir este tiempo a costa de que sean necesarias más actualizaciones remotas de las listas de paquetes el mismo día.

  • Los paquetes, en cambio, se consideran válidos durante 14 días. A diferencia del caso anterior que un paquete cacheado se vuelva obsoleto, no supone un error, ya que el cliente descargará el nuevo paquete. La única desventaja es que cachearemos la versión antigua y la nueva y ocuparemos espacio de disco innecesariamente.

7.2.2.2.10.8. Testeo del rendimiento

Notas al pie