Homelab WireGuard VPN
WireGuard es un protocolo VPN moderno y rápido. Es la opción correcta para el acceso remoto a una red doméstica: más sencillo de configurar que OpenVPN y más rápido que la mayoría de las alternativas.
Todos los ejemplos de configuración muestran configuraciones comunes. Revisa cada comando —especialmente las reglas de reenvío de iptables y los permisos de archivos de claves— antes de aplicarlos a tu sistema, y realiza los cambios en una ventana de mantenimiento.
Cuándo Usarlo
- Configurar un servidor WireGuard en una Raspberry Pi, host Linux, pfSense o enrutador
- Generar pares de claves WireGuard y escribir archivos de configuración de pares
- Configurar acceso remoto desde un teléfono o portátil a una red doméstica
- Explicar el túnel dividido (enrutar solo tráfico doméstico) vs el túnel completo (enrutar todo el tráfico)
- Solucionar problemas de conexiones WireGuard que no se establecen
- Automatizar la generación de configuración de pares para múltiples clientes
Cómo Funciona WireGuard
Tu teléfono (cliente WireGuard)
│
│ Túnel UDP cifrado (puerto 51820)
│
Tu enrutador doméstico (servidor WireGuard — necesita una IP pública o DDNS)
│
Tu red doméstica (192.168.1.0/24, NAS, Pi, etc.)
Cada dispositivo tiene un par de claves (clave pública + privada).
El servidor conoce la clave pública de cada cliente.
El cliente conoce la clave pública del servidor + endpoint (IP:puerto).
El tráfico se cifra de extremo a extremo sin servidor central ni autoridad certificadora.
Configuración del Servidor (Linux)
# Instalar WireGuard
sudo apt update && sudo apt install wireguard -y
# Generar par de claves del servidor: crea archivos con permisos privados desde el inicio
sudo mkdir -p /etc/wireguard
sudo sh -c 'umask 077; wg genkey > /etc/wireguard/server_private.key'
sudo sh -c 'wg pubkey < /etc/wireguard/server_private.key > /etc/wireguard/server_public.key'
# Escribir la configuración del servidor — sustituye la clave privada real
# No almacenes claves privadas en el control de versiones ni las compartas
sudo tee /etc/wireguard/wg0.conf << 'EOF'
[Interface]
Address = 10.8.0.1/24 # Subred VPN — el servidor obtiene .1
ListenPort = 51820
PrivateKey = <pegar_clave_privada_servidor_aqui>
# Reglas de reenvío con ámbito: permitir tráfico VPN de entrada/salida, no un FORWARD ACCEPT general
PostUp = iptables -A FORWARD -i wg0 -o eth0 -j ACCEPT
PostUp = iptables -A FORWARD -i eth0 -o wg0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -o eth0 -j ACCEPT
PostDown = iptables -D FORWARD -i eth0 -o wg0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
[Peer]
# Teléfono — reemplaza con la clave pública real del teléfono
PublicKey = <clave_publica_telefono>
AllowedIPs = 10.8.0.2/32
[Peer]
# Portátil — reemplaza con la clave pública real del portátil
PublicKey = <clave_publica_portatil>
AllowedIPs = 10.8.0.3/32
EOF
sudo chmod 600 /etc/wireguard/wg0.conf
# Reemplaza eth0 con el nombre de tu interfaz de salida real
# Compruébalo con: ip route show default
# Habilitar reenvío de IP (requerido para enrutar tráfico a través del servidor)
echo "net.ipv4.ip_forward=1" | sudo tee /etc/sysctl.d/99-wireguard.conf
sudo sysctl --system
# Iniciar WireGuard y habilitar al arranque
sudo wg-quick up wg0
sudo systemctl enable wg-quick@wg0
Configuración del Cliente
# Generar un par de claves único para cada dispositivo cliente
# Ejecutar en el cliente, o en el servidor y transferir la clave privada de forma segura — nunca en texto plano
umask 077
wg genkey | tee phone_private.key | wg pubkey > phone_public.key
# Archivo de configuración del cliente (phone_wg0.conf):
[Interface]
PrivateKey = <clave_privada_telefono>
Address = 10.8.0.2/32
DNS = 192.168.1.2 # Opcional: usar Pi-hole para DNS a través del túnel
[Peer]
PublicKey = <clave_publica_servidor>
Endpoint = tu-ip-casa.ddns.net:51820 # Tu IP pública o nombre de host DDNS
AllowedIPs = 192.168.1.0/24 # Túnel dividido: solo tráfico de red doméstica
# AllowedIPs = 0.0.0.0/0, ::/0 # Túnel completo: todo el tráfico a través de VPN
PersistentKeepalive = 25 # Mantener agujero NAT abierto (requerido para clientes móviles)
Túnel Dividido vs Túnel Completo
# Túnel dividido: AllowedIPs = 192.168.1.0/24
Solo el tráfico destinado a tu red doméstica pasa por la VPN.
El tráfico de Internet (YouTube, Spotify) va directamente — mejor rendimiento en móvil.
Mejor para: "Solo quiero acceder a mi NAS y Pi desde cualquier lugar."
# Túnel completo: AllowedIPs = 0.0.0.0/0, ::/0
TODO el tráfico pasa por tu conexión a Internet doméstica.
Útil para: aprovechar el bloqueo de anuncios del DNS doméstico/Pi-hole.
Desventaja: la velocidad de subida de casa se convierte en el cuello de botella en todas partes.
# Túnel dividido de múltiples subredes (caso de uso más común en homelab):
AllowedIPs = 192.168.10.0/24, 192.168.20.0/24, 192.168.30.0/24, 10.8.0.0/24
Enruta todas tus VLANs a través del túnel; Internet permanece directo.
Generación de Claves y Gestión de Pares
import subprocess
def generate_keypair() -> tuple[str, str]:
"""Generar un par de claves WireGuard. Devuelve (clave_privada, clave_publica)."""
private = subprocess.check_output(["wg", "genkey"]).decode().strip()
public = subprocess.run(
["wg", "pubkey"], input=private.encode(), capture_output=True
).stdout.decode().strip()
return private, public
def generate_preshared_key() -> str:
return subprocess.check_output(["wg", "genpsk"]).decode().strip()
def build_client_config(
client_private_key: str,
client_vpn_ip: str, # ej. "10.8.0.3"
server_public_key: str,
server_endpoint: str, # ej. "home.example.com:51820"
allowed_ips: str = "192.168.1.0/24",
dns: str = "",
) -> str:
dns_line = f"DNS = {dns}\n" if dns else ""
return f"""[Interface]
PrivateKey = {client_private_key}
Address = {client_vpn_ip}/32
{dns_line}
[Peer]
PublicKey = {server_public_key}
Endpoint = {server_endpoint}
AllowedIPs = {allowed_ips}
PersistentKeepalive = 25
"""
def build_server_peer_block(
client_public_key: str,
client_vpn_ip: str,
comment: str = "",
) -> str:
comment_line = f"# {comment}\n" if comment else ""
return f"""
{comment_line}[Peer]
PublicKey = {client_public_key}
AllowedIPs = {client_vpn_ip}/32
"""
Mantén las claves privadas fuera del control de versiones. Si usas este script, escribe el material de las claves en archivos con modo 600 y nunca lo registres o imprimas.
WireGuard en pfSense / OPNsense
# pfSense: VPN → WireGuard → Añadir Túnel
Claves de Interfaz: Generar (crea el par de claves automáticamente)
Puerto de Escucha: 51820
Dirección de Interfaz: 10.8.0.1/24
# Añadir Par (uno por cliente):
Clave Pública: <clave pública del cliente>
IPs Permitidas: 10.8.0.2/32
# Asignar la interfaz WireGuard:
Interfaces → Asignaciones → Añadir (seleccionar wg0)
Habilitar interfaz, no necesita IP (se establece en la configuración del túnel)
# Reglas de cortafuegos:
WAN → Permitir tráfico UDP puerto 51820 entrante (para que los clientes puedan alcanzar el servidor)
Interfaz WireGuard → Permitir tráfico a las redes LAN que deseas accesibles
DDNS (DNS Dinámico) para Servidores Domésticos
La mayoría de las conexiones a Internet domésticas tienen una IP dinámica. Usa DDNS para que tu endpoint VPN siga siendo accesible después de un cambio de IP.
# Opción 1: Cloudflare DDNS — almacena credenciales en un archivo de secretos, no en línea
# Entrada docker-compose usando un archivo env:
ddns-updater:
image: qmcgaw/ddns-updater
env_file: ./ddns.env # almacena zone_id y token aquí, no en compose
restart: unless-stopped
# ddns.env (chmod 600, no enviado a git):
# SETTINGS_CLOUDFLARE_ZONE_ID=tu_zone_id
# SETTINGS_CLOUDFLARE_TOKEN=tu_api_token
# Opción 2: DuckDNS (gratuito, sencillo)
Regístrate en duckdns.org → obtén un token y subdominio (micasa.duckdns.org)
Almacena el token en /etc/ddns.env (modo 600), luego usa un pequeño script propiedad de root:
# /usr/local/bin/update-duckdns
#!/bin/sh
set -eu
. /etc/ddns.env
curl --fail --silent --show-error --max-time 10 \
--get "https://www.duckdns.org/update" \
--data-urlencode "domains=micasa" \
--data-urlencode "token=${DUCKDNS_TOKEN}" \
--data-urlencode "ip="
# Tarea cron:
*/5 * * * * /usr/local/bin/update-duckdns >/dev/null 2>&1
Solución de Problemas
# Verificar el estado de WireGuard y el último handshake
sudo wg show
# Si "latest handshake" nunca aparece o es muy antiguo, el túnel no está conectado.
# Verificar:
# 1. ¿Está abierto el puerto UDP 51820 en el enrutador/cortafuegos?
sudo ufw status # o verificar reglas de cortafuegos pfSense/UniFi
# 2. ¿Es correcta la clave pública del servidor en la configuración del cliente?
sudo wg show wg0 public-key # Comparar con lo que está en la configuración del cliente
# 3. ¿Está habilitado el reenvío de IP en el servidor?
cat /proc/sys/net/ipv4/ip_forward # Debería ser 1
# 4. ¿Cubre AllowedIPs del cliente la IP a la que intentas acceder?
# Si AllowedIPs = 192.168.1.0/24 y estás intentando acceder a 192.168.3.5, no enrutará.
# Verificar registros del kernel para errores de WireGuard
dmesg | grep wireguard
# Reiniciar WireGuard
sudo wg-quick down wg0 && sudo wg-quick up wg0
Anti-patrones
# MALO: Almacenar claves privadas en control de versiones o compartirlas
# Las claves privadas son equivalentes a contraseñas — nunca las envíes a git
# MALO: Usar AllowedIPs = 0.0.0.0/0 en móvil sin considerar el impacto
# El túnel completo enruta todo el tráfico móvil a través de tu subida doméstica — generalmente lento
# MALO: No establecer PersistentKeepalive en clientes móviles
# Los clientes móviles detrás de NAT pierden túneles inactivos sin él
# MALO: Abrir el puerto 51820 en el cortafuegos pero olvidar el reenvío de IP en el servidor
# El túnel se conecta pero no enruta tráfico — confuso de depurar
# MALO: Compartir un par de claves entre múltiples dispositivos cliente
# Cada dispositivo debe tener su propio par de claves único — compartir claves rompe el modelo de seguridad
# MALO: Usar una regla iptables "FORWARD ACCEPT" amplia
# Limita las reglas de reenvío a la interfaz wg0 y dirección solamente
Mejores Prácticas
- Genera un par de claves único por dispositivo cliente — nunca reutilices claves
- Usa túnel dividido (
AllowedIPs = <subredes domésticas>) para móviles - Establece
PersistentKeepalive = 25en todos los clientes móviles - Usa DDNS si tu ISP asigna una IP dinámica; almacena credenciales en archivos env, no en línea
- Usa reglas de reenvío iptables con ámbito (entrante solo en wg0) en lugar de un FORWARD ACCEPT general
- Agrega la IP de Pi-hole como
DNS =en las configuraciones de cliente para obtener bloqueo de anuncios a través de la VPN - Rota el par de claves del servidor periódicamente y actualiza todas las configuraciones de cliente
Habilidades Relacionadas
- homelab-network-setup
- homelab-vlan-segmentation
- homelab-pihole-dns