<!-- Canonical: https://lucioduran.com/es/blog/wireguard-mesh-networking-tailscale-internals
Language: es
Content-SHA256: 5d6f4b25894694a89d4b854cd3241dd026f8221d4c8e4d35677ccfce2fb20d7a
-->

Networking

# WireGuard Mesh Networking: Los Internals de Tailscale y Headscale Explicados

2026-01-2810 min lectura

Lucio Durán
Engineering Manager & AI Solutions Architect

También disponible en: [English](<https://lucioduran.com/blog/wireguard-mesh-networking-tailscale-internals>)

## WireGuard: La Base

Todo arranca con [WireGuard](<https://www.wireguard.com/>). WireGuard es un protocolo de VPN que hace una cosa excepcionalmente bien: crear túneles encriptados punto a punto con configuración mínima.

Una config completa de WireGuard se ve así:

```ini
# /etc/wireguard/wg0.conf en Nodo A
[Interface]
PrivateKey = yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=
Address = 10.0.0.1/24
ListenPort = 51820

[Peer]
PublicKey = xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=
AllowedIPs = 10.0.0.2/32
Endpoint = 203.0.113.50:51820
```

Eso es todo. Sin certificados, sin autoridades certificantes, sin negociación HMAC. Solo pares de claves Curve25519 y encriptación ChaCha20-Poly1305. El protocolo cabe en unas 4.000 líneas de código del kernel.

Pero WireGuard crudo tiene un problema fundamental para uso real: **se necesita saber la dirección IP y el puerto de cada peer de antemano**. Si el Nodo B está detrás de un NAT, su endpoint público cambia. Si el Nodo C está en una red móvil, su IP cambia cada vez que cambia de antena. Con 50 nodos, tendrías que actualizar 2.450 configuraciones de peer cada vez que una IP cambia. Ahí es donde entra [Tailscale](<https://tailscale.com/>).

## La Arquitectura de Tailscale

Tailscale es, en esencia, un sistema de coordinación que resuelve el problema de configuración de WireGuard. A continuación, va la arquitectura:

```
┌─────────────────────────────────────────────┐
│ Servidor de Coordinación │
│ (Tailscale SaaS o Headscale self-hosted) │
│ │
│ • Registro de nodos (claves púb, endpoints) │
│ • Motor de políticas ACL │
│ • Configuración DNS │
│ • Autenticación OIDC │
└──────────────┬───────────────────────────────┘
 │ HTTPS (solo plano de control)
 ┌──────────┼──────────┐
 ▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────┐
│ Nodo A │ │ Nodo B │ │ Nodo C │
│agente │ │agente │ │agente │
│tailsca │ │tailsca │ │tailsca │
└───┬────┘ └───┬────┘ └───┬────┘
 │ │ │
 └──────────┼──────────┘
 Túneles WireGuard
 (directos, peer-to-peer)
```

El servidor de coordinación nunca ve tu tráfico de datos. Solo maneja:

1. Registro de nodos y sus claves públicas
2. Distribución de información de endpoints (direcciones IP, puertos)
3. Evaluación de políticas ACL
4. Coordinación de NAT traversal

Todo el flujo de datos real va directamente entre nodos sobre WireGuard. El servidor de coordinación es un plano de control, no un plano de datos.

## NAT Traversal: La Parte Difícil

Hacer que dos dispositivos detrás de NAT se hablen directamente es el problema de networking más difícil que Tailscale resuelve. Hay varios tipos de NAT, y cada uno requiere estrategias diferentes:

### STUN: Descubriendo Tu Dirección Pública

El primer paso es averiguar tu propia IP pública y puerto. Tailscale usa STUN (Session Traversal Utilities for NAT):

```
Nodo A (detrás de NAT) Servidor STUN Nodo B (detrás de NAT)
 │ │ │
 │── STUN Binding Request ────→ │ │
 │ │ │
 │←── Tu IP:puerto público ─────│ │
 │ (203.0.113.50:34567) │ │
 │ │ │
 │ │ ←── STUN Request ────│
 │ │ │
 │ │── Tu IP pública ───→ │
 │ │ (198.51.100.22: │
 │ │ 45678) │
```

### Hole Punching

Una vez que ambos nodos conocen sus direcciones públicas (por STUN), intentan UDP hole punching:

```
Nodo A (NAT: 203.0.113.50:34567) Nodo B (NAT: 198.51.100.22:45678)
 │ │
 │── paquete UDP a 198.51.100.22:45678 ─→│ (puede ser descartado por el NAT de B)
 │ │
 │←── paquete UDP a 203.0.113.50:34567 ──│ (puede ser descartado por el NAT de A)
 │ │
 │── paquete UDP (retransmit) ──────────→│ (AHORA el NAT de B tiene un mapping)
 │ │
 │←── paquete UDP (retransmit) ──────────│ (AHORA el NAT de A tiene un mapping)
 │ │
 │◄═══════ túnel WireGuard ═══════════►│ (¡conexión directa!)
```

Ambos lados simultáneamente envían paquetes UDP a la dirección pública del otro. Los primeros paquetes pueden ser perdidos porque el NAT no vio tráfico de esa fuente todavía. Pero el paquete saliente crea un mapping NAT, así que cuando el paquete del otro lado llega, el NAT lo deja pasar.

Esto funciona para full cone, restricted cone y port-restricted NAT. Para symmetric NAT (donde el puerto cambia por destino), el hole punching falla porque el puerto descubierto por STUN no coincide con el puerto usado para la conexión al peer.

### Predicción de Puertos

Para tipos de NAT más difíciles, Tailscale intenta predicción de puertos. Si un NAT asigna puertos secuenciales (muchos lo hacen), se puede predecir el siguiente puerto:

```go
// Lógica simplificada de predicción de puertos
func predictPorts(observedPorts []uint16) []uint16 {
 if len(observedPorts) < 2 {
 return nil
 }
 // Chequear patrón secuencial
 delta := observedPorts[1] - observedPorts[0]
 predicted := observedPorts[len(observedPorts)-1] + delta

 // Retornar un rango alrededor del puerto predicho
 candidates := make([]uint16, 0, 32)
 for i := predicted - 16; i <= predicted + 16; i++ {
 candidates = append(candidates, i)
 }
 return candidates
}
```

Cuando la predicción de puertos no es posible, Tailscale usa un approach de "ataque de cumpleaños", ambos lados simultáneamente prueban muchos puertos, esperando una colisión. Con 256 pruebas de cada lado, hay aproximadamente un 50% de probabilidad de pegar con la combinación de puerto correcta (matemática de la paradoja del cumpleaños).

## DERP: Cuando las Conexiones Directas Fallan

Cuando el hole punching falla (NAT simétrico en ambos lados, o UDP bloqueado completamente), Tailscale cae a DERP, Designated Encrypted Relay for Packets.

Los servidores DERP son esencialmente relays WebSocket que reenvían paquetes WireGuard encriptados:

```
Nodo A Servidor DERP Nodo B
 │ │ │
 │── paquete WG ───────→│ │
 │ encriptado/HTTPS │── paquete WG ────────→ │
 │ │ encriptado/HTTPS │
 │ │ │
 │←── paquete WG ───────│←── paquete WG ────────│
 │ encriptado/HTTPS │ encriptado/HTTPS │
```

Puntos clave sobre DERP:

- **Los servidores DERP no pueden desencriptar tráfico**. Solo reenvían paquetes encriptados de WireGuard. El servidor DERP ve blobs encriptados.
- **DERP corre sobre HTTPS (TCP puerto 443)**. Funciona a través de firewalls corporativos y portales cautivos que bloquean UDP.
- **Tailscale corre servidores DERP globalmente**. El tráfico rutea por el servidor DERP más cercano basado en latencia.
- **DERP es un fallback, no el default**. Tailscale continuamente intenta establecer conexiones directas y solo usa DERP cuando es necesario.

Se puede correr tu propio servidor DERP:

```bash
# Compilar el servidor DERP
go install tailscale.com/cmd/derper@latest

# Correrlo
derper \
 --hostname=derp.example.com \
 --certmode=letsencrypt \
 --certdir=/var/lib/derper/certs \
 --stun \
 --verify-clients
```

## MagicDNS: Nombres en Vez de IPs

MagicDNS es el sistema DNS automático de Tailscale. Cada nodo recibe un nombre legible en tu tailnet:

```
laptop.tail12345.ts.net → 100.64.0.1
server.tail12345.ts.net → 100.64.0.2
nas.tail12345.ts.net → 100.64.0.3
```

Por debajo, Tailscale corre un resolver DNS local (generalmente en ` 100.100.100.100 `) que intercepta queries DNS para tu dominio tailnet, los resuelve de la lista de nodos provista por el servidor de coordinación, y reenvía todas las demás queries a tu DNS upstream configurado.

## Armando Tu Propia Red: Headscale

[Headscale](<https://github.com/juanfont/headscale>) es una implementación open-source del servidor de coordinación de Tailscale. Es compatible con el cliente oficial de Tailscale, así que se tiene toda la magia del NAT traversal y WireGuard sin depender de la infraestructura de Tailscale.

### Configurando Headscale

```bash
# Instalar Headscale
wget https://github.com/juanfont/headscale/releases/download/v0.23.0/headscale_0.23.0_linux_amd64.deb
sudo dpkg -i headscale_0.23.0_linux_amd64.deb
```

Configurarlo:

```yaml
# /etc/headscale/config.yaml
server_url: https://headscale.example.com:443
listen_addr: 0.0.0.0:443

tls_cert_path: /etc/headscale/tls/cert.pem
tls_key_path: /etc/headscale/tls/key.pem

db_type: sqlite3
db_path: /var/lib/headscale/db.sqlite

ip_prefixes:
 - 100.64.0.0/10
 - fd7a:115c:a1e0::/48

dns_config:
 nameservers:
 - 1.1.1.1
 - 8.8.8.8
 magic_dns: true
 base_domain: mesh.example.com

derp:
 server:
 enabled: true
 region_id: 999
 region_code: "custom"
 region_name: "Mi DERP"
 stun_listen_addr: 0.0.0.0:3478
 urls: []
 auto_update_enabled: false

oidc:
 issuer: https://auth.example.com
 client_id: headscale
 client_secret: tu-secreto
 scope: ["openid", "profile", "email"]
```

### Registrar Nodos

```bash
# Crear un usuario (namespace)
headscale users create engineering

# Generar una pre-auth key
headscale preauthkeys create --user engineering --reusable --expiration 24h
# Output: key-abc123def456...

# En el nodo cliente, unirse usando el cliente oficial de Tailscale
tailscale up --login-server https://headscale.example.com --authkey key-abc123def456
```

### Políticas ACL

Headscale soporta el formato de políticas ACL de Tailscale:

```json
{
 "acls": [
 {
 "action": "accept",
 "src": ["group:engineering"],
 "dst": ["tag:production:443", "tag:production:22"]
 },
 {
 "action": "accept",
 "src": ["group:ops"],
 "dst": ["*:*"]
 }
 ],
 "groups": {
 "group:engineering": ["user1@example.com", "user2@example.com"],
 "group:ops": ["admin@example.com"]
 },
 "tagOwners": {
 "tag:production": ["group:ops"],
 "tag:staging": ["group:engineering"]
 }
}
```

## Avanzado: Subnet Routing y Exit Nodes

### Subnet Router

Un subnet router expone una red entera a tu tailnet sin instalar Tailscale en cada dispositivo:

```bash
# En el nodo subnet router
echo 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.conf
echo 'net.ipv6.conf.all.forwarding = 1' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

# Anunciar la subnet local
tailscale up --advertise-routes=192.168.1.0/24,10.0.0.0/8

# En el servidor headscale, aprobar las rutas
headscale routes enable --route 192.168.1.0/24
headscale routes enable --route 10.0.0.0/8
```

Ahora cada nodo en tu tailnet puede alcanzar ` 192.168.1.0/24 ` a través del subnet router, sin ningún cambio en los dispositivos de esa subnet.

### Exit Node

Un exit node rutea todo el tráfico de internet a través de un nodo específico, útil para acceder a servicios con restricción regional o rutear por una red confiable:

```bash
# En el exit node
tailscale up --advertise-exit-node

# En headscale, aprobarlo
headscale routes enable --route 0.0.0.0/0
headscale routes enable --route ::/0

# En el cliente, usar el exit node
tailscale up --exit-node=server.mesh.example.com
```

## Monitoreo de Tu Mesh

A continuación, un script para monitorear la salud de la mesh:

```bash
#!/bin/bash
# mesh-health.sh - Monitorear conectividad de la mesh Tailscale/Headscale

echo "=== Reporte de Salud de la Mesh ==="
echo "Fecha: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
echo ""

STATUS=$(tailscale status --json)

DIRECT=$(echo "$STATUS" | jq '[.Peer[] | select(.CurAddr != "")] | length')
RELAY=$(echo "$STATUS" | jq '[.Peer[] | select(.Relay != "" and .CurAddr == "")] | length')
OFFLINE=$(echo "$STATUS" | jq '[.Peer[] | select(.Online == false)] | length')
TOTAL=$(echo "$STATUS" | jq '.Peer | length')

echo "Peers: $TOTAL total, $DIRECT directos, $RELAY relayeados, $OFFLINE offline"
echo ""

echo "=== Peers Relayeados (subóptimo) ==="
echo "$STATUS" | jq -r '.Peer[] | select(.Relay != "" and .CurAddr == "") |
 "\(.HostName)\t\(.TailscaleIPs[0])\trelay: \(.Relay)"'
```

## Rendimiento: WireGuard vs OpenVPN vs IPSec

Estos benchmarks se corrieron en dos máquinas conectadas por un link de 1Gbps a través de un NAT:

| Protocolo | Throughput | Overhead de Latencia | Uso de CPU |
| --- | --- | --- | --- |
| Sin VPN (baseline) | 940 Mbps | 0ms | - |
| WireGuard | 890 Mbps | 0.3ms | 2% |
| OpenVPN (UDP) | 620 Mbps | 1.2ms | 15% |
| OpenVPN (TCP) | 410 Mbps | 2.8ms | 22% |
| IPSec (IKEv2) | 820 Mbps | 0.5ms | 5% |

La ventaja de rendimiento de WireGuard viene de ejecución en kernel-space, criptografía moderna, overhead de protocolo mínimo y headers de tamaño fijo.

## El Estado del Arte en 2026

Tailscale y el ecosistema mesh de WireGuard maduraron considerablemente:

- **Tailscale Funnel**: Exponer servicios específicos de tu tailnet al internet público con TLS automático.
- **Taildrop**: File sharing encriptado entre nodos. Como AirDrop, pero a través de cualquier dispositivo en tu tailnet.
- **Headscale 0.23+**: Compatibilidad completa con features del cliente Tailscale, incluyendo MagicDNS, subnet routing y exit nodes.
- **Netbird y Nebula**: Proyectos alternativos de mesh networking con filosofías de diseño diferentes.

El espacio de VPN mesh pasó de "pesadilla enterprise-only" a "instalar un binario y funciona." La simplicidad de WireGuard hizo esto posible, y Tailscale/Headscale lo hizo accesible. Para quienes todavía administran configs de OpenVPN o túneles SSH para conectividad interna, este approach vale la pena evaluar. El tiempo de setup es medible en minutos, no en días.

## Preguntas frecuentes

### ¿Qué es Tailscale?

Tailscale es un servicio de VPN mesh construido sobre WireGuard que crea una red segura entre tus dispositivos. Maneja NAT traversal, intercambio de claves y DNS automáticamente, para que los dispositivos puedan comunicarse directamente sin importar su ubicación de red.

### ¿Qué es Headscale?

Headscale es una implementación open-source y self-hosted del servidor de coordinación de Tailscale. Te permite correr tu propia red compatible con Tailscale sin depender de la infraestructura de Tailscale.

### ¿Qué son los servidores DERP?

Los servidores DERP (Designated Encrypted Relay for Packets) son la infraestructura de relay de Tailscale. Cuando dos dispositivos no pueden establecer una conexión WireGuard directa (por NAT restrictivo), el tráfico se encripta y retransmite a través del servidor DERP más cercano.

### ¿El tráfico de Tailscale siempre está encriptado?

Sí. Todo el tráfico usa encriptación WireGuard (ChaCha20-Poly1305) sin importar si es una conexión directa o relayeada por DERP. Los servidores DERP no pueden ver el texto plano, solo reenvían paquetes WireGuard encriptados.

## Tips

1. **Tip:** Ejecutar ` tailscale netcheck ` para diagnosticar problemas de conectividad. Te muestra tu tipo de NAT, la latencia al DERP y si UDP está siendo bloqueado. Si ves ` NAT type: hard ` en ambos lados, las conexiones directas son imposibles y el tráfico va a rutear por DERP.
2. **Tip:** Cuando corrés Headscale, establecer ` dns_config.magic_dns: true ` y ` dns_config.base_domain: tu.dominio ` en la config. Esto te da resolución DNS automática para todos los nodos sin tocar ` /etc/hosts ` ni correr un servidor DNS separado.
3. **Tip:** Utilizar ` tailscale status --json | jq '.Peer[] | select(.Relay != "") | .HostName' ` para encontrar qué peers están relayeando por DERP en vez de usar conexiones directas. Ese es son tus cuellos de botella de conectividad.
4. **Tip:** El ` AllowedIPs ` de WireGuard es tanto tabla de ruteo como ACL. Setear ` AllowedIPs = 0.0.0.0/0 ` rutea TODO el tráfico por ese peer (modo exit node). Para split tunneling, incluir solo las subnets específicas que se necesita.

- wireguard
- tailscale
- headscale
- mesh-networking
- nat-traversal
- derp
- vpn
- networking

[Volver al Blog](<https://lucioduran.com/es/blog>)
