Envoy es un proxy de capa 7 de alto rendimiento, proyecto graduado de la CNCF, que se usa como proxy de borde, balanceador de carga y sidecar en mallas de servicios como Istio. Se configura con un archivo YAML basado en cuatro piezas: listeners (dónde escucha), filtros (qué hace con el tráfico), rutas (a dónde lo envía) y clusters (los backends).

En este tutorial instalarás Envoy en Ubuntu 24.04 a partir del binario oficial, lo ejecutarás como servicio systemd con un usuario sin privilegios y lo configurarás como balanceador HTTP delante de dos backends, con health checks activos, reintentos y la interfaz de administración para comprobar su estado.

Requisitos previos

Para seguir este tutorial necesitas:

  • Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath, con al menos 1 GB de RAM.
  • Un usuario no root con privilegios sudo.
  • El puerto 80 libre (si tienes Nginx o Apache escuchando en él, detenlo o usa otro puerto en el listener).

Paso 1: Descargar e instalar el binario de Envoy

El repositorio APT oficial de Envoy no publica paquetes para Ubuntu 24.04 y va varias versiones por detrás, así que la forma más fiable de instalarlo es el binario estático que se adjunta a cada release en GitHub. Consulta la última versión en la página de releases de Envoy y guárdala en una variable (este tutorial usa la 1.39.1):

ENVOY_VERSION=1.39.1

Descarga el binario y el archivo de checksums:

cd /tmp
curl -fLO "https://github.com/envoyproxy/envoy/releases/download/v${ENVOY_VERSION}/envoy-${ENVOY_VERSION}-linux-x86_64"
curl -fLO "https://github.com/envoyproxy/envoy/releases/download/v${ENVOY_VERSION}/checksums.txt.asc"

En un servidor ARM64, sustituye x86_64 por aarch_64 en el nombre del archivo.

Comprueba que el SHA-256 del binario coincide con el publicado:

echo "$(grep "/envoy-${ENVOY_VERSION}-linux-x86_64$" checksums.txt.asc | cut -d' ' -f1)  envoy-${ENVOY_VERSION}-linux-x86_64" | sha256sum -c
envoy-1.39.1-linux-x86_64: OK

Instálalo en /usr/local/bin con los permisos correctos:

sudo install -m 0755 "envoy-${ENVOY_VERSION}-linux-x86_64" /usr/local/bin/envoy
envoy --version
envoy  version: .../1.39.1/Clean/RELEASE/BoringSSL

Paso 2: Crear el usuario y el directorio de configuración

Envoy no necesita privilegios de root. Crea un usuario de sistema sin shell y el directorio donde vivirá la configuración:

sudo useradd --system --no-create-home --shell /usr/sbin/nologin envoy
sudo mkdir -p /etc/envoy

Comprueba que el usuario existe:

id envoy
uid=998(envoy) gid=998(envoy) groups=998(envoy)

Paso 3: Levantar dos backends de prueba

Para ver el balanceo necesitas algo detrás de Envoy. Si ya tienes una aplicación escuchando en local, puedes saltarte este paso y usar sus puertos en el Paso 4. Si no, crea dos servidores HTTP mínimos con Python, cada uno con una página que indica quién responde:

sudo mkdir -p /srv/backend1 /srv/backend2
echo "respuesta desde backend1" | sudo tee /srv/backend1/index.html
echo "respuesta desde backend2" | sudo tee /srv/backend2/index.html

Arráncalos como servicios transitorios de systemd, escuchando solo en 127.0.0.1 en los puertos 8081 y 8082:

sudo systemd-run --unit=backend1 python3 -m http.server 8081 --bind 127.0.0.1 --directory /srv/backend1
sudo systemd-run --unit=backend2 python3 -m http.server 8082 --bind 127.0.0.1 --directory /srv/backend2

Comprueba que responden:

curl http://127.0.0.1:8081/
curl http://127.0.0.1:8082/
respuesta desde backend1
respuesta desde backend2

Paso 4: Escribir la configuración de Envoy

La configuración estática de Envoy tiene dos bloques principales. En static_resources.listeners defines el puerto en el que escucha y la cadena de filtros; para HTTP, el filtro clave es el http_connection_manager, que contiene las rutas y termina en el filtro router. En static_resources.clusters defines los backends, la política de balanceo y los health checks. El bloque admin expone la interfaz de administración.

Crea el archivo de configuración:

sudo nano /etc/envoy/envoy.yaml

Pega el siguiente contenido:

admin:
  address:
    socket_address: { address: 127.0.0.1, port_value: 9901 }

static_resources:
  listeners:
  - name: http_listener
    address:
      socket_address: { address: 0.0.0.0, port_value: 80 }
    filter_chains:
    - filters:
      - name: envoy.filters.network.http_connection_manager
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
          stat_prefix: ingress_http
          access_log:
          - name: envoy.access_loggers.stdout
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog
          route_config:
            name: local_route
            virtual_hosts:
            - name: app
              domains: ["*"]
              routes:
              - match: { prefix: "/" }
                route:
                  cluster: app_backend
                  timeout: 15s
                  retry_policy:
                    retry_on: "connect-failure,refused-stream,5xx"
                    num_retries: 2
          http_filters:
          - name: envoy.filters.http.router
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router

  clusters:
  - name: app_backend
    connect_timeout: 2s
    type: STATIC
    lb_policy: ROUND_ROBIN
    load_assignment:
      cluster_name: app_backend
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address: { address: 127.0.0.1, port_value: 8081 }
        - endpoint:
            address:
              socket_address: { address: 127.0.0.1, port_value: 8082 }
    health_checks:
    - timeout: 1s
      interval: 5s
      unhealthy_threshold: 2
      healthy_threshold: 2
      http_health_check:
        path: "/"

Qué hace cada parte:

  • admin: la interfaz de administración escucha solo en 127.0.0.1:9901. Nunca la expongas a Internet: permite, entre otras cosas, apagar Envoy.
  • Listener http_listener: acepta HTTP en el puerto 80 de todas las interfaces y escribe un log de acceso por petición en la salida estándar, que acabará en journald.
  • route_config: todas las peticiones (domains: ["*"], prefijo /) van al cluster app_backend, con un timeout de 15 segundos y hasta 2 reintentos si el backend rechaza la conexión o devuelve un 5xx.
  • Cluster app_backend: dos endpoints con IP fija (type: STATIC) balanceados en round robin. El health check pide / cada 5 segundos y saca un endpoint del balanceo tras 2 fallos seguidos.

Si tus backends se identifican por nombre DNS en lugar de IP, usa type: STRICT_DNS y pon el nombre en address.

Valida la sintaxis antes de arrancar nada:

envoy --mode validate -c /etc/envoy/envoy.yaml
configuration '/etc/envoy/envoy.yaml' OK

Si hay un error, Envoy indica el campo exacto que no reconoce. Los fallos más habituales son la indentación y un @type mal escrito.

Paso 5: Ejecutar Envoy como servicio systemd

Crea una unidad de systemd que ejecute Envoy con el usuario envoy. La capacidad CAP_NET_BIND_SERVICE le permite escuchar en el puerto 80 sin ser root, y ExecStartPre valida la configuración antes de cada arranque para que un error de sintaxis no tumbe el servicio en marcha:

sudo nano /etc/systemd/system/envoy.service
[Unit]
Description=Envoy Proxy
After=network-online.target
Wants=network-online.target

[Service]
User=envoy
Group=envoy
ExecStartPre=/usr/local/bin/envoy --mode validate -c /etc/envoy/envoy.yaml
ExecStart=/usr/local/bin/envoy -c /etc/envoy/envoy.yaml --log-level info
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
NoNewPrivileges=true
LimitNOFILE=65536
Restart=on-failure

[Install]
WantedBy=multi-user.target

Recarga systemd, habilita el servicio y arráncalo:

sudo systemctl daemon-reload
sudo systemctl enable --now envoy
sudo systemctl status envoy
● envoy.service - Envoy Proxy
     Loaded: loaded (/etc/systemd/system/envoy.service; enabled; preset: enabled)
     Active: active (running) since Thu 2026-09-25 10:12:41 UTC; 3s ago

Si usas UFW, abre el puerto 80:

sudo ufw allow 80/tcp

Paso 6: Comprobar el balanceo

Lanza varias peticiones seguidas. Envoy las reparte en round robin entre los dos backends:

for i in 1 2 3 4; do curl -s http://localhost/; done
respuesta desde backend1
respuesta desde backend2
respuesta desde backend1
respuesta desde backend2

Cada petición deja una línea en el log de acceso, que puedes seguir con journald:

sudo journalctl -u envoy -f
[2026-09-25T10:13:02.114Z] "GET / HTTP/1.1" 200 - 0 25 1 1 "-" "curl/8.5.0" "..." "localhost" "127.0.0.1:8081"

El último campo indica qué backend atendió la petición. Pulsa CTRL+C para salir.

Paso 7: Usar la interfaz de administración y probar los health checks

La interfaz de administración es la herramienta principal para saber qué está haciendo Envoy. Comprueba que el proceso está listo para recibir tráfico:

curl -s http://127.0.0.1:9901/ready
LIVE

Consulta el estado de salud de cada endpoint del cluster:

curl -s http://127.0.0.1:9901/clusters | grep health_flags
app_backend::127.0.0.1:8081::health_flags::healthy
app_backend::127.0.0.1:8082::health_flags::healthy

Ahora simula la caída de un backend:

sudo systemctl stop backend2

Espera unos 10 segundos (dos health checks fallidos) y vuelve a consultar:

curl -s http://127.0.0.1:9901/clusters | grep health_flags
app_backend::127.0.0.1:8081::health_flags::healthy
app_backend::127.0.0.1:8082::health_flags::/failed_active_hc

Todas las peticiones van ya a backend1, sin errores para el cliente:

for i in 1 2 3; do curl -s http://localhost/; done
respuesta desde backend1
respuesta desde backend1
respuesta desde backend1

Las estadísticas del cluster muestran cuántas peticiones se han servido y cuántos health checks han fallado:

curl -s http://127.0.0.1:9901/stats | grep -E 'cluster.app_backend.(upstream_rq_2xx|health_check.failure)'
cluster.app_backend.health_check.failure: 4
cluster.app_backend.upstream_rq_2xx: 7

Otros endpoints útiles son /config_dump (la configuración efectiva en JSON), /listeners y /stats/prometheus (las mismas métricas en formato Prometheus, listas para que las recoja tu sistema de monitorización a través de un túnel o un proxy autenticado).

Cuando termines las pruebas, detén y elimina los backends de ejemplo:

sudo systemctl stop backend1
sudo rm -rf /srv/backend1 /srv/backend2

Aplicar cambios de configuración

Envoy no relee envoy.yaml al recibir una señal. Para aplicar cambios en una configuración estática, valida y reinicia:

envoy --mode validate -c /etc/envoy/envoy.yaml && sudo systemctl restart envoy

El reinicio corta las conexiones abiertas durante un instante. Si necesitas cambios sin cortes, el camino es la configuración dinámica (xDS) con un plano de control, que es lo que hacen Istio o Envoy Gateway.

Solución de problemas

El servicio no arranca y journalctl -u envoy muestra un error de validación: el ExecStartPre ha detectado un error en el YAML. Ejecuta envoy --mode validate -c /etc/envoy/envoy.yaml para ver el campo exacto.

cannot bind '0.0.0.0:80': Address already in use: otro proceso usa el puerto 80. Identifícalo con sudo ss -ltnp 'sport = :80' y detenlo o cambia port_value en el listener.

cannot bind '0.0.0.0:80': Permission denied: falta la capacidad CAP_NET_BIND_SERVICE en la unidad. Revisa las líneas AmbientCapabilities y CapabilityBoundingSet y ejecuta sudo systemctl daemon-reload.

Envoy responde 503 con no healthy upstream: todos los endpoints han fallado el health check. Comprueba con /clusters qué endpoints están marcados como /failed_active_hc y que la ruta del http_health_check devuelve un 200 en tus backends.

Envoy responde 503 con upstream connect error: el backend no acepta conexiones en la IP y puerto configurados. Pruébalo directamente con curl desde el servidor.

Conclusión

Tienes Envoy instalado en Ubuntu 24.04 como servicio systemd sin privilegios de root, balanceando tráfico HTTP entre dos backends con health checks activos y reintentos, y sabes usar la interfaz de administración para ver el estado de cada endpoint y las métricas.

Como siguientes pasos puedes añadir un listener en el puerto 443 con terminación TLS mediante un transport_socket y tus certificados, crear rutas por prefijo o por dominio que apunten a clusters distintos, o recoger /stats/prometheus con Prometheus para vigilar latencias y errores.