SSH a través de un relé de flujo de NGINX sin romperlo
Una cabecera PROXY es un mensaje para un receptor que la espera, y un registro DNS con proxy no es un relé TCP. Corrija el borde y el origen por separado.
El SSH público a través de un relé falla en dos fronteras no relacionadas, y arreglar una suele revelar la otra. O el borde devuelve una respuesta HTTP en lugar de transportar un flujo, o el relé antepone una cabecera que el backend nunca aceptó leer. Tres condiciones deciden cuál de las dos está viendo: a qué apunta realmente el registro público, qué envía el bloque de flujo hacia el origen y qué nombre verifica el cliente SSH. Trátelas en ese orden.
Dos fronteras, dos síntomas
Un fallo en el borde se parece a una página de estado HTTP que llega donde debería estar un banner SSH. La conexión se completa a nivel TCP y los primeros bytes son texto que pertenece a un servidor web. Eso es una discrepancia de protocolo, no un paquete perdido. También le dice que el borde sirve deliberadamente otra cosa para ese nombre.
Un fallo en el origen se ve distinto. La conexión llega a sshd, sshd lee bytes que no son el intercambio de identificación que espera, y la sesión muere o se cuelga sin ofrecer nunca una clave.
Es fácil confundir ambos porque los dos producen un cliente que conecta y luego falla. La primera respuesta de protocolo que recibe es lo que los distingue, y por eso pertenece al ticket.
La cabecera PROXY es un mensaje, no un interruptor
En el módulo de flujo, proxy_protocol on indica a NGINX que escriba una cabecera de protocolo PROXY hacia el origen antes de la carga útil. No habilita el proxy TCP y no tiene nada que ver con el cifrado SSH. Es una nota dirigida al receptor. Si el receptor no puede leerla, se convierte en basura al inicio del flujo.
El servicio receptor tiene que consumir esa cabecera de forma deliberada. Un sshd normal espera el intercambio de identificación SSH como primeros bytes de la conexión, así que un preámbulo inesperado rompe el saludo de una manera que parece un servidor corrupto.
stream {
server {
listen 2222;
proxy_pass 10.77.0.20:22;
}
}
Ese fragmento es todo un relé funcional para un backend alcanzable a través de la VPN. No aparece ninguna directiva PROXY porque el receptor no habla el protocolo, y esa ausencia es una decisión, no una omisión.
Separe también las dos direcciones. Poner proxy_protocol en la línea listen significa que NGINX espera que su propio cliente envíe primero la cabecera; habilitarlo para un cliente SSH corriente produce exactamente lo contrario del problema que la gente suele intentar resolver.
A qué llega realmente el nombre de host público
Una página HTTP 400 en una sesión SSH es evidencia sobre el borde. Algo en el camino habla HTTP, y ninguna cantidad de trabajo con la clave o la cuenta en el backend lo cambiará. Deje de cambiar el backend hasta que el borde esté resuelto.
El registro con proxy ordinario de Cloudflare transporta su tráfico HTTP y HTTPS compatible en puertos definidos. Activar el proxy para un nombre de host no crea un relé TCP general, y mover SSH al puerto 443 no lo convierte en HTTPS.
Existen dos direcciones viables. Un registro solo DNS puede apuntar a su propio relé TCP, lo que deja la decisión sobre la exposición en sus manos. Como alternativa, use un producto que documente explícitamente el transporte de SSH, con su propia configuración de origen, cliente y acceso.
Elija lo que elija, el relé sigue necesitando una ruta VPN hacia el backend, una conexión permitida y un camino de vuelta. La ruta no es opcional porque el lado público funcione.
Mantenga estable la identidad SSH mediante un alias
Los usuarios suelen querer seguir escribiendo su nombre de host actual. Un alias en la configuración de SSH puede hacerlo sin cambiar nada del relé, y mantiene el cambio en el lado del cliente, donde corresponde.
Host git.example.com
HostName ssh-entry.example.net
Port 2222
User git
HostKeyAlias git.example.com
Sin HostKeyAlias, OpenSSH busca la clave del host bajo el nombre al que se conecta, así que la entrada se mueve y cada usuario ve un aviso de clave cambiada en el primer uso. Establecer el alias mantiene estables las entradas de known-hosts mientras la conexión sigue yendo al relé.
Este alias solo se aplica a remotos basados en SSH. Una URL de Git por HTTPS no lo consulta, y la URL típica del remoto debe coincidir con el transporte que realmente aprovisionó.
Lo que no debe hacer es desactivar la verificación de la clave del host para que el aviso desaparezca. Eso elimina una protección que la reparación del reenvío nunca pidió tocar, y convierte una molestia estética en una exposición real.
Verifique cada salto por separado
Pruebe el oyente público por sí solo, luego la conexión del relé al backend desde el host del relé, y solo después el cliente de extremo a extremo. Cada una de esas tres pruebas descarta una capa distinta. Saltarse una significa que los dos resultados restantes no se pueden interpretar. Tres observaciones, tres lugares donde puede vivir un fallo.
Conserve la dirección de destino, el puerto y los primeros bytes de protocolo con cada resultado. Ese trío distingue un error de borde de un problema de enrutamiento VPN y de un fallo de autenticación SSH, y es la diferencia entre una reparación y una conjetura.
Por último, documente la exposición prevista. Un relé TCP que transporta SSH hacia un backend privado es una apertura deliberada en el perímetro público, y merece la misma revisión que cualquier otro servicio entrante.