# ¿Por qué puedo conectarme por SSH usando IPv4 si el socket aparece como tcp6? El misterio del dual-stack en Linux

- Link: https://binariocero.com/articulos/por-que-puedo-conectarme-por-ssh-usando-ipv4-si-el-socket-aparece-como-tcp6-el-misterio-del-dual-stack-en-linux
- Published: 2026-07-11T09:24:49+02:00
- Author: Juan José Fernández Durán

![Loading](https://binariocero.com/wp-content/plugins/page-views-count/ajax-loader-
2x.gif)

Si alguna vez has ejecutado `netstat -ptnoua | grep LISTEN` en un servidor Linux,
seguramente te habrá llamado la atención algo como esto:

    ```wp-block-code
    tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      179/nginx: master p
    tcp        0      0 0.0.0.0:443             0.0.0.0:*               LISTEN      179/nginx: master p
    tcp6       0      0 :::22                   :::*                    LISTEN      1/init
    ```

Nginx aparece escuchando en `tcp` (IPv4 puro), mientras que SSH aparece en `tcp6`.
Y sin embargo, te conectas sin problema por `ssh usuario@192.168.x.x`, una dirección
claramente IPv4. ¿Cómo es posible que un socket "IPv6" acepte tráfico IPv4?

### La clave: los sockets dual-stack

La respuesta está en un comportamiento del kernel de Linux conocido como **direcciones
IPv4-mapeadas sobre IPv6** (_IPv4-mapped IPv6 addresses_).

Cuando un proceso abre un socket en modo `AF_INET6` y hace _bind_ a la dirección`::`(
el equivalente IPv6 de "todas las interfaces"), ese socket puede comportarse de 
dos maneras distintas, según un parámetro del kernel:

    ```wp-block-code
    sysctl net.ipv6.bindv6only
    ```

 * **`0`** (valor por defecto en Debian, Ubuntu y la mayoría de distros): el socket
   IPv6 es **dual-stack**, es decir, acepta tráfico tanto IPv6 como IPv4 en el mismo
   puerto.
 * **`1`**: el socket es _solo_ IPv6; el tráfico IPv4 necesitaría su propio socket
   independiente.

Con `bindv6only=0`, cuando llega una conexión IPv4, el kernel la traduce internamente
a una dirección con el prefijo `::ffff:`, por ejemplo `::ffff:192.168.1.10`, y la
entrega al mismo socket que está escuchando en `::`. Por eso SSH, aunque aparece
como `tcp6`, es perfectamente capaz de aceptar tu conexión IPv4: en realidad nunca
ha dejado de escuchar IPv4, simplemente lo hace a través del "envoltorio" IPv6.

### ¿Por qué unos servicios aparecen como `tcp` y otros como `tcp6`?

Esto no depende de una regla global del sistema, sino de cómo cada aplicación decide
crear sus sockets:

 * **Nginx** hace _bind_ explícito a `0.0.0.0`, por lo que solo escucha IPv4 (`tcp`).
   Si quisiera escuchar también en IPv6, necesitarías añadir una directiva como `
   listen [::]:80;` en su configuración.
 * **OpenSSH** (gestionado aquí vía _socket activation_ de `init`/systemd) hace 
   _bind_ por defecto a `::`, y al no forzar `IPV6_V6ONLY`, hereda el comportamiento
   dual-stack del kernel.

Es decir: el protocolo que ves en `netstat` refleja la decisión de diseño de cada
aplicación, no una limitación real de conectividad.

### Cómo comprobarlo tú mismo

Para confirmar el valor del parámetro del kernel:

    ```wp-block-code
    sysctl net.ipv6.bindv6only
    ```

Y para verificar que una conexión IPv4 realmente está llegando "disfrazada" de IPv6:

    ```wp-block-code
    ss -tnp | grep :22
    ```

Verás la IP de origen representada como `::ffff:tu.ip.v4.origen`, prueba directa
de que el kernel está haciendo la traducción.

### Por qué esto importa en producción

Más allá de la curiosidad técnica, este comportamiento tiene implicaciones prácticas
reales:

 * **Firewalls e `iptables`**: si filtras tráfico solo con reglas IPv4 (`iptables`)
   pero el servicio escucha en un socket dual-stack, una conexión IPv4 mapeada podría
   evaluarse bajo las reglas de `ip6tables` en lugar de `iptables`, dependiendo 
   de cómo la trate el kernel. Es fácil abrir una brecha sin darte cuenta si solo
   se protege una de las dos pilas.
 * **Reverse proxies (Nginx/Apache)**: si en algún momento configuras un `listen[::]:
   80;` sin especificar `ipv6only=on|off` de forma explícita, puedes terminar con
   un comportamiento dual-stack no intencionado, lo cual puede complicar el logging
   de IPs reales o el filtrado por IP de origen (por ejemplo, en configuraciones
   donde solo se permite tráfico desde rangos de Cloudflare, o servicios definidos).
 * **Auditoría y trazabilidad**: si dependes de logs para saber "quién se conectó
   desde dónde", conviene saber que una IP IPv4 puede aparecer registrada como `::
   ffff:x.x.x.x` en ciertos servicios, y las herramientas de análisis deben tenerlo
   en cuenta.

### Conclusión

Ver `tcp` en un servicio y `tcp6` en otro no significa que uno esté mal configurado
y el otro no. Simplemente refleja si la aplicación abrió el socket en modo IPv4 
puro o en modo IPv6 dual-stack. Gracias a `net.ipv6.bindv6only=0`, un socket `tcp6`
escuchando en `::` puede aceptar conexiones IPv4 con total normalidad, traduciéndolas
mediante direcciones mapeadas. Entender este mecanismo es clave para configurar 
correctamente firewalls, reverse proxies y reglas de filtrado en entornos donde 
conviven IPv4 e IPv6.
