Ejecutar Apache Nginx y HAProxy en el mismo servidor ubuntu 20.04

Si eres un ingenios@ de sistemas, probablemente tengas un VPS en el que puedas albergar mas de una aplicación auto alojada. Hoy día puedes encontrar servidores con 4 CPU 8 GB de Ram y 200 GB de disco duro por 6€ al mes IVA incluido en CONTABO . También debes saber que existen dos servidores web muy populares. Apache es un servidor web muy conocido desde los años 90. Nginx fue desarrollado por primera vez en 2004 y rápidamente ganó tracción debido a su ligera huella de memoria y su rápida velocidad de procesamiento para archivos HTML estáticos.

En este tutorial aprenderas como instalar y configurar Apache Nginx y HAProxy en el mismo servidor

Tanto Apache como Nginx admiten el alojamiento virtual, lo que significa que puedes alojar varios sitios o aplicaciones web en el mismo servidor. Sin embargo, te encontrarás con situaciones en las que tienes un servidor web en funcionamiento, pero una aplicación web en particular requiere el uso de un servidor web diferente. El puerto 80 o 443 en la dirección IP pública sólo puede ser utilizado por un proceso. Si Apache está usando el puerto, entonces Nginx no puede usarlo (o enlazarlo). Entonces, ¿qué puedes hacer?

Puedes configurar Nginx como un proxy inverso a Apache, para que Nginx pueda redirigir las peticiones HTTP a Apache. En mi experiencia, he encontrado que esto no es siempre la mejor manera porque alguna vez ha causado problemas extraños que no puedo solucionar. En su lugar, prefiero usar HAProxy como proxy inverso tanto para Nginx como para Apache. HAProxy es un balanceador de carga y servidor proxy de alta disponibilidad, gratuito y de código abierto, para aplicaciones basadas en TCP y HTTP.

Ejecutar Apache Nginx y HAProxy en el mismo servidor

Así es como funciona.

  • Nginx escucha en 127.0.0.1:80 y 127.0.0.1:443
  • Apache escucha en 127.0.0.2:80 y 127.0.0.2:443
  • HAProxy escucha en los puertos 80 y 443 de la dirección IP pública. Redirige las peticiones HTTP del puerto 80 al puerto 443. Cuando una solicitud llega al puerto 443, elegirá entre Nginx y Apache analizando la cabecera SNI (indicación del nombre del servidor) en la solicitud HTTPS.
Ejecutar Apache Nginx y HAProxy en el mismo servidor
Podemos ver como independientemente del navegador que estés usando dependiendo de si quieres ir a chatwoot, mautic o matomo. Haproxy te llevara

Paso 1: Detener Nginx y Apache

Para detener Nginx, ejecuta

sudo systemctl stop nginx

Para detener Apache, ejecuta

sudo systemctl stop apache2

Paso 2: Cambiar el puerto de escucha en Nginx

Necesitamos hacer que Nginx escuche en 127.0.0.1:80. Abre tus archivos de configuración de Nginx en /etc/nginx/conf.d/ o /etc/nginx/sites-enabled/ y encuentra la siguiente línea.

listen 80;

Cámbialo por

listen 127.0.0.1:80;

Si https está habilitado en el bloque del servidor Nginx, entonces también encuentre

listen 443 ssl;

Y cambiarla por

listen 127.0.0.1:443 ssl;

El archivo de configuración principal de Nginx /etc/nginx/nginx.conf puede incluir un host virtual por defecto escuchando en el puerto 80 o 443, por lo que es posible que tengas que editar este archivo también.

Reinicie Nginx para que los cambios surtan efecto.

sudo systemctl restart nginx

Paso 3: Cambiar el puerto de escucha en Apache

Tenemos que hacer que Apache escuche en 127.0.0.2:80.

Edita este archivo /etc/apache2/ports.conf .

sudo nano /etc/apache2/ports.conf

Cambia

Listen 80
Listen 443

Por

Listen 127.0.0.2:80
Listen 127.0.0.2:443

Guarde y cierre el archivo. También vaya al directorio /etc/apache2/sites-enabled/ y edite los archivos del host virtual. Cambia

<VirtualHost *:80>

a

<VirtualHost 127.0.0.2:80>

Si hay host virtual SSL, entonces también cambia

<VirtualHost *:443>

A

<VirtualHost 127.0.0.2:443>

Reinicia Apache.

sudo systemctl restart apache2

Paso 4: Configurar HAProxy

Instale HAProxy en su distribución.

Debian/Ubuntu

sudo apt install haproxy

Editar el archivo de configuración de HAProxy.

sudo nano /etc/haproxy/haproxy.cfg

Añade el siguiente fragmento de código al final del archivo, que hará que HAPorxy escuche en el puerto 80 de la dirección IP pública y redirija las peticiones HTTP del puerto 80 al puerto 443. Sustituye 12.34.56.78 por la dirección IP pública de tu servidor.

frontend http
    bind 12.34.56.78:80
    mode http
    redirect scheme https code 301

Ahora también tenemos que añadir un front end HTTPS.

frontend https
    bind 12.34.56.78:443
    mode tcp
    tcp-request inspect-delay 5s
    tcp-request content accept if { req_ssl_hello_type 1 }

A continuación, define los back-end Nginx y Apache. El parámetro check indica a HAProxy que realice comprobaciones de salud en el back-end enviando un paquete TCP.

backend nginx
    mode tcp
    option ssl-hello-chk
    server nginx 127.0.0.1:443 check

backend apache
    mode tcp
    option ssl-hello-chk
    server apache 127.0.0.2:443 check

Puedes definir un back end por defecto con:

default_backend nginx

Utilizaremos la cabecera SNI en la petición HTTPS para redirigir al back end correcto. Por ejemplo, si Nginx está sirviendo al dominio1.com y Apache está sirviendo al dominio2.com, entonces se añaden las siguientes dos líneas. También puede utilizar subdominios, siempre que sean diferentes.

use_backend nginx if { req_ssl_sni -i domain1.com }
use_backend apache if { req_ssl_sni -i domain2.com }

Observa que las directivas default_backend y use_backend deben colocarse por encima de las definiciones del backend.

frontend http
    bind 12.34.56.78:80
    mode http
    redirect scheme https code 301

frontend https
    bind 12.34.56.78:443
    mode tcp
    tcp-request inspect-delay 5s
    tcp-request content accept if { req_ssl_hello_type 1 }

    default_backend nginx

    use_backend nginx if { req_ssl_sni -i domain1.com }
    use_backend apache if { req_ssl_sni -i domain2.com }

backend nginx
    mode tcp
    option ssl-hello-chk
    server nginx 127.0.0.1:443 check

backend apache
    mode tcp
    option ssl-hello-chk
    server apache 127.0.0.2:443 check

En la configuración anterior, utilizamos la función SNI (Server Name Indication) en TLS para diferenciar el tráfico HTTPS.

  • Cuando dominio1.com está en el Cliente Hello TLS, HAProxy redirige el tráfico al backend nginx.
  • Cuando dominio2.com está en el Cliente Hello TLS, HAProxy redirige el tráfico al backend de apache.

Si el cliente no especifica el nombre del servidor en TLS Cliente Hello, entonces HAproxy utilizará el backend por defecto (nginx).

Guarde y cierre el archivo. A continuación, reinicie HAproxy.

sudo systemctl restart haproxy

Ahora Apache, Nginx y HAProxy pueden funcionar en el mismo servidor.

Cómo reenviar la dirección IP del cliente al backend

Por defecto, Apache y Nginx sólo pueden ver la dirección IP de HAProxy. Para obtener la dirección IP real del cliente, asegúrese de añadir la opción send-proxy-v2 en la definición del back-end de HAProxy como se indica a continuación.

backend nginx
    mode tcp
    option ssl-hello-chk
    server nginx 127.0.0.1:443 send-proxy-v2 check

backend apache
    mode tcp
    option ssl-hello-chk
    server apache 127.0.0.2:443 send-proxy-v2 check

También tenemos que añadir alguna configuración en Nginx y Apache para que funcione, de lo contrario su sitio web será inaccesible.

Nginx

Añade proxy_protocol en la directiva de escucha de Nginx como se indica a continuación.

listen 127.0.0.2:443 ssl http2 proxy_protocol;

A continuación, añade las siguientes dos directivas en el bloque Nginx http { } del archivo /etc/nginx/nginx.conf.

set_real_ip_from 127.0.0.1;
real_ip_header proxy_protocol;

Guarde y cierre el archivo. A continuación, vuelva a cargar Nginx.

sudo systemctl reload nginx

Nota: Si tu sitio web se ejecuta detrás de Cloudflare CDN, entonces debes cambiar real_ip_header proxy_protocol; a real_ip_header CF-Connecting-IP; para mostrar la dirección IP real del cliente. También puede añadir esta directiva real_ip_header al archivo de bloque del servidor individual para anular la configuración global en el archivo /etc/nginx/nginx.conf.

Por último, reinicia HAProxy.

sudo systemctl restart haproxy

Apache

Si utiliza Apache en Debian/Ubuntu, necesita habilitar el módulo remoteip. (Este módulo está habilitado en CentOS por defecto).

sudo a2enmod remoteip

A continuación, añade las siguientes 3 líneas en tu archivo de configuración del host virtual de Apache.

RemoteIPProxyProtocol On
RemoteIPHeader X-Forwarded-For
RemoteIPTrustedProxy 127.0.0.1

Así.

<VirtualHost 127.0.0.2:443>
    ServerName www.example.com
    RemoteIPProxyProtocol On
    RemoteIPHeader X-Forwarded-For
    RemoteIPTrustedProxy 127.0.0.1

Guarda y cierra el archivo. A continuación, también tenemos que cambiar el formato de registro combinado. Edita el archivo de configuración principal de Apache.

sudo nano /etc/apache2/apache2.conf

o

sudo nano /etc/httpd/conf/httpd.conf

Encuentra la siguiente línea.

LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined

Sustitúyelo por:

LogFormat "%a %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined

Guarda y cierra el archivo. A continuación, reinicie Apache para que los cambios surtan efecto.

sudo systemctl restart apache2

o

sudo systemctl restart httpd

Nota que la directiva RemoteIPProxyProtocol On sólo está disponible en Apache 2.4.31 y posteriores. Para comprobar su versión de Apache, ejecute

sudo apache2 -v

o

sudo httpd -v

Ubuntu 18.04 viene con Apache 2.4.29. Si su versión de Apache no cumple con este requisito, entonces se debe eliminar el send-proxy-v2 en la definición del back-end de HAProxy. CentOS 8 viene con Apache 2.4.37.

Por último, reinicia HAProxy.

sudo systemctl restart haproxy

Cómo añadir nuevos hosts virtuales

Si tienes servidores web Apache y Nginx, entonces sólo necesitas dos backends: uno para Apache y otro para Nginx.

Cuando necesites añadir otro dominio (o subdominio), no necesitas añadir un nuevo backend en HAProxy. En su lugar, debes crear un nuevo host virtual para el nuevo dominio (o subdominio) en Apache o Nginx, y luego utilizar la directiva use_backend en HAProxy para redirigir el tráfico al backend correcto.

frontend http
    bind 12.34.56.78:80
    mode http
    redirect scheme https code 301

frontend https
    bind 12.34.56.78:443
    mode tcp
    tcp-request inspect-delay 5s
    tcp-request content accept if { req_ssl_hello_type 1 }

    default_backend nginx

    use_backend nginx if { req_ssl_sni -i domain1.com }
    use_backend nginx if { req_ssl_sni -i sub.domain1.com }
    use_backend apache if { req_ssl_sni -i domain2.com }
    use_backend apache if { req_ssl_sni -i sub.domain2.com }

backend nginx
    mode tcp
    option ssl-hello-chk
    server nginx 127.0.0.1:443 check

backend apache
    mode tcp
    option ssl-hello-chk
    server apache 127.0.0.2:443 check

Recomiendo usar apache y nginx como nombres de backend como en el código anterior, para que sepas qué servidor web aloja cada aplicación.

Obtención del nuevo certificado SSL de Let’s Encrypt

http-01 challenge

En primer lugar, es necesario entender cómo utilizar el autentificador Certbot apache y nginx para obtener el certificado TLS.

El autentificador de Certbot apache y nginx utiliza el desafío http-01, que funciona en el puerto TCP 80. Como HAProxy utiliza el puerto TCP 80, es probable que el desafío http-01 de Certbot falle. Sin embargo, puedes hacer que funcione con el siguiente método.

  1. Habilite el host SSL en Apache o Nginx con un certificado autofirmado o un certificado para otro dominio.
  2. A continuación, utilice el autentificador Certbot apache y nginx como de costumbre.

Este método funciona porque el desafío http-01 de Certbot puede ser redirigido desde el puerto TCP 80 al puerto TCP 443, y Certbot acepta certificados no válidos en el puerto 443.

dns-01 challenge

También puedes utilizar el desafío dns-01 de certbot, que funciona creando un registro TXT temporal para tu dominio para certificar que realmente eres el propietario de este dominio, por lo que puede evitar el puerto TCP 80 y el puerto TCP 443. La desventaja de este método es que no todos los registradores de dominios son compatibles.

En primer lugar, necesitas instalar el plugin DNS de Certbot. Hay varios plugins de DNS disponibles en el repositorio de software de Debian y Ubuntu, que puedes encontrar con

apt search python3-certbot-dns

La salida:

python3-certbot-dns-cloudflare/bionic,bionic 0.23.0-1 all
Cloudflare DNS plugin for Certbot

python3-certbot-dns-digitalocean/bionic,bionic 0.23.0-1 all
DigitalOcean DNS plugin for Certbot

python3-certbot-dns-dnsimple/bionic,bionic 0.23.0-1 all
DNSimple DNS plugin for Certbot

python3-certbot-dns-google/bionic,bionic 0.23.0-1 all
Google DNS plugin for Certbot

python3-certbot-dns-rfc2136/bionic,bionic 0.23.0-1 all
RFC 2136 DNS plugin for Certbot

python3-certbot-dns-route53/bionic,bionic 0.23.0-1 all
Route53 DNS plugin for Certbot

Encuentra los plugins de DNS en el repositorio de EPEL.

sudo dnf install epel-release
dnf search certbot-dns

Salida:

python3-certbot-dns-ovh.noarch : OVH DNS Authenticator plugin for Certbot
python3-certbot-dns-nsone.noarch : NS1 DNS Authenticator plugin for Certbot
python3-certbot-dns-gehirn.noarch : Gehirn Infrastructure Service DNS Authenticator plugin for Certbot
python3-certbot-dns-google.noarch : Google Cloud DNS Authenticator plugin for Certbot
python3-certbot-dns-linode.noarch : Linode DNS Authenticator plugin for Certbot
python3-certbot-dns-luadns.noarch : LuaDNS Authenticator plugin for Certbot
python3-certbot-dns-rfc2136.noarch : RFC 2136 DNS Authenticator plugin for Certbot
python3-certbot-dns-route53.noarch : Route53 DNS Authenticator plugin for Certbot
python3-certbot-dns-cloudxns.noarch : CloudXNS DNS Authenticator plugin for Certbot
python3-certbot-dns-dnsimple.noarch : DNSimple DNS Authenticator plugin for Certbot
python3-certbot-dns-cloudflare.noarch : Cloudflare DNS Authenticator plugin for Certbot
python3-certbot-dns-cloudflare.noarch : Cloudflare DNS Authenticator plugin for Certbot
python3-certbot-dns-dnsmadeeasy.noarch : DNS Made Easy DNS Authenticator plugin for Certbot
python3-certbot-dns-sakuracloud.noarch : Sakura Cloud DNS Authenticator plugin for Certbot

Instala uno de estos plugins según el servicio de alojamiento de DNS que estés utilizando. Yo estoy usando Cloudflare, así que usaré Cloudflare como ejemplo. Ejecuta el siguiente comando para instalar el paquete python3-certbot-dns-cloudflare en Debian/Ubuntu.

sudo apt install python3-certbot-dns-cloudflare

A continuación, cree un archivo de configuración para Cloudflare.

sudo nano /etc/letsencrypt/cloudflare.ini

Tenemos que añadir la dirección de correo electrónico de nuestra cuenta de Cloudflare y la clave de la API en este archivo.

# Cloudflare API credentials used by Certbot
dns_cloudflare_email = you@example.com
dns_cloudflare_api_key = 0123456789abcdef0123456789abcdef01234567

Puede encontrar la clave de la API de Cloudflare en https://dash.cloudflare.com/profile. Observa que el plugin de Cloudflare de Certbot no admite actualmente los “tokens de API” de Cloudflare, así que asegúrate de utilizar la “clave de API global” para la autenticación.

Guarda y cierra el archivo. La clave de la API omite la autenticación de dos factores de Cloudflare, por lo que solo debe permitir que el usuario root lea este archivo.

sudo chmod 600 /etc/letsencrypt/cloudflare.ini

Ahora ejecuta certbot.

sudo certbot --agree-tos -a dns-cloudflare -i nginx --redirect --hsts --staple-ocsp --email you@example.com -d www.your-domain.com,your-domain.com

En el comando anterior, especificamos que usaremos dns-cloudflare como autentificador para obtener un nuevo certificado TLS y usaremos el plugin nginx para crear el bloque del servidor HTTPS. Si usas Apache, entonces sustituye nginx por apache.

Este comando le pedirá que introduzca la ruta del archivo .ini, así que introduces /etc/letsencrypt/cloudflare.ini y pulsas la tecla Enter.

certbot dns cloudflare

Una vez obtenido e instalado el certificado SSL en el archivo de configuración del servidor web. Tienes que hacer que escuche en 127.0.0.1:443 o 127.0.0.2:443. Por ejemplo, si utiliza Nginx, busque

listen 443 ssl

Y cámbialo por

listen 127.0.0.1:443 ssl http2

Además, cambia el puerto 80 por

listen 127.0.0.1:80;

Guarda y cierra el archivo. A continuación, reinicie su servidor web.

Common Errors

Nombre de dominio incorrecto

Si tienes varios hosts virtuales Nginx en un servidor, y cuando escribes un nombre de dominio, el navegador web te lleva a otro nombre de dominio alojado en el mismo servidor, podría ser que uno de tus nombres de archivo de host virtual Nginx no termina con la extensión de nombre de archivo .conf, por lo que Nginx no cargó este host virtual. También podría ser que hayas establecido un registro AAAA para el nombre de dominio, pero no hayas configurado Nginx para servir el nombre de dominio en IPv6.

Lo anterior también se aplica al servidor web Apache.

Violación del protocolo de red

Si estableces la cabecera send-proxy-v2 en HAProxy, pero no activas proxy_protocol en Nginx o Apache, tu sitio web mostrará el siguiente error.

The site  has experienced a network protocol violation that cannot be repaired.

Recuerda reiniciar Nginx/Apache después de hacer cambios en el archivo de configuración.

PR_CONNECT_RESET_ERROR

Si habilita proxy_protocol en Nginx o Apache, pero no ha establecido la cabecera send-proxy-v2 en HAProxy, su sitio web mostrará este error.

secure connection failed. PR_CONNECT_RESET_ERROR

Y encontrarás el siguiente mensaje de error en el registro de errores de Nginx o Apache.

broken header while reading PROXY protocol

Recuerda reiniciar Nginx/Apache después de hacer cambios en el archivo de configuración.

También hay que tener en cuenta que si se habilita el protocolo proxy en un archivo de host virtual de Apache/Nginx, entonces también se habilita implícitamente para todos los demás hosts virtuales. Y no he encontrado una manera de deshabilitar proxy_protocol para un host virtual individual. Dado que está ejecutando Apache y Nginx en el mismo servidor, una solución es que sólo habilite el protocolo proxy en Nginx y no lo habilite en Apache. Si un host virtual no necesita obtener la dirección IP real del cliente, configúrelo para usar Apache.

Actualización: Se me ha ocurrido que puedes configurar un nuevo host virtual Apache/Nginx en otra dirección loopback como 127.0.0.4. De esta forma, el nuevo host virtual no habilitará el protocolo proxy.

PR_END_OF_FILE_ERROR

If you enabled proxy protocol in Apache/Nginx, but you try to access the virtual host directly (bypassing HAProxy), then you will see the following error.

PR_END_OF_FILE_ERROR

Espero que este tutorial te ayude a sacar mejor partido de tu infraestructura VPS y poder disponer de varias aplicaciones en un mismo servidor, a veces por conveniencia y otras veces por necesidad.

¡Que tengas un Ingenioso día!

¡que tengas un ingenioso día!

Contrata tu plan Ingenios@ de Sistemas por 5€ al mes y responderé a todas tus preguntas sobre tecnología en el menor tiempo posible. Pasa a formar parte de la comunidad Ingenios@s de Sistemas y disfruta de contenido exclusivo y aprende sobre sistemas Open Source, Blockchain, SmarContract, Web3, Inteligencia Artificial y Reaidad Virtual, súbete al tren de la Revolución 4.0

Si quieres estar al día y no perderte nada Suscribete al Podcast Ingenios@s de Sistemas, un episodio diario que te mantendrá informado y formado en esta vertiginosa carrera.

Deja un comentario

Share to...