Volver al blog

gRPC a través de un proxy en arquitectura de microservicios: guía completa para configurar el túnel HTTP/2

Guía detallada para configurar el tráfico gRPC a través de proxies en arquitecturas de microservicios: desde el tunelado de HTTP/2 hasta el balanceo de carga y la terminación TLS.

📅7 de agosto de 2026
```html

gRPC es un marco de trabajo RPC de alto rendimiento de Google que funciona sobre HTTP/2 y se está convirtiendo en el estándar de facto para la interacción entre servicios. Pero en cuanto intentas enviar tráfico gRPC a través de un proxy, surgen problemas de inmediato: la mayoría de los proxies clásicos no saben trabajar con HTTP/2 y conexiones de streaming de larga duración. En este artículo, analizaremos cómo configurar correctamente gRPC a través de un proxy, desde la elección de la arquitectura hasta configuraciones específicas de Nginx, Envoy y HAProxy.

Por qué gRPC no funciona bien con proxies comunes

Para entender el problema, es necesario comprender cómo gRPC está diseñado internamente. El protocolo utiliza HTTP/2 como capa de transporte, y esto lo diferencia radicalmente de REST sobre HTTP/1.1. La mayoría de los proxies corporativos, firewalls y balanceadores de carga fueron diseñados en la era de HTTP/1.1 y simplemente no pueden manejar flujos multiplexados de HTTP/2.

Aquí hay problemas específicos que encontrarás al intentar enviar gRPC a través de un proxy HTTP clásico:

  • Degradación a HTTP/1.1. Muchos proxies degradan automáticamente la versión del protocolo. gRPC requiere HTTP/2; sin él, la conexión simplemente no se establecerá, y el cliente recibirá un error UNAVAILABLE.
  • Interrupción de conexiones de larga duración. gRPC utiliza activamente el streaming del servidor y el streaming bidireccional. Los proxies con tiempos de espera agresivos (especialmente AWS ELB Classic, algunas versiones de Squid) cierran conexiones que no transfieren datos durante más de 60 segundos.
  • Problemas con Content-Type. gRPC utiliza el encabezado Content-Type: application/grpc. Un proxy que no conoce este tipo puede rechazar la solicitud o almacenar en búfer incorrectamente el cuerpo.
  • Trailers. gRPC utiliza trailers de HTTP/2 para transmitir el estado de finalización de la llamada. Los proxies HTTP/1.1 no soportan trailers; la información sobre el estado se perderá.
  • Almacenamiento en búfer del cuerpo. Algunos proxies almacenan en búfer todo el cuerpo de la respuesta antes de enviarlo al cliente. Para las llamadas gRPC de streaming, esto significa que el cliente no recibirá ningún mensaje hasta que el stream se complete.

Conclusión clave:

Para que gRPC funcione a través de un proxy, se necesita un proxy que soporte HTTP/2 de extremo a extremo o un modo especial de tunelización. Un proxy HTTP clásico sin ajustes de configuración no funcionará.

Tunelización HTTP/2: cómo funciona

Existen dos enfoques fundamentalmente diferentes para la proxyización del tráfico gRPC, y es importante entender la diferencia entre ellos para elegir la solución adecuada para tu arquitectura.

Enfoque 1: HTTP/2 de extremo a extremo (recomendado)

El proxy entiende HTTP/2 y establece una conexión HTTP/2 tanto con el cliente como con el backend. Puede analizar flujos individuales, aplicar balanceo a nivel de solicitudes, agregar encabezados y realizar la terminación TLS. Esta es la opción más funcional: así funcionan Envoy, Nginx (a partir de la versión 1.13.10) y balanceadores de carga compatibles con gRPC en proveedores de nube.

Enfoque 2: Tunelización TCP (CONNECT)

El proxy no analiza el tráfico HTTP/2, sino que simplemente crea un túnel TCP transparente a través del método CONNECT. El cliente establece una conexión TLS directamente con el backend a través del túnel. El proxy solo ve un flujo de bytes cifrados. Este método es más fácil de configurar, pero te priva de las capacidades de balanceo a nivel de solicitudes gRPC y de agregar encabezados.

Característica HTTP/2 de extremo a extremo Túnel TCP CONNECT
Balanceo por solicitudes ✅ Sí ❌ No (solo por TCP)
Terminación TLS en el proxy ✅ Sí ❌ No
Agregar encabezados ✅ Sí ❌ No
Complejidad de configuración Media Baja
Soporte para streaming ✅ Completo ✅ Completo
Observabilidad (métricas) ✅ Detallada ❌ Solo TCP

Configuración de Nginx como proxy gRPC

Nginx soporta la proxyización de gRPC a partir de la versión 1.13.10 (febrero de 2018). Para que funcione, se necesita el módulo ngx_http_grpc_module, que está incluido en la compilación estándar. Importante: Nginx soporta HTTP/2 en el lado del cliente (frontend), pero en el lado del backend utiliza HTTP/2 solo para gRPC; el upstream HTTP normal funciona sobre HTTP/1.1.

Configuración básica de proxy gRPC en Nginx

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

    # Certificados TLS
    ssl_certificate     /etc/nginx/ssl/server.crt;
    ssl_certificate_key /etc/nginx/ssl/server.key;

    # Parámetros TLS modernos
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    location / {
        # Directiva grpc_pass en lugar de proxy_pass
        grpc_pass grpc://grpc_backend;

        # Tiempos de espera para streams de larga duración
        grpc_read_timeout  3600s;
        grpc_send_timeout  3600s;
        grpc_connect_timeout 5s;

        # Pasar la IP real del cliente
        grpc_set_header X-Real-IP $remote_addr;
        grpc_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

upstream grpc_backend {
    server backend-1:50051;
    server backend-2:50051;
    server backend-3:50051;

    # Keepalive para conexiones HTTP/2
    keepalive 32;
}

Presta atención a varios detalles críticos. Primero, se utiliza la directiva grpc_pass, en lugar de proxy_pass; son módulos diferentes con comportamientos distintos. En segundo lugar, los tiempos de espera grpc_read_timeout y grpc_send_timeout están establecidos en 3600 segundos (1 hora); esto es importante para los streams del servidor que pueden funcionar durante mucho tiempo sin transferir datos. En tercer lugar, keepalive 32 en el upstream permite reutilizar conexiones HTTP/2 con los backends.

Configuración para gRPC no cifrado (grpc://)

server {
    listen 80 http2;
    server_name grpc-internal.example.com;

    location / {
        grpc_pass grpc://127.0.0.1:50051;

        # Manejo de errores gRPC
        error_page 502 = /error502grpc;
    }

    location = /error502grpc {
        internal;
        default_type application/grpc;
        add_header grpc-status 14;
        add_header content-length 0;
        return 204;
    }
}

El bloque error502grpc es un detalle importante: cuando el backend no está disponible, devuelve el estado gRPC correcto UNAVAILABLE (14) en lugar del HTTP 502, que el cliente gRPC no podrá manejar correctamente.

Envoy Proxy: la mejor opción para gRPC en microservicios

Envoy fue creado por Lyft específicamente para arquitecturas de microservicios, y el soporte para gRPC está implementado a un nivel muy profundo. Entiende Protocol Buffers, puede transcodificar gRPC a REST, recopila métricas detalladas para cada método RPC y es la base para soluciones de service mesh como Istio, AWS App Mesh y otras. Si estás construyendo una arquitectura de microservicios seria, Envoy es el estándar de la industria.

Configuración básica de Envoy para gRPC

static_resources:
  listeners:
  - name: grpc_listener
    address:
      socket_address:
        address: 0.0.0.0
        port_value: 8080
    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: grpc_proxy
          codec_type: HTTP2
          route_config:
            name: grpc_routes
            virtual_hosts:
            - name: grpc_services
              domains: ["*"]
              routes:
              - match:
                  prefix: "/com.example.UserService"
                route:
                  cluster: user_service
                  timeout: 30s
                  retry_policy:
                    retry_on: "reset,connect-failure,retriable-status-codes"
                    num_retries: 3
                    retriable_status_codes: [14]
              - match:
                  prefix: "/com.example.OrderService"
                route:
                  cluster: order_service
                  timeout: 60s
          http_filters:
          - name: envoy.filters.http.grpc_stats
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.grpc_stats.v3.FilterConfig
              emit_filter_state: true
          - name: envoy.filters.http.router
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router

  clusters:
  - name: user_service
    connect_timeout: 5s
    type: STRICT_DNS
    lb_policy: ROUND_ROBIN
    http2_protocol_options: {}
    load_assignment:
      cluster_name: user_service
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: user-service
                port_value: 50051

Las principales ventajas de esta configuración son: enrutamiento por servicios gRPC (el prefijo de la ruta corresponde al nombre completo del servicio en el formato package.ServiceName), reintentos automáticos en caso de estado UNAVAILABLE y recopilación de métricas gRPC a través del filtro grpc_stats.

Transcodificación gRPC-Web en Envoy

Una de las características más destacadas de Envoy es su transcodificador gRPC-Web integrado. Los navegadores no soportan gRPC directamente (debido a las limitaciones del Fetch API sobre trailers HTTP/2), por lo que se utiliza el protocolo gRPC-Web. Envoy puede convertir automáticamente las solicitudes gRPC-Web del navegador en gRPC normal para el backend:

http_filters:
- name: envoy.filters.http.grpc_web
  typed_config:
    "@type": type.googleapis.com/envoy.extensions.filters.http.grpc_web.v3.GrpcWeb
- name: envoy.filters.http.cors
  typed_config:
    "@type": type.googleapis.com/envoy.extensions.filters.http.cors.v3.CorsPolicy
- name: envoy.filters.http.router
  typed_config:
    "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router

HAProxy y gRPC: configuración con balanceo de carga

HAProxy soporta gRPC a partir de la versión 1.9.2. Funciona a nivel TCP o HTTP/2, puede balancear tráfico gRPC y realizar health checks. HAProxy es una buena opción si ya lo estás utilizando en tu infraestructura y deseas agregar soporte para gRPC sin introducir un nuevo componente.

global
    maxconn 50000
    log stdout format raw local0

defaults
    log global
    timeout connect 5s
    timeout client  3600s
    timeout server  3600s

frontend grpc_frontend
    bind *:443 ssl crt /etc/haproxy/certs/server.pem alpn h2,http/1.1
    mode http
    option http-use-htx
    default_backend grpc_servers

backend grpc_servers
    mode http
    balance leastconn
    option http-use-htx

    # Health check a través del Protocolo de Verificación de Salud gRPC
    option httpchk GET /grpc.health.v1.Health/Check
    http-check expect status 200

    server grpc1 10.0.0.1:50051 check ssl verify none
    server grpc2 10.0.0.2:50051 check ssl verify none
    server grpc3 10.0.0.3:50051 check ssl verify none

Presta atención a varias configuraciones importantes. alpn h2,http/1.1 en la directiva bind indica que HAProxy acepta conexiones tanto HTTP/2 como HTTP/1.1 a través de la negociación TLS ALPN. timeout client 3600s y timeout server 3600s son parámetros críticos para streams gRPC de larga duración. El algoritmo leastconn es preferible a roundrobin para gRPC, ya que las conexiones de streaming pueden ser de larga duración y cargar desigualmente los backends.

Terminación TLS y cifrado de extremo a extremo para gRPC

gRPC supone por defecto el uso de TLS; esto es parte de la especificación. En la práctica, en una arquitectura de microservicios surge la pregunta: ¿dónde realizar la terminación TLS y cómo organizar el cifrado entre componentes? Existen tres patrones principales.

Patrón 1: Terminación TLS en el proxy (Edge TLS)

El proxy acepta tráfico cifrado de los clientes, lo descifra y lo envía a los backends a través de un canal no cifrado (o con un TLS separado). Este es el enfoque más común en redes corporativas. Los backends pueden utilizar grpc.Insecure() para simplificar la configuración.

Patrón 2: Cifrado de extremo a extremo (mTLS)

TLS mutuo (mTLS) es el estándar para service mesh. Cada servicio tiene su propio certificado, y en cada conexión ambas partes verifican los certificados entre sí. Esto asegura la autenticación de los servicios y el cifrado del tráfico dentro del clúster. Así es como funciona Istio con el proxy sidecar de Envoy.

# Go: servidor gRPC con mTLS
import (
    "crypto/tls"
    "crypto/x509"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
)

func createMTLSCredentials() credentials.TransportCredentials {
    cert, _ := tls.LoadX509KeyPair("server.crt", "server.key")
    
    caCert, _ := os.ReadFile("ca.crt")
    caCertPool := x509.NewCertPool()
    caCertPool.AppendCertsFromPEM(caCert)
    
    tlsConfig := &tls.Config{
        Certificates: []tls.Certificate{cert},
        ClientAuth:   tls.RequireAndVerifyClientCert,
        ClientCAs:    caCertPool,
    }
    
    return credentials.NewTLS(tlsConfig)
}

// Creación de un servidor con mTLS
creds := createMTLSCredentials()
server := grpc.NewServer(grpc.Creds(creds))

Patrón 3: Passthrough TLS

El proxy opera a nivel TCP y no descifra el tráfico; simplemente redirige los bytes cifrados al backend. Esta es la opción más simple desde el punto de vista del proxy, pero priva de la capacidad de analizar el tráfico gRPC, agregar encabezados o realizar balanceo a nivel de solicitudes.

Balanceo de carga para streams gRPC: características y soluciones

El balanceo de carga para gRPC es una tarea no trivial, y aquí está el porqué. En HTTP/1.1, cada solicitud es una conexión TCP separada (o una conexión del pool), y el balanceador distribuye fácilmente las solicitudes entre los backends. En HTTP/2, una conexión TCP multiplexa múltiples flujos; si el balanceador opera a nivel TCP, todos los flujos de una conexión irán a un solo backend.

Para gRPC, esto significa que si un cliente establece una conexión HTTP/2 y envía 100 solicitudes RPC a través de ella, con el balanceo TCP, todas las 100 solicitudes serán manejadas por una instancia del backend. Los otros backends estarán inactivos. La solución es el balanceo a nivel de flujos HTTP/2 (balanceo L7).

Algoritmos de balanceo para gRPC

Algoritmo Adecuado para gRPC Comentario
Round Robin ✅ Sí Bueno para RPC unarios con tiempos de procesamiento aproximadamente iguales
Least Connection ✅ Mejor opción Considera flujos activos, distribuye la carga uniformemente
Random ⚠️ Condicional Funciona bien con un gran número de solicitudes, es desigual con pocas
IP Hash ❌ Malo Vincula al cliente a un solo backend, no tiene sentido en L7
Pick First (integrado en gRPC) ❌ No para producción Todas las solicitudes van al primer servidor disponible

Balanceo del cliente en gRPC

gRPC soporta balanceo de carga del lado del cliente; el cliente decide a qué servidor enviar la solicitud. Esto permite evitar el problema de la multiplexión TCP. Para el descubrimiento de servicios se utiliza DNS con múltiples registros A o resolvers especiales (Consul, etcd). Ejemplo de configuración de balanceo del cliente en Go:

import (
    "google.golang.org/grpc"
    "google.golang.org/grpc/balancer/roundrobin"
)

// Cliente con balanceo round-robin a través de DNS
conn, err := grpc.Dial(
    "dns:///grpc-service.internal:50051",
    grpc.WithDefaultServiceConfig(
        `{"loadBalancingConfig": [{"round_robin":{}}]}`
    ),
    grpc.WithTransportCredentials(creds),
)

// Cliente con least-connection (disponible en gRPC >= 1.58)
conn, err := grpc.Dial(
    "dns:///grpc-service.internal:50051",
    grpc.WithDefaultServiceConfig(
        `{"loadBalancingConfig": [{"least_request":{}}]}`
    ),
    grpc.WithTransportCredentials(creds),
)

Proxies externos para gRPC: cuándo y por qué son necesarios en microservicios

Además de los componentes de proxy internos (Nginx, Envoy, HAProxy), en una arquitectura de microservicios a veces surge la necesidad de utilizar proxies externos, por ejemplo, para enrutar tráfico a través de regiones específicas, eludir restricciones de red o aislar conexiones salientes. Analicemos los principales escenarios.

Escenario 1: Microservicios geográficamente distribuidos

Si tus microservicios están ubicados en diferentes regiones (por ejemplo, algunos en Europa, otros en EE. UU.) y necesitas controlar a través de qué dirección IP se establecen las conexiones gRPC entre regiones, los proxies externos pueden ayudar a organizar un enrutamiento predecible. Para tales tareas, son adecuados los proxies de centros de datos; proporcionan direcciones IP estables y alta velocidad de conexión, lo cual es crítico para gRPC debido a su sensibilidad a la latencia.

Escenario 2: Aislamiento del tráfico saliente

En algunos entornos corporativos, todo el tráfico saliente debe pasar a través de un proxy corporativo. Para los clientes gRPC que necesitan acceder a servicios gRPC externos (por ejemplo, Google Cloud APIs que utilizan gRPC), esto crea complicaciones. La solución es configurar un túnel CONNECT a través del proxy corporativo.

Ejemplo de configuración de un cliente gRPC para trabajar a través de un proxy HTTP CONNECT en Go:

import (
    "net"
    "net/http"
    "golang.org/x/net/proxy"
    "google.golang.org/grpc"
)

// Uso de un proxy SOCKS5 para gRPC
proxyDialer, _ := proxy.SOCKS5(
    "tcp",
    "proxy.example.com:1080",
    &proxy.Auth{User: "user", Password: "pass"},
    proxy.Direct,
)

conn, err := grpc.Dial(
    "grpc-service.example.com:443",
    grpc.WithContextDialer(func(ctx context.Context, addr string) (net.Conn, error) {
        return proxyDialer.Dial("tcp", addr)
    }),
    grpc.WithTransportCredentials(creds),
)

Escenario 3: Pruebas y desarrollo

Al desarrollar microservicios, a menudo es necesario probar el comportamiento de los servicios desde diferentes entornos de red: verificar cómo responde un servicio con alta latencia o probar la lógica geo-dependiente. Para tales tareas, son convenientes los proxies residenciales, que permiten simular solicitudes desde regiones específicas con direcciones IP reales de usuarios domésticos.

Errores comunes al configurar gRPC a través de un proxy y su solución

Analicemos los problemas más comunes que enfrentan los desarrolladores al configurar gRPC a través de un proxy y las formas específicas de resolverlos.

Error 1: "transport: received the unexpected content-type"

Síntoma:

El cliente recibe un error transport: received the unexpected content-type "text/html; charset=utf-8"

Causa: El proxy devolvió una página HTML con un error (por ejemplo, 502 Bad Gateway) en lugar de una respuesta gRPC. El cliente gRPC no sabe manejar HTML y genera este error.
Solución: Asegúrate de que el backend esté disponible. Agrega manejo de errores gRPC a nivel de proxy (como se muestra en el ejemplo de Nginx anterior con el bloque error502grpc).

Error 2: La conexión se cierra después de 60-120 segundos

Síntoma:

Las conexiones gRPC de streaming se cierran inesperadamente con el error UNAVAILABLE: transport is closing después de un intervalo de tiempo aproximadamente igual.

Causa: El proxy cierra las conexiones inactivas por tiempo de espera. Valores clásicos: AWS ELB — 60 segundos, Nginx por defecto — 60 segundos.
Solución 1: Aumenta los tiempos de espera en el proxy (como se muestra en los ejemplos anteriores).
Solución 2: Configura keepalive en el lado del cliente gRPC:

import "google.golang.org/grpc/keepalive"

kaParams := keepalive.ClientParameters{
    Time:                30 * time.Second, // Ping cada 30 segundos
    Timeout:             10 * time.Second, // Esperar respuesta 10 segundos
    PermitWithoutStream: true,             // Hacer ping incluso sin RPC activos
}

conn, err := grpc.Dial(
    "grpc-service:50051",
    grpc.WithKeepaliveParams(kaParams),
    grpc.WithTransportCredentials(creds),
)

Error 3: HTTP/2 no se negocia (ALPN failure)

Síntoma:

Error transport: failed to dial: context deadline exceeded o no application protocol

Causa: El proxy o el equipo intermedio no soporta ALPN (Application-Layer Protocol Negotiation) o h2 no está habilitado en la lista de protocolos soportados.
Solución: Asegúrate de que en la configuración TLS del proxy se especifique explícitamente el protocolo h2: alpn h2,http/1.1 (HAProxy) o listen 443 ssl http2 (Nginx).

Error 4: El streaming se congela — no llegan datos

Síntoma:

El streaming del servidor funciona en el backend (visible en los logs), pero el cliente no recibe mensajes hasta que el stream se completa.

Causa: El proxy almacena en búfer la respuesta y solo la envía al cliente después de que se completa. Problema típico para proxies configurados con proxy_buffering on.
Solución para Nginx:

location / {
    grpc_pass grpc://backend;
    
    # Desactivar el almacenamiento en búfer para streams gRPC
    grpc_buffer_size 0;
    
    # O para proxy_pass normal:
    proxy_buffering off;
    proxy_cache off;
}

Lista de verificación de diagnóstico para gRPC a través de un proxy

✅ Lista de verificación: diagnóstico de proxy gRPC

  • El proxy soporta HTTP/2 (verifica con curl --http2 -v)
  • ALPN h2 está habilitado en las configuraciones TLS del proxy
  • Los tiempos de espera del cliente y del servidor están establecidos en al menos 300 segundos
  • El almacenamiento en búfer de respuestas está desactivado para los endpoints gRPC
  • Se ha configurado keepalive gRPC en el cliente (Time: 30s, Timeout: 10s)
  • Los errores del backend se devuelven como estados gRPC, no como códigos HTTP
  • El health check utiliza el Protocolo de Verificación de Salud gRPC
  • El balanceo funciona en L7 (streams HTTP/2), no en L4 (TCP)

Conclusión

Configurar gRPC a través de un proxy requiere entender las diferencias clave entre HTTP/2 y HTTP/1.1: multiplexión de flujos, conexiones de larga duración, trailers de HTTP/2 y negociación ALPN. Los proxies HTTP clásicos sin configuraciones especiales no funcionan con gRPC; se necesita un proxy L7 que soporte HTTP/2 (Nginx 1.13.10+, Envoy, HAProxy 1.9.2+) o tunelización TCP CONNECT.

Para entornos de producción, se recomienda Envoy; fue diseñado específicamente para arquitecturas de microservicios, tiene soporte nativo para gRPC, puede recopilar métricas detalladas para cada método RPC y es la base de la mayoría de las soluciones de service mesh. Nginx es una buena opción si ya lo estás utilizando como API Gateway y deseas agregar soporte para gRPC sin introducir un nuevo componente. HAProxy es adecuado si la rendimiento es crítica y ya estás familiarizado con su configuración.

Independientemente del proxy elegido, tres reglas permanecen constantes: aumenta los tiempos de espera para streams de larga duración, desactiva el almacenamiento en búfer de respuestas y configura keepalive en el cliente. Estas tres configuraciones resuelven el 80% de los problemas con gRPC a través de un proxy.

Si en tu arquitectura los servicios gRPC deben interactuar a través de redes externas o necesitas enrutar tráfico a través de regiones específicas, considera los proxies de centros de datos; proporcionan direcciones IP estables, baja latencia y alta capacidad de procesamiento, lo cual es especialmente importante para gRPC debido a su protocolo binario y sensibilidad a la latencia.

```