Problema

En entornos de auto‑hosting es frecuente querer que una aplicación específica, como SearXNG, utilice una VPN para todas sus peticiones externas. La necesidad surge cuando se desea ocultar la IP del servidor, evitar bloqueos geográficos o simplemente separar el tráfico de búsqueda del resto del tráfico de la máquina. El reto consiste en forzar que solo el proceso que corre bajo el usuario searxng (UID 999) salga por la interfaz wg-<nombre> sin afectar a los demás servicios ni romper la conectividad de la propia VM.

Causa

Los sistemas Linux manejan la tabla de rutas global por defecto. Cambiar la ruta por defecto a la interfaz WireGuard (wg-UK-4) redirige todo el tráfico, incluido el tráfico de gestión y de los propios peers de WireGuard, lo que suele provocar pérdida de conectividad a la red local y a los servidores de búsqueda. Además, WireGuard, al ser una interfaz punto‑a‑punto, no tiene una puerta de enlace tradicional; el tráfico debe ser enviado a la dirección del peer mediante la tabla de rutas. Cuando se sustituyen las rutas sin crear una tabla de políticas, el kernel no sabe a qué interfaz asignar paquetes cuyo origen no coincide con la IP de la VPN, y los DNS/resolución fallan.

Solución

La solución genérica se basa en policy routing (tablas de enrutamiento separadas) combinada con una regla que asocie el UID del proceso a la tabla que usa la VPN. Opcionalmente, se pueden reforzar los límites con nftables para garantizar que ningún otro proceso utilice la interfaz.

Paso 1: crear una tabla de rutas dedicada a WireGuard

Edita /etc/iproute2/rt_tables y añade una entrada, por ejemplo:

200 wg_vpn

Paso 2: definir la ruta predeterminada dentro de esa tabla

ip route add default dev wg-UK-4 table wg_vpn

Si el peer de WireGuard necesita una ruta explícita (por ejemplo, para el endpoint), añádela:

ip route add 154.47.24.193/32 dev wg-UK-4 table wg_vpn

Paso 3: asociar el UID a la tabla

ip rule add uidrange 999-999 lookup wg_vpn

Con esto, cualquier paquete cuyo proceso tenga UID 999 será evaluado usando la tabla wg_vpn, enviándose por wg-UK-4.

Paso 4: asegurar que el DNS de la VPN se usa para el proceso

Los procesos que usan la regla anterior siguen tomando /etc/resolv.conf del host. Si el DNS local no es accesible a través de la VPN, crea un systemd-resolved o dnsmasq aislado y apunta a los servidores de la VPN, luego exporta la variable de entorno RES_OPTIONS o LD_PRELOAD a SearXNG. En la práctica, basta con añadir al archivo de servicio:

Environment=RES_OPTIONS=nameserver 10.2.0.1

Paso 5 (opcional): reforzar con nftables

Para evitar que procesos con UID diferente utilicen la interfaz, crea una regla que descarte paquetes salientes por wg-UK-4 cuyo UID no sea 999.

nft add table ip filter
nft 'add chain ip filter output { type filter hook output priority 0; }'
nft add rule ip filter output oif "wg-UK-4" meta skuid != 999 drop

Esta regla actúa como segunda capa de seguridad y es útil cuando se usan contenedores o usuarios con privilegios elevados.

Cuándo aplicar esta solución

  • Entorno multi‑usuario donde solo una aplicación debe pasar por la VPN.
  • Servidores de búsqueda (SearXNG, Metasearch, etc.) que requieren anonimato o evitar bloqueos.
  • Redes domésticas o homelab con una única IP pública y una suscripción VPN que se quiere compartir de forma selectiva.
  • No aplicar si la VPN necesita gestionar tráfico de gestión (SSH, actualizaciones) o si el endpoint de WireGuard está en la misma subred que la red local, pues la regla de política puede crear bucles.

Código

# 1. Añadir tabla de rutas
echo "200 wg_vpn" >> /etc/iproute2/rt_tables

# 2. Rutas en la tabla wg_vpn
ip route add default dev wg-UK-4 table wg_vpn
ip route add 154.47.24.193/32 dev wg-UK-4 table wg_vpn

# 3. Regla de UID → tabla
ip rule add uidrange 999-999 lookup wg_vpn

# 4. (Opcional) nftables para bloquear UID no autorizado
nft add table ip filter
nft 'add chain ip filter output { type filter hook output priority 0; }'
nft add rule ip filter output oif "wg-UK-4" meta skuid != 999 drop

Verificación

  1. Comprobar la regla de UID

    ip rule list | grep wg_vpn
    

    Debería mostrarse la línea con uidrange 999-999.

  2. Verificar la ruta usada por el proceso
    Ejecuta dentro del contexto del usuario searxng:

    sudo -u searxng ip route get 8.8.8.8
    

    La salida debe indicar dev wg-UK-4.

  3. Probar conectividad DNS

    sudo -u searxng dig @10.2.0.1 example.com +short
    

    Si devuelve una dirección, el DNS de la VPN funciona.

  4. Consultar un motor de búsqueda desde SearXNG
    Accede a la interfaz web y revisa que los resultados aparecen. En los logs (/var/log/searxng/*.log) no debe haber errores de “connection timed out”.

  5. Revisar nftables (si se usó)

    nft list chain ip filter output
    

    Confirma que la regla meta skuid != 999 drop está presente.

Notas adicionales

  • Persistencia: los comandos ip route y ip rule no sobreviven a reinicios. Añade los bloques al archivo /etc/network/interfaces.d/wg-UK-4 o crea un servicio systemd que ejecute el script al arranque.
  • Múltiples usuarios: si más de un UID necesita la VPN, usa rangos (uidrange 1000-1010) o crea una regla por cada UID.
  • Contenedores: para Docker o podman, la opción más limpia es ejecutar el contenedor con --network=none y asignarle la interfaz wg-UK-4 mediante --net=container:<id> o usar --cap-add=NET_ADMIN y aplicar la regla de UID dentro del contenedor.
  • Depuración: tcpdump -i wg-UK-4 -nn ayuda a confirmar que los paquetes salen por la VPN. Si ves tráfico ARP o DHCP, la tabla de rutas no está siendo aplicada correctamente.
  • Persistencia de DNS: algunos sistemas sobrescriben /etc/resolv.conf al iniciar systemd-resolved. Usa systemd-resolved en modo stub y configura /etc/systemd/resolved.conf con DNS=10.2.0.1 y Domains=~. para que solo la VPN resuelva nombres.

Con estos pasos, cualquier proceso que corra bajo el UID del servicio (en este caso SearXNG) se encamina de forma fiable a través de la interfaz WireGuard, mientras el resto del servidor mantiene su ruta tradicional. La combinación de policy routing y nftables ofrece un control granular y evita los problemas comunes de “todo el tráfico desaparece” que aparecen al reemplazar la ruta por defecto sin una tabla dedicada.