WireGuard «Handshake did not complete»: localizar problemas de NAT, puertos y tiempo

¿Te fue útil?

WireGuard suele ser aburrido—en el mejor sentido. Hasta que no lo es. Despliegas un túnel, la interfaz sube y luego el estado se queda ahí como un pez muerto: latest handshake: (none) o “Handshake did not complete.” Mientras tanto, tu teléfono de guardia empieza a inventar nuevas formas de vibrar.

Esta falla es engañosamente simple: un extremo no puede completar la danza criptográfica con el otro. El truco es que la causa raíz a menudo no es criptografía. Es estado NAT que expira, un puerto UDP bloqueado silenciosamente, una ruta apuntando al vacío, la fragmentación por MTU que convierte paquetes en confeti, o la deriva de reloj haciendo que la protección contra replay haga demasiado bien su trabajo.

Qué significa realmente «handshake» (y qué no)

El handshake de WireGuard es un pequeño y rápido intercambio de paquetes UDP que establece claves de sesión efímeras. Si se completa, verás una marca de tiempo en “latest handshake” y bytes fluyendo. Si no, la interfaz aún puede parecer “activa” porque es solo una interfaz; WireGuard no hace “conectado” en el sentido de TCP.

La última frase importa porque ahí ocurren muchos errores de diagnóstico. La gente ve que wg0 existe y asume conectividad. WireGuard no está prometiendo nada; está ofreciendo una oportunidad.

Además: “handshake did not complete” no es un solo error. Es una clase de fallos:

  • Los paquetes nunca salen del cliente (firewall local, tabla de rutas equivocada, nombre de host del endpoint incorrecto).
  • Los paquetes salen pero nunca llegan (ISP bloquea UDP, firewall ascendente, reenvío de puerto equivocado, CGNAT).
  • Los paquetes llegan pero la respuesta no puede volver (enrutamiento asimétrico, desajuste de mapeo NAT, rp_filter, firewall de salida).
  • El handshake se completa pero el tráfico falla (AllowedIPs, enrutamiento, MTU, DNS). Esto parece similar si solo observas “ping”.
  • El handshake falla de forma intermitente (timeout NAT, cambio de endpoint por roaming, falta de keepalive).
  • Handshake rechazado (claves incorrectas, peer obsoleto, problemas de tiempo/replay). Menos común, pero sucede.

Una cita, porque sigue siendo el mejor consejo operativo en todo este ámbito: La esperanza no es una estrategia. — Gene Kranz. Vas a medir, no a adivinar.

Guion rápido de diagnóstico (verificar primero/segundo/tercero)

Si solo haces una cosa de este artículo, haz esto en orden. Reducirá tu tiempo de resolución de “arruina mi tarde” a “ligeramente molesto”.

Primero: demuestra la alcanzabilidad UDP al puerto del servidor

  1. En el servidor, verifica que esté escuchando en el puerto que crees (wg show y ss -lunp).
  2. En el servidor, ejecuta tcpdump en la interfaz pública para ese puerto UDP.
  3. Desde el cliente, inicia tráfico (incluso un solo ping a través del túnel, o simplemente levanta la interfaz).
  4. Si tcpdump del servidor no ve nada: está aguas arriba de WireGuard (NAT/reenvío de puertos/firewall/ISP/CGNAT/IP equivocada).

Segundo: si los paquetes llegan, demuestra que el servidor responde y el cliente recibe las respuestas

  1. Mantén tcpdump en el servidor: ¿ves UDP saliente de vuelta al cliente?
  2. Ejecuta tcpdump en el cliente: ¿ves UDP entrante desde el servidor?
  3. Si el servidor responde pero el cliente nunca lo ve: NAT en el medio, ruta de retorno o caída por firewall stateful.

Tercero: si los handshakes ocurren pero el tráfico falla, depura AllowedIPs, rutas y MTU

  1. wg show debería mostrar handshake reciente y contadores en aumento.
  2. ip route get para IPs de destino debería apuntar a wg0 cuando corresponda.
  3. Haz ping con DF puesto (o clamp de MSS) para detectar agujeros MTU.

Eso es todo. Estás buscando el primer eslabón roto en la cadena: “el cliente emite UDP → el servidor recibe UDP → el servidor responde → el cliente recibe la respuesta → se establecen claves → las rutas envían tráfico al túnel → el tráfico sobrevive al MTU”.

Hechos y contexto para tus discusiones con compañeros

  • WireGuard fue diseñado para ser minimalista. La base de código es famosamente pequeña comparada con pilas VPN antiguas, lo que reduce “lo desconocido” durante incidentes.
  • Usa UDP por diseño. Evita el colapso de TCP sobre TCP y funciona bien con roaming, pero significa que debes pensar en términos de alcanzabilidad y estado NAT.
  • El handshake se basa en Noise. La familia de protocolos es un enfoque moderno para el intercambio de claves autenticado que busca simplicidad y propiedades fuertes.
  • “AllowedIPs” es tanto ACL como política de enrutamiento. Es una elección de diseño deliberada: es poderosa y una fuente común de errores.
  • WireGuard no hace liveness como la gente espera. No hay estado “conectado”; envía iniciaciones de handshake cuando tiene tráfico que enviar.
  • PersistentKeepalive existe principalmente por NAT. Si alguna vez gritaste con una Wi‑Fi de hotel, esta opción es la forma educada de WireGuard de tocar la tabla NAT cada N segundos.
  • Muchos routers de consumo tienen timeouts UDP cortos. No es inusual ver mapeos expirar en 30–120 segundos cuando están inactivos.
  • El Carrier-grade NAT es ahora normal. Muchas conexiones “públicas” no son realmente públicas desde la perspectiva entrante. Ningún reenvío de puertos puede arreglar eso.
  • La protección contra replay es sensible al tiempo. Si los relojes están muy desajustados, algunos paquetes legítimos pueden ser tratados como replays. La seguridad hace su trabajo; tú eres el que viaja en el tiempo.

Broma 1/2: NAT es como el helpdesk corporativo: te olvida en cuanto dejas de enviar tickets.

Modelo mental práctico: paquetes, estado y tiempo

Piénsalo como cuatro cosas que deben ser verdaderas:

  1. Direcciones correctas: el cliente conoce el endpoint del servidor (IP:puerto), el servidor sabe cómo responder.
  2. Alcanzabilidad UDP bidireccional: no es “ping funciona”, sino “los paquetes UDP atraviesan en ambas direcciones”.
  3. El estado dura lo suficiente: NAT y firewalls mantienen el mapeo/estado lo suficiente para el handshake y el tráfico subsecuente.
  4. La identidad criptográfica coincide: claves públicas y AllowedIPs alineadas; la protección contra replay no se dispara por rarezas temporales.

Este modelo es intencionalmente soso. Quieres modelos sosos en producción. Los modelos elegantes son para charlas de conferencia y gente que no carga con un pager.

Tareas prácticas: comandos, salidas y decisiones (12+)

Estas son las tareas que ejecuto en orden al depurar “handshake did not complete.” Cada una incluye qué deberías esperar, qué significa una salida rara y qué decisión tomar a continuación.

Task 1: Confirmar que WireGuard realmente está corriendo y en qué puerto escucha

cr0x@server:~$ sudo wg show
interface: wg0
  public key: 8x3u...redacted...
  listening port: 51820

peer: 7p1Q...redacted...
  endpoint: (none)
  allowed ips: 10.10.0.2/32
  latest handshake: (none)
  transfer: 0 B received, 0 B sent

Qué significa: tienes un puerto escuchando (bien). “endpoint: (none)” es normal en servidores; WireGuard aprende endpoints de cliente dinámicamente.

Decisión: si el puerto de escucha no es el que configuraste, estás depurando el puerto equivocado. Arregla la configuración primero y luego continúa.

Task 2: Verificar que el SO escucha en UDP como se espera

cr0x@server:~$ sudo ss -lunp | grep 51820
UNCONN 0      0            0.0.0.0:51820      0.0.0.0:*    users:(("wg",pid=1132,fd=6))

Qué significa: el kernel está enlazado en 0.0.0.0:51820, así que debería aceptar paquetes a cualquier dirección local.

Decisión: si no lo ves, WireGuard no está activo (o enlazado a otra dirección). Arregla el servicio primero; no toques NAT todavía.

Task 3: Comprueba el endpoint configurado en el cliente (te sorprendería)

cr0x@client:~$ sudo wg show
interface: wg0
  public key: dE5...redacted...
  listening port: 48712

peer: 8x3u...redacted...
  endpoint: 203.0.113.10:51820
  allowed ips: 10.10.0.0/24
  latest handshake: (none)
  transfer: 0 B received, 0 B sent
  persistent keepalive: 25

Qué significa: el cliente piensa que el servidor está en 203.0.113.10:51820. Keepalive está establecido (bueno para NAT).

Decisión: si la IP del endpoint es incorrecta (IP pública antigua, error tipográfico, DNS equivocado), arréglalo y vuelve a probar antes de hacer cualquier otra cosa.

Task 4: Demuestra que el servidor recibe paquetes UDP en el puerto de WireGuard

cr0x@server:~$ sudo tcpdump -ni eth0 udp port 51820
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes

Ahora levanta el túnel del cliente o genera tráfico.

cr0x@client:~$ sudo wg-quick up wg0
[#] ip link add wg0 type wireguard
[#] wg setconf wg0 /dev/fd/63
[#] ip address add 10.10.0.2/32 dev wg0
[#] ip link set mtu 1420 up dev wg0
[#] ip route add 10.10.0.0/24 dev wg0
[#] resolvconf -a wg0 -m 0 -x

Si los paquetes están llegando, tcpdump debería mostrar algo como:

cr0x@server:~$ sudo tcpdump -ni eth0 udp port 51820
12:22:31.104512 IP 198.51.100.23.48712 > 203.0.113.10.51820: UDP, length 148
12:22:31.104889 IP 203.0.113.10.51820 > 198.51.100.23.48712: UDP, length 92

Qué significa: los paquetes llegan al servidor y el servidor responde. Si el handshake aún no se completa, estás en territorio de “identidad criptográfica / AllowedIPs / problemas de tiempo”.

Decisión:

  • Sin líneas entrantes en absoluto: esto es aguas arriba (NAT/firewall/ISP/reenvío de puerto). Ve allí a continuación.
  • Entradas pero sin respuesta saliente: firewall del servidor o enrutamiento por política está bloqueando las respuestas.
  • Ambas direcciones presentes: pasa a claves/tiempo/AllowedIPs.

Task 5: Revisa reglas iptables/nftables que puedan descartar UDP/51820

cr0x@server:~$ sudo nft list ruleset | sed -n '1,120p'
table inet filter {
  chain input {
    type filter hook input priority 0; policy drop;
    ct state established,related accept
    iif "lo" accept
    tcp dport 22 accept
    udp dport 51820 accept
    counter drop
  }
}

Qué significa: UDP 51820 está explícitamente permitido. Genial.

Decisión: si no ves una regla allow (o la política es drop), añádela. Si la política es accept pero aún nada llega en tcpdump, el firewall no es tu problema.

Task 6: Verifica forwarding IP si es site-to-site o esperas acceso LAN

cr0x@server:~$ sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 0

Qué significa: el forwarding está apagado. El handshake aún puede funcionar, pero el tráfico enrutado no.

Decisión: si necesitas enrutar entre wg0 y otra interfaz, habilita forwarding y configura firewall/NAT acorde.

Task 7: Revisa rp_filter (el pequeño vigilante del enrutamiento asimétrico)

cr0x@server:~$ sysctl net.ipv4.conf.all.rp_filter net.ipv4.conf.eth0.rp_filter
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.eth0.rp_filter = 1

Qué significa: el filtrado de ruta inversa estricto está activado. Si las respuestas salen por una interfaz diferente a la que el kernel espera, los paquetes pueden ser descartados.

Decisión: en configuraciones multihomed o con policy routing, pon rp_filter a 2 (loose) o ajusta el enrutamiento para que la ruta de retorno coincida. No lo cambies a ciegas en cajas expuestas a Internet a menos que entiendas por qué.

Task 8: Confirma que el servidor tiene la IP pública correcta y la ruta de vuelta al cliente

cr0x@server:~$ ip route get 198.51.100.23
198.51.100.23 via 203.0.113.1 dev eth0 src 203.0.113.10 uid 0
    cache

Qué significa: el servidor responderá por eth0 usando 203.0.113.10. Bien.

Decisión: si la ruta apunta a un sitio inesperado, estás depurando enrutamiento asimétrico. Arregla el enrutamiento antes de tocar las configuraciones de WireGuard.

Task 9: Confirma que las claves coinciden con lo que crees (sin filtrar secretos)

cr0x@server:~$ sudo wg show wg0 public-key
8x3u...redacted...
cr0x@client:~$ sudo wg show wg0 peers
8x3u...redacted...

Qué significa: el cliente apunta a la clave pública del servidor.

Decisión: si las claves no coinciden, el handshake nunca se completará. Arregla las claves, reinicia la interfaz, vuelve a probar con tcpdump.

Task 10: Revisa la hora del sistema y la sincronización NTP en ambos extremos

cr0x@server:~$ timedatectl
               Local time: Sat 2025-12-27 12:26:02 UTC
           Universal time: Sat 2025-12-27 12:26:02 UTC
                 RTC time: Sat 2025-12-27 12:26:01
                Time zone: Etc/UTC (UTC, +0000)
System clock synchronized: yes
              NTP service: active
          RTC in local TZ: no

Qué significa: el reloj del servidor es razonable y está sincronizado.

Decisión: si un extremo no está sincronizado y la hora está muy desviada, arregla NTP antes de seguir persiguiendo fantasmas.

Task 11: Observa transiciones de estado del handshake y contadores en tiempo real

cr0x@client:~$ watch -n 1 sudo wg show wg0
Every 1.0s: sudo wg show wg0

interface: wg0
  public key: dE5...redacted...
  listening port: 48712

peer: 8x3u...redacted...
  endpoint: 203.0.113.10:51820
  allowed ips: 10.10.0.0/24
  latest handshake: 4 seconds ago
  transfer: 3.12 KiB received, 2.98 KiB sent
  persistent keepalive: 25

Qué significa: el handshake ahora se completa; los contadores se mueven.

Decisión: si la marca de tiempo del handshake se actualiza pero tus aplicaciones aún fallan, deja de culpar al handshake. Pasa a enrutamiento/AllowedIPs/MTU/DNS.

Task 12: Valida AllowedIPs y decisiones de enrutamiento con ip route get

cr0x@client:~$ ip route get 10.10.0.1
10.10.0.1 dev wg0 src 10.10.0.2 uid 1000
    cache

Qué significa: el tráfico a 10.10.0.1 irá por wg0.

Decisión: si sale por tu interfaz por defecto, tus AllowedIPs/rutas no están instaladas como piensas.

Task 13: Detecta agujeros MTU con ping DF

cr0x@client:~$ ping -M do -s 1380 -c 3 10.10.0.1
PING 10.10.0.1 (10.10.0.1) 1380(1408) bytes of data.
1388 bytes from 10.10.0.1: icmp_seq=1 ttl=64 time=32.1 ms
1388 bytes from 10.10.0.1: icmp_seq=2 ttl=64 time=31.8 ms
1388 bytes from 10.10.0.1: icmp_seq=3 ttl=64 time=32.0 ms

--- 10.10.0.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms

Qué significa: al menos payloads de 1380 bytes sobreviven; el MTU probablemente está bien.

Decisión: si obtienes “Frag needed and DF set” o pérdida silenciosa en tamaños mayores, ajusta MTU (comúnmente 1420, 1380, o menor dependiendo de encapsulación y camino).

Task 14: Demuestra si el servidor está o no detrás de CGNAT (reenvío de puertos no te salvará)

cr0x@server:~$ ip -4 addr show dev eth0 | sed -n '1,5p'
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    inet 100.64.12.34/24 brd 100.64.12.255 scope global eth0
       valid_lft forever preferred_lft forever

Qué significa: 100.64.0.0/10 es un rango de carrier-grade NAT. Tu “servidor” no tiene una IPv4 pública real en su interfaz.

Decisión: deja de intentar reenviar puertos en esa caja; necesitas un endpoint público en otro lugar (VPS, IPv6, diseño con relay, o un plan ISP diferente).

Task 15: Confirma que el servidor ve la IP/puerto de origen del cliente consistentemente (rebinding NAT)

cr0x@server:~$ sudo wg show wg0 | sed -n '1,30p'
interface: wg0
  public key: 8x3u...redacted...
  listening port: 51820

peer: 7p1Q...redacted...
  endpoint: 198.51.100.23:48712
  allowed ips: 10.10.0.2/32
  latest handshake: 12 seconds ago
  transfer: 14.21 KiB received, 13.88 KiB sent

Qué significa: WireGuard ha aprendido el endpoint. Si el endpoint sigue cambiando cada minuto, un dispositivo NAT está rebindando con frecuencia.

Decisión: pon PersistentKeepalive = 25 en el cliente (o en el lado que está detrás de NAT) y considera usar un upstream estable.

NAT y reenvío de puertos: cómo falla en redes reales

NAT es la razón número 1 por la que los handshakes no se completan, y no está cerca en segundo lugar. Eso se debe a que el “endpoint” es un objetivo móvil cuando cualquiera de los dos extremos está detrás de un dispositivo que:

  • reescribe puertos de origen de forma impredecible,
  • expira los mapeos UDP rápidamente,
  • no soporta “hairpin NAT” (NAT loopback),
  • o no es realmente el NAT que necesitas configurar (doble NAT).

Sabe qué lado debe ser alcanzable

WireGuard puede funcionar en varios patrones, pero lo más simple es: el servidor tiene una IP pública y puerto UDP abierto, los clientes se conectan hacia afuera. Esa es la “forma por defecto en Internet”.

Si ambos extremos están detrás de NAT y ninguno tiene mapeo de puerto entrante, estás intentando NAT traversal sin un mediador. A veces funciona por accidente. En producción es donde los accidentes van a morir.

Doble NAT: el segundo router oculto

El doble NAT es común: modem/router del ISP hace NAT, luego tu firewall hace NAT otra vez. Reenvías UDP/51820 en el router interno, te sientes orgulloso y nada funciona porque el router externo lo descarta.

Cómo detectarlo rápido:

  • La IP WAN de tu router está en un rango privado (192.168/10.0/172.16) o CGNAT (100.64/10).
  • tcpdump del servidor no ve nada aunque estés seguro de haber reenviado el puerto.

Timeouts NAT: por qué funciona 30 segundos y luego muere

Una “conexión” UDP es una ilusión de NAT. Cuando un cliente envía UDP, el dispositivo NAT crea un mapeo (src IP:src port → public IP:public port). Ese mapeo expira cuando está inactivo. Si expira, la respuesta del servidor va a un vacío. WireGuard puede recuperarse, pero solo cuando tráfico nuevo dispara un nuevo handshake. Los usuarios llaman a esto “se desconecta al azar”. Los SREs lo llaman “inactividad predecible”.

La solución suele ser PersistentKeepalive en el lado detrás de NAT. No en el servidor público. En el lado que necesita mantener el agujero abierto.

Reenvío de puertos: las tres reglas que la gente ignora

  1. Reenvía UDP, no TCP. He visto toggles “TCP/UDP” que por defecto seleccionan solo TCP. Eso es un mal día.
  2. Reenvía a la IP interna correcta. Los cambios DHCP son cómo creas interrupciones intermitentes y culpas “a Internet”.
  3. No reenvíes a un host que también ejecute otro servicio UDP en ese puerto. Suena obvio; es menos obvio cuando ejecutas múltiples túneles y copias/pegas configuraciones.

Firewalls y filtrado UDP: demostrar lo negativo

Los firewalls son el segundo culpable más común porque las caídas UDP no te dan la cortesía de un error. Te dan silencio, que tu cerebro interpreta como misterio.

Usa tcpdump como tu suero de la verdad

Si el servidor nunca ve paquetes en UDP/51820, deja de editar configuraciones de WireGuard. Tienes un problema de alcanzabilidad de red. La herramienta correcta es captura de paquetes. Todo lo demás es ambientación.

Firewalls stateful y mitos de “related/established”

Muchas políticas de firewall permiten paquetes entrantes solo si coinciden con una conexión establecida. UDP es “sin conexión”, pero los firewalls stateful aún lo tratan como flujos con timeouts. Por eso un keepalive ayuda, y por eso el tráfico de retorno puede ser descartado si el firewall no vio la iniciación saliente en la dirección esperada.

Security groups en la nube vs firewall del host

En entornos cloud a menudo tienes:

  • un security group / ACL de red del proveedor,
  • un firewall en el host (nftables/iptables/ufw/firewalld),
  • y a veces un load balancer gestionado que no le gusta UDP a menos que se configure explícitamente.

Escoge una fuente de verdad y documenta. Si “simplemente lo permites en todas partes”, igual lo romperás después—solo que ahora también aumentaste tu radio de impacto.

Broma 2/2: Depurar UDP a través de tres firewalls es como la política de oficina—nadie admite que descartó tu paquete, pero todos tenían una “razón de política”.

Enrutamiento y AllowedIPs: el rompedor silencioso

El AllowedIPs de WireGuard no es un ajuste decorativo. Hace dos trabajos:

  1. Filtrado de entrada: qué IPs origen aceptarás de ese peer.
  2. Enrutamiento de salida: qué IPs de destino se cifran hacia ese peer.

Esto es elegante. También es por qué una sola CIDR equivocada puede hacer desaparecer tráfico sin errores de handshake. A veces el handshake se completa bien, pero no puedes alcanzar nada porque nada se enruta al túnel. Otras veces, el handshake falla porque la IP de túnel del peer no se considera válida para ese peer.

Patrones comunes de AllowedIPs

  • Cliente road warrior: el servidor pone AllowedIPs = 10.10.0.2/32 para ese cliente. El cliente pone AllowedIPs = 0.0.0.0/0, ::/0 si quieres túnel completo, o solo los rangos privados si es split-tunnel.
  • Site-to-site: cada lado anuncia sus LANs locales vía AllowedIPs para que el otro lado enrute hacia ellas. Aquí es donde los solapamientos y colisiones RFC1918 se convierten en tu villano.

Subredes solapadas: el clásico corporativo

Si ambos lados usan 192.168.1.0/24 internamente, puedes establecer un túnel, pero el enrutamiento se vuelve una moneda al aire. La gente entonces “arregla” añadiendo más rutas estáticas hasta que la red parece un mural conspirativo. No. Renumera un lado o usa NAT dentro del túnel deliberadamente y documentalo.

Tiempo y protección contra replay: cuando los relojes arruinan tu día

El tiempo rara vez causa fallos de handshake, pero cuando lo hace, es exasperante porque parece magia. WireGuard incluye protección contra replay: no aceptará paquetes que parezcan replays. Si los relojes están severamente desincronizados o una VM se pausa/recupera con saltos de tiempo, puedes obtener síntomas que parecen “simplemente no hace handshake”, especialmente tras reinstalaciones o snapshots.

Qué hacer en la práctica:

  • Haz que NTP sea aburrido. Usa systemd-timesyncd/chronyd. Asegúrate de que arranque temprano en el boot.
  • En hosts virtualizados, confirma que la sincronización de tiempo del hipervisor no compita con NTP.
  • Si usas snapshots y restores, espera rarezas: una VM restaurada puede tener estado WireGuard antiguo y tiempo antiguo.

Cómo detectar si el tiempo es el problema

Usualmente verás:

  • handshakes que se completan justo después de reboot o sincronización de tiempo, luego fallan más tarde,
  • o fallos después de suspend/resume de la VM,
  • o un reloj del sistema muy equivocado (timedatectl muestra no sincronizado).

MTU y fragmentación: el primo lento del handshake

Hablando estrictamente, los problemas de MTU rompen más a menudo los datos que los handshakes, porque los paquetes de handshake son pequeños. Pero en la vida real, la gente diagnostica “handshake did not complete” porque el túnel “no funciona”, y nunca separan éxito del plano de control del fallo del plano de datos.

Los problemas MTU aparecen como:

  • el handshake funciona, pero transferencias grandes se estancan,
  • algunos sitios funcionan, otros no (clásico agujero PMTUD),
  • SSH funciona, HTTPS cuelga en respuestas grandes,
  • VoIP va entrecortado y todos culpan al codec.

Arréglalo de forma adulta

Empieza con el MTU por defecto de wg-quick (a menudo 1420). Si tienes encapsulación adicional (PPPoE, VLANs, otros túneles), redúcelo. Valida con ping DF, luego pasa a clamp de MSS si estás enroutando tráfico TCP pesado.

Tres microhistorias corporativas desde la trinchera

Incidente 1: la caída causada por una suposición equivocada

Una empresa mediana desplegó WireGuard como “VPN de acceso remoto simple” para ingenieros. El doc de diseño decía: “Abrir UDP/51820 en el servidor VPN.” Sencillo.

Desplegaron el servidor en un rack de colo detrás de un firewall de borde gestionado por otro equipo de redes. El equipo de aplicaciones pidió “abrir puerto 51820”. El equipo de redes abrió TCP/51820 porque la plantilla del sistema de tickets por defecto activaba TCP, y nadie lo cuestionó. Todos probaron desde la red interna, donde un path NAT hairpin interno hacía que las cosas parecieran funcionar “a veces”, que es el peor tipo de funcionar.

El día del lanzamiento, los usuarios remotos reportaron “Handshake did not complete.” El equipo de aplicaciones pasó horas rotando claves y reconstruyendo configs. Mientras tanto el equipo de redes insistía “el puerto está abierto.” Lo estaba. Simplemente no el protocolo correcto.

La solución fue una línea en el firewall permitiendo UDP/51820 entrante. La lección tenía menos que ver con WireGuard y más con suposiciones: nunca aceptes “puerto abierto” sin “protocolo abierto”, y nunca depures criptografía antes de confirmar que llegan paquetes.

Incidente 2: una optimización que salió mal

Otra organización quiso “reducir el ruido de fondo” en clientes móviles para ahorrar batería y datos. Alguien notó PersistentKeepalive = 25 y decidió que era un gasto innecesario. Lo eliminaron en toda la flota.

Funcionó en su Wi‑Fi de oficina y en planes LTE corporativos. Luego el personal de campo empezó a usar redes aleatorias: Wi‑Fi de hotel, cafés, portales cautivos de aeropuerto. Esas redes a menudo tienen timeouts UDP agresivos. Los túneles inactivos morían en silencio. Cuando los usuarios reanudaban actividad, algo de tráfico disparaba un nuevo handshake, pero no siempre en el orden correcto para sus aplicaciones. Vieron fallos intermitentes: a veces funcionaba tras 10–30 segundos, a veces no hasta reiniciar la app.

La respuesta al incidente se centró en versiones de WireGuard, kernels y “quizá la encriptación está rota.” No lo estaba. La “optimización” quitó el único mecanismo que mantenía vivos los mapeos NAT.

Reintrodujeron keepalive—selectivamente. Portátiles siempre con keepalive. Teléfonos con un intervalo mayor y solo para perfiles usados en redes hostiles. La lección más profunda: optimizar sin un modelo de fallo es simplemente ofrecerte voluntario para futuros incidentes.

Incidente 3: la práctica aburrida pero correcta que salvó el día

Un equipo de servicios financieros tenía una costumbre que parecía anticuada: cada cambio de servicio de red venía con una captura de paquetes en ambos extremos durante una ventana de prueba, guardada con el registro del cambio. No porque cumplimiento lo exigiera, sino porque les gustaba dormir.

Un viernes, un cambio menor de enrutamiento del ISP movió la IP pública de su servidor WireGuard (planificado). El DNS se actualizó rápido, pero un subconjunto de clientes siguió golpeando la IP antigua por caché y un path de resolución obsoleto. Los usuarios vieron “Handshake did not complete.” El equipo on‑call sacó la última captura de paquetes conocida y la comparó con una captura nueva en minutos.

Las capturas contaron la historia: los clientes enviaban UDP a la IP antigua; el servidor nunca lo veía. No hubo necesidad de tocar claves, MTU o reglas de firewall. Redujeron el TTL de DNS para ese registro durante la migración y empujaron una IP de endpoint actualizada a los clientes que no podían confiar en DNS.

Fue aburrido, medible y rápido. La práctica no era glamorosa, pero convirtió adivinanzas en un incidente corto.

Errores comunes: síntoma → causa raíz → solución

  • Síntoma: El cliente muestra “latest handshake: (none)”; tcpdump del servidor no muestra UDP entrante.
    Causa raíz: IP pública/endpoint equivocado, firewall ascendente, reenvío de puertos faltante, CGNAT o bloqueo UDP del ISP.
    Solución: verifica IP del endpoint, abre UDP en cada capa de firewall, configura reenvío de puertos correcto, o mueve el servidor a una IP pública real (o IPv6).
  • Síntoma: El servidor ve paquetes UDP entrantes pero nunca envía respuestas.
    Causa raíz: firewall del host que descarta salida, enrutamiento por política que manda respuestas por interfaz equivocada, rp_filter que descarta, o WireGuard no enlazado/iniciado correctamente.
    Solución: permite UDP saliente, arregla enrutamiento, relaja rp_filter donde corresponda, valida ss -lunp y wg show.
  • Síntoma: El servidor responde en tcpdump; el cliente nunca ve las respuestas.
    Causa raíz: firewall del lado cliente, mapeo NAT expirado, comportamiento NAT simétrico, o ruta de retorno bloqueada.
    Solución: añade PersistentKeepalive en el cliente, permite UDP entrante desde el servidor, prueba desde otra red, o usa un servidor con conectividad estable.
  • Síntoma: El handshake se completa, pero no puedes alcanzar nada a través del túnel.
    Causa raíz: AllowedIPs incorrectos, rutas faltantes, forwarding IP deshabilitado, reglas de NAT/forward faltantes en el servidor, o subredes solapadas.
    Solución: corrige AllowedIPs, verifica ip route get, habilita forwarding, añade reglas forward/NAT, renumera o haz NAT en un lado.
  • Síntoma: Funciona un minuto, luego muere en inactividad; la marca de handshake deja de actualizarse.
    Causa raíz: timeout NAT de UDP; el mapeo expira.
    Solución: PersistentKeepalive = 25 (o valor apropiado) en el peer detrás de NAT; considera reducir timeouts UDP en el firewall si lo controlas.
  • Síntoma: Funciona en algunas redes, nunca en otras (especialmente redes guest corporativas).
    Causa raíz: UDP bloqueado o con rate-limit; solo permiten TCP/443 saliente.
    Solución: proporciona una ruta de salida alternativa (otra red), o despliega un diseño que no requiera egress UDP crudo para esos clientes (decisión organizativa, no una opción de WireGuard).
  • Síntoma: Tras reanudar o restaurar una VM, los handshakes fallan de forma impredecible.
    Causa raíz: salto de reloj/deriva de tiempo, estado obsoleto o casos límite de protección replay.
    Solución: asegura sincronía de tiempo, reinicia/derriba la interfaz, evita restaurar snapshots antiguos como “paso de reparación” para servicios de red.
  • Síntoma: El handshake funciona, los pings pequeños funcionan, las descargas grandes cuelgan.
    Causa raíz: agujero MTU/PMTUD; fragmentación bloqueada.
    Solución: baja MTU de WG, prueba con pings DF, aplica clamp de MSS en caminos de forwarding.

Listas de verificación / plan paso a paso

Checklist A: “Handshake nunca se completa” (falla dura)

  1. Confirma endpoint: la Endpoint IP y puerto del cliente son correctos.
  2. Confirma que el servidor escucha: wg show y ss -lunp.
  3. Captura de paquetes en el servidor: tcpdump -ni <if> udp port <port>.
  4. Si el servidor no ve nada: inspecciona reenvíos de puertos, firewalls exteriores y si el servidor tiene IP pública real (no 100.64/10).
  5. Si el servidor ve solo entrada: revisa egress del firewall del servidor, enrutamiento y rp_filter.
  6. Si el servidor ve ambas direcciones: valida claves y sincronía de tiempo; luego verifica si un middlebox reescribe/blackholea respuestas en el lado cliente.

Checklist B: “Handshake se completa pero el tráfico no” (falla suave)

  1. Confirma que los contadores se mueven: wg show debería mostrar bytes en aumento.
  2. Revisa AllowedIPs: asegúrate de que las redes destino estén incluidas en el lado emisor; asegura que las IPs de túnel del peer sean correctas en el receptor.
  3. Revisa enrutamiento: ip route get <dest> debe apuntar a wg0 para destinos tunelizados.
  4. Revisa forwarding/NAT: si esperas acceso LAN, habilita forwarding y permite tráfico en la cadena forward.
  5. Prueba MTU: ping DF con tamaños crecientes; ajusta MTU de WG o clamp de MSS.
  6. Luego DNS: solo después de que la conectividad IP funcione. Los problemas de DNS se hacen pasar por “VPN rota” y hacen perder horas.

Checklist C: “Se cae cada pocos minutos” (falla intermitente)

  1. Observa cambios de endpoint: el campo endpoint en wg show del servidor que fluctúa indica rebinding NAT.
  2. Añade keepalive: PersistentKeepalive = 25 en el lado detrás de NAT; afina según necesidad.
  3. Revisa timeouts UDP: en firewalls que controlas, aumenta el timeout de sesión UDP para ese puerto/host.
  4. Busca NATs en competencia: doble NAT y aislamiento de cliente en Wi‑Fi guest pueden comportarse como pérdida de paquetes.

FAQ

1) ¿“Handshake did not complete” siempre significa que el puerto UDP está bloqueado?

No. Significa que los paquetes de handshake no se intercambian correctamente. El UDP bloqueado es común, pero una IP de endpoint equivocada, enrutamiento asimétrico o un desajuste de claves también pueden provocarlo. Usa tcpdump para separar “no llegan paquetes” de “llegan paquetes pero el handshake falla”.

2) Si puedo hacer ping a la IP pública del servidor, ¿por qué WireGuard no hace handshake?

Ping es ICMP. WireGuard usa UDP. Las redes frecuentemente permiten ICMP pero bloquean o limitan UDP, especialmente en guest corporativos y algunos ISP. Prueba alcanzabilidad UDP con capturas de paquetes, no con vibraciones.

3) ¿Debería cambiar el puerto de WireGuard desde 51820?

A veces. Si sospechas filtrado ascendente en puertos VPN comunes, mover a un puerto UDP alto aleatorio puede ayudar. Pero no lo hagas en modo ritual: si tcpdump no muestra nada en el servidor, aún tienes que abrir/reverforward el nuevo puerto en todas partes.

4) ¿Dónde debería poner PersistentKeepalive?

En el peer detrás de NAT que necesita mantenerse alcanzable. Normalmente es el cliente. Ponerlo en el servidor público no hace nada para los mapeos NAT del cliente.

5) ¿Pueden AllowedIPs incorrectos impedir el handshake?

Sí, de un par de formas. Si el servidor espera que el peer use cierta IP de túnel pero AllowedIPs no la incluye, los paquetes pueden descartarse como “no de este peer”. Más a menudo, el handshake tiene éxito pero los datos no llegan a ninguna parte porque el enrutamiento nunca envía paquetes al túnel.

6) ¿Por qué funciona en mi hotspot del teléfono pero no en la oficina?

Las redes de oficina suelen bloquear UDP saliente excepto para DNS/NTP, o ejecutan inspección stateful agresiva que mata flujos inactivos largos. Tu hotspot de teléfono tiende a ser más simple y amable con UDP.

7) ¿Necesito reiniciar WireGuard después de cambiar reglas de firewall?

No, los cambios de firewall aplican de inmediato. Reiniciar puede ayudar a despejar confusiones, pero no es obligatorio. Prefiere cambiar una variable a la vez para saber qué lo arregló.

8) ¿Cómo sé si estoy detrás de CGNAT?

Si la interfaz WAN tiene una IP en 100.64.0.0/10, estás detrás de CGNAT. También, si la IP WAN de tu router es privada (192.168/10.0/172.16), estás detrás de otro NAT. El reenvío de puertos entrantes no funcionará a menos que controles el NAT ascendente también.

9) El handshake se completa, pero solo algunas subredes son alcanzables. ¿Por qué?

Eso suele ser un desajuste de enrutamiento/AllowedIPs o subredes solapadas. Confirma ip route get para cada destino y verifica que AllowedIPs de cada peer incluya los CIDR correctos.

10) ¿La sincronización de tiempo es realmente relevante para WireGuard?

La mayoría del tiempo, no. Pero cuando los relojes están muy mal—especialmente tras suspend/resume de VM o restaurar snapshots—la protección contra replay y el estado pueden comportarse de forma que parezca un fallo aleatorio de handshake. Mantén NTP sano y aburrido.

Conclusión: siguientes pasos que realmente reducen páginas

Cuando WireGuard dice “Handshake did not complete”, no te está pidiendo meditar sobre criptografía. Te pide que sigas los paquetes. Haz la secuencia aburrida:

  1. Verifica endpoint/puerto y que el servidor esté escuchando.
  2. Captura paquetes en el servidor: ¿llegan paquetes UDP entrantes?
  3. Si sí, ¿salen respuestas y las recibe el cliente?
  4. Si los handshakes tienen éxito, deja de mirar handshakes y arregla enrutamiento/AllowedIPs/forwarding/MTU.

Operativamente, tu mejor ganancia a largo plazo es estandarizar un paquete diagnóstico mínimo: salida de wg show, ss -lunp, un tcpdump de 30 segundos en ambos extremos y timedatectl. Mantén eso como memoria muscular. Tu yo futuro te lo agradecerá—en silencio, porque finalmente está durmiendo.

← Anterior
Cuotas ZFS para multiinquilinos: evitar que un usuario deje el pool sin espacio
Siguiente →
Dimensionamiento L2ARC de ZFS: Cuando 200GB ayudan más que 2TB

Deja un comentario