Administración de Linux

Redes en Linux: ip, enrutamiento, DNS, sockets, firewalls y túneles SSH

Intermedio9 min de lecturaActualizado

Esta página se tradujo del inglés con ayuda de IA y se cotejó con el original. Leer la versión en inglés

Los problemas de red suelen diagnosticarse en el orden equivocado, y por eso llevan tanto tiempo. Esta guía recorre la pila igual que lo hace un paquete —interfaz, dirección, ruta, nombre, puerto— y te da el comando moderno para inspeccionar cada capa. Después trata los firewalls con nftables y sus front ends, el valor diagnóstico de curl y dig, y los trucos de túneles SSH que te permiten pasar por un bastión.

Lo que aprenderás

  • Inspeccionar y configurar interfaces, direcciones y rutas con el comando ip
  • Explicar cómo un nombre se convierte en una dirección y depurar la resolución con dig y resolvectl
  • Listar los sockets en escucha y establecidos con ss y relacionarlos con su proceso
  • Escribir e inspeccionar reglas de firewall con nftables, ufw o firewalld
  • Aislar un fallo de conectividad capa por capa y acceder a un servicio privado mediante un túnel SSH

La mayoría de los problemas de red en Linux acaban siendo una de cinco cosas: la interfaz no tiene dirección, no hay ruta hacia el destino, el nombre no se resuelve, nada escucha en el puerto o un firewall descarta el paquete. Conocer esa lista y tener un comando para cada punto convierte una tarde de conjeturas en una comprobación de cinco minutos.

Las herramientas también han cambiado. ifconfig, netstat, route y arp venían del paquete net-tools, que ya no se mantiene y no aparece en las instalaciones por defecto; ip y ss, de iproute2, los sustituyeron y muestran cosas que las herramientas antiguas no pueden ver. Esta guía usa los comandos modernos, menciona una vez los nombres antiguos para que puedas leer documentación más vieja y da por hecho un host Debian, Ubuntu o de la familia RHEL actual.

Las capas que realmente tocas

No necesitas el modelo completo de siete capas para trabajar con soltura, pero hay cuatro niveles que aparecen inevitablemente en el diagnóstico diario:

  1. Enlace: una interfaz de red con una dirección MAC, conectada a un segmento. Es la capa de ip link. ARP (ip neigh) asocia direcciones IP con direcciones MAC dentro de un segmento.
  2. Red: direcciones IP y rutas. Llevar un paquete desde este host hacia un destino es cosa de ip addr e ip route.
  3. Transporte: puertos TCP y UDP, conexiones, sockets en escucha. Es la capa de ss.
  4. Aplicación: HTTP, DNS, TLS. curl, dig, openssl s_client.

Una dirección se escribe con una longitud de prefijo: 10.0.3.17/24 significa que los primeros 24 bits identifican la red, así que las direcciones de 10.0.3.0 a 10.0.3.255 son locales y todo lo demás tiene que pasar por un router. Equivocarse con ese número produce el desesperante síntoma de que algunos destinos funcionan y otros no.

Tres rangos privados —10.0.0.0/8, 172.16.0.0/12 y 192.168.0.0/16— están reservados para uso interno y nunca se enrutan en la Internet pública, y por eso las VPC de los clouds y las redes domésticas se parecen tanto. 127.0.0.0/8 es el loopback, que vive por completo dentro del kernel. Las direcciones IPv6 conviven con IPv4 en la mayoría de las redes modernas; las direcciones fe80::/10 son link-local, válidas solo en un segmento, y siempre llevan un sufijo %interface cuando las usas.

Interfaces, direcciones y rutas

$ ip -brief addr
lo               UNKNOWN        127.0.0.1/8 ::1/128
eth0             UP             10.0.3.17/24 fe80::5054:ff:fe12:3456/64

$ ip route
default via 10.0.3.1 dev eth0 proto dhcp src 10.0.3.17 metric 100
10.0.3.0/24 dev eth0 proto kernel scope link src 10.0.3.17

La opción -brief ofrece la vista resumida; ip addr a secas añade la MTU, los flags y los tiempos de vida. Lee la tabla de rutas del prefijo más específico al menos específico: el kernel elige el prefijo coincidente más largo, así que 10.0.3.0/24 se encarga del segmento local y default recoge todo lo demás enviándolo a la puerta de enlace 10.0.3.1.

ip route get responde a la pregunta directamente y te ahorra tener que descifrar la tabla a ojo:

$ ip route get 93.184.216.34
93.184.216.34 via 10.0.3.1 dev eth0 src 10.0.3.17 uid 1000

Los cambios hechos con ip son inmediatos y se pierden al reiniciar, lo que los hace ideales para pruebas e inadecuados para la configuración:

sudo ip link set eth0 up
sudo ip addr add 10.0.3.200/24 dev eth0
sudo ip route add 10.50.0.0/16 via 10.0.3.254
sudo ip addr flush dev eth0            # careful: this drops your SSH session

La configuración persistente corresponde al gestor que use la distribución: YAML de netplan en /etc/netplan/ en Ubuntu Server, perfiles de conexión de NetworkManager (nmcli) en escritorios y servidores Fedora y de la familia RHEL, o archivos .network de systemd-networkd en imágenes mínimas.

sudo netplan try                    # applies with an automatic rollback after 120 s
nmcli con mod eth0 ipv4.addresses 10.0.3.17/24 ipv4.gateway 10.0.3.1 ipv4.method manual
nmcli con up eth0

netplan try y el rollback de NetworkManager existen porque un cambio de dirección erróneo en un host remoto te deja fuera. Úsalos, o programa un reinicio antes de hacer el cambio.

Resolución de nombres

Los programas no llaman al DNS directamente. Llaman al resolver de la biblioteca de C, que consulta /etc/nsswitch.conf para decidir el orden de las fuentes: normalmente files (es decir, /etc/hosts), luego un stub resolver local y después el DNS. Por tanto, /etc/hosts gana al DNS, lo que lo convierte a la vez en una forma útil de forzar valores y en una causa habitual de «solo funciona en ese servidor».

En los sistemas que usan systemd-resolved —la opción por defecto en Ubuntu y cada vez más en otras distribuciones—, /etc/resolv.conf es un enlace simbólico a un archivo stub que apunta a 127.0.0.53. Editarlo a mano no sirve de nada: se regenera. Consulta mejor la configuración real:

$ resolvectl status | sed -n '1,12p'
Global
       Protocols: LLMNR=resolve -mDNS +DNSOverTLS DNSSEC=no/unsupported
Link 2 (eth0)
Current Scopes: DNS
     DNS Servers: 10.0.0.2 10.0.0.3
      DNS Domain: internal.example.com

$ resolvectl query api.internal.example.com
api.internal.example.com: 10.0.4.21

dig es la herramienta para interrogar al propio DNS, saltándose por completo la biblioteca:

dig +short api.example.com            # just the answer
dig api.example.com A +noall +answer  # the answer section with TTLs
dig @1.1.1.1 example.com MX           # ask a specific server
dig +trace example.com                # follow delegation from the root
dig -x 93.184.216.34                  # reverse lookup

Merece la pena interiorizar la distinción de las preguntas frecuentes de arriba: dig te dice lo que responde el DNS; getent hosts name, lo que obtendrán tus aplicaciones. Cuando no coinciden, la respuesta está en /etc/hosts, en nsswitch.conf o en el stub resolver, no en la zona.

La caché añade su propia dosis de confusión. El TTL de un registro determina cuánto tiempo pueden conservar los resolvers la respuesta antigua, así que un cambio que «no se ha propagado» suele ser simplemente un TTL que aún no ha caducado. Vacía la caché local con sudo resolvectl flush-caches y consulta el servidor autoritativo con dig @ns1.example.com antes de dar por hecho que algo está roto.

Puertos y sockets

ss sustituye a netstat y es muchísimo más rápido en hosts con mucha carga. Las opciones que conviene memorizar son -l (en escucha), -t/-u (TCP/UDP), -n (numérico, sin resolución de nombres) y -p (proceso propietario, requiere root).

$ sudo ss -ltnp
State  Recv-Q Send-Q  Local Address:Port  Peer Address:Port Process
LISTEN 0      4096        127.0.0.1:5432       0.0.0.0:*     users:(("postgres",pid=1842,fd=5))
LISTEN 0      511           0.0.0.0:80         0.0.0.0:*     users:(("nginx",pid=1201,fd=6))
LISTEN 0      128           0.0.0.0:22         0.0.0.0:*     users:(("sshd",pid=980,fd=3))

La columna de dirección local es lo primero que hay que mirar cuando un servicio resulta inaccesible. 127.0.0.1:5432 solo acepta conexiones del mismo host; 0.0.0.0:80 las acepta de cualquier origen y por cualquier interfaz; [::]:80 es el equivalente en IPv6, y en Linux un socket enlazado a [::] suele aceptar también conexiones IPv4. Enlazar un servicio al loopback y ponerlo detrás de un reverse proxy es una buena opción por defecto, y es exactamente lo que hace la guía de la pila LAMP con PHP-FPM.

Otras formas útiles:

ss -tan state established             # current TCP connections
ss -tn dst 10.0.4.21                  # connections to one host
ss -s                                 # summary counters by protocol
ss -tlnp 'sport = :8080'              # who owns port 8080

En un socket en escucha, Recv-Q es el número de conexiones que esperan a ser aceptadas; un valor que se mantiene distinto de cero indica que la aplicación no acepta lo bastante rápido. En un socket establecido, las colas muestran los bytes sin leer y sin confirmar, y así es como detectas a un lector atascado.

Los puertos por debajo de 1024 son privilegiados: solo root, o un proceso con CAP_NET_BIND_SERVICE, puede enlazarse a ellos. Por eso los servidores de aplicaciones escuchan en el 8080 y un proxy o una unidad socket de systemd se ocupa del 80 y del 443.

Firewalls

Los kernels modernos filtran con nftables, que sustituyó a iptables. La mayoría de los administradores lo manejan a través de un front end.

ufw (Debian y Ubuntu) es minimalista a propósito:

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH                 # named application profile
sudo ufw allow 80,443/tcp
sudo ufw allow from 10.0.0.0/8 to any port 5432 proto tcp
sudo ufw enable
sudo ufw status verbose

firewalld (familia RHEL) organiza las reglas en zonas, y cada zona se aplica a un conjunto de interfaces u orígenes:

sudo firewall-cmd --get-active-zones
sudo firewall-cmd --zone=public --add-service=https --permanent
sudo firewall-cmd --zone=public --add-port=8080/tcp --permanent
sudo firewall-cmd --reload
sudo firewall-cmd --list-all

La opción --permanent escribe la regla en disco sin aplicarla; sin ella, se aplica sin guardarse. Hacer ambas cosas y después --reload es la secuencia fiable, y la forma que solo actúa en tiempo de ejecución es una auténtica red de seguridad cuando editas reglas por SSH, porque un reinicio deshace el error.

Aunque uses un front end, vale la pena entender cómo se escriben directamente las reglas de nftables:

table inet filter {
  chain input {
    type filter hook input priority filter; policy drop;
    ct state established,related accept
    iif lo accept
    ip protocol icmp accept
    tcp dport { 22, 80, 443 } accept
    tcp dport 5432 ip saddr 10.0.0.0/8 accept
  }
}

Léelo de arriba abajo: por defecto se descarta todo, las respuestas a conexiones iniciadas por este host se aceptan gracias al seguimiento de conexiones, el loopback no tiene restricciones, y quedan abiertos tres puertos más un puerto de base de datos restringido por origen. sudo nft list ruleset muestra lo que está cargado, y /etc/nftables.conf junto con systemctl enable nftables lo hace persistente.

Dos advertencias. Primera: permite siempre SSH antes de establecer una política de denegación por defecto, y mantén una segunda sesión abierta. Segunda: en un entorno cloud hay un segundo firewall —un security group o una ACL de red— completamente fuera de la máquina; un paquete bloqueado ahí nunca llega al host, y nada en nft list ruleset te dará una pista.

Diagnosticar de fuera hacia dentro

ping comprueba la alcanzabilidad por ICMP, que muchas redes y la mayoría de los balanceadores de carga en cloud bloquean deliberadamente, así que un ping fallido demuestra muy poco. Estos comandos demuestran más:

curl -sS -o /dev/null -w '%{http_code} %{time_total}s\n' https://example.com/health
curl -v --connect-timeout 3 https://api.internal:8443/status
curl --resolve api.example.com:443:10.0.4.21 https://api.example.com/   # bypass DNS, test one backend
openssl s_client -connect example.com:443 -servername example.com </dev/null | head -20
mtr -rwc 20 10.0.4.21                 # traceroute plus per-hop loss, over 20 cycles
tcpdump -ni eth0 'tcp port 5432 and host 10.0.4.21'

curl -v es la herramienta de diagnóstico más valiosa de la lista porque narra cada etapa: resolución DNS, conexión TCP, handshake TLS con la cadena de certificados, petición y cabeceras de respuesta. El punto en el que se detiene te dice qué capa ha fallado. Si se queda colgado en «Trying 10.0.4.21:443», hay un firewall descartando paquetes en silencio; un «Connection refused» inmediato significa que el paquete llegó y no había nada escuchando: problemas muy distintos con el mismo síntoma visible para el usuario.

tcpdump es el último recurso y el definitivo: si ves salir el SYN y no vuelve ningún SYN-ACK, el problema está más allá de este host. Guarda la captura en un archivo con -w capture.pcap cuando necesites analizarla con calma.

SSH como fontanería de red

Además de para iniciar sesión, SSH sirve para redirigir conexiones TCP, y así es como llegas a una base de datos o a una interfaz de administración que no está expuesta públicamente.

# Local forward: localhost:15432 on my laptop -> db.internal:5432, via the bastion
ssh -N -L 15432:db.internal:5432 [email protected]
psql -h 127.0.0.1 -p 15432 -U app orders

# SOCKS proxy: a browser can then reach anything the bastion can
ssh -N -D 1080 [email protected]

# Jump host in one hop, without an agent on the bastion
ssh -J [email protected] [email protected]

-N significa «sin comando remoto»: obtienes un túnel, sin shell. Pon los detalles una sola vez en ~/.ssh/config y la complejidad desaparece:

Host bastion
  HostName bastion.example.com
  User alice
  IdentityFile ~/.ssh/id_ed25519

Host db
  HostName db.internal
  User alice
  ProxyJump bastion
  LocalForward 15432 db.internal:5432

El agent forwarding (-A) es cómodo pero arriesgado: root en el host intermedio puede usar tu agente para autenticarse en otros sitios haciéndose pasar por ti. ProxyJump consigue el mismo resultado sin exponer el agente y debería ser la opción por defecto.

Un orden de operaciones

Cuando algo no consigue llegar a otra cosa, avanza hacia fuera y detente en el primer fallo:

  1. ¿Está escuchando, y en qué dirección? sudo ss -ltnp | grep :<port>.
  2. ¿Responde en local? curl -v http://127.0.0.1:<port>/. Si no, el problema es la aplicación: revisa sus logs con journalctl -u, como se explica en procesos y systemd.
  3. ¿Se resuelve el nombre, y a la dirección correcta? getent hosts <name> y dig +short <name>.
  4. ¿Hay una ruta? ip route get <address>.
  5. ¿Lo permite el firewall del host? sudo nft list ruleset, ufw status, firewall-cmd --list-all.
  6. ¿Lo bloquea algo fuera del host? Security groups del cloud, ACL de red, un proxy corporativo. Prueba desde una tercera máquina del mismo segmento para separar las reglas del host de las de la red.
  7. ¿Qué dicen los paquetes? tcpdump en ambos extremos, si llegas hasta aquí.

Errores comunes

  • Enlazar a 127.0.0.1 y esperar acceso remoto. Comprueba la dirección local en ss antes de tocar el firewall.
  • Editar /etc/resolv.conf cuando lo gestiona systemd-resolved. El archivo se regenera; configura el resolver en su lugar.
  • Fiarse de ping como prueba de alcanzabilidad. ICMP está filtrado en muchos entornos; prueba el puerto real con curl o nc -vz.
  • Aplicar una política de firewall de denegación por defecto sin una regla para SSH. Añade primero la regla que permite el acceso, después la política, y mantén una segunda sesión abierta.
  • Configurar una dirección con ip addr add y darlo por terminado. Desaparece en el siguiente arranque; escríbela en netplan, NetworkManager o systemd-networkd.
  • Olvidarse del firewall del lado del cloud. La mitad de los tickets de «el puerto está abierto pero no puedo conectar» terminan en un security group.
  • Ignorar IPv6. A un nombre con registros A y AAAA se puede llegar por IPv6, donde tus reglas de firewall o tu enrutamiento pueden ser completamente distintos. Prueba por separado con curl -4 y curl -6.

Ideas clave

  • Diagnostica en el orden de las capas —interfaz, dirección, ruta, nombre, puerto, firewall— y detente en lo primero que falle.
  • ip addr, ip route e ip route get sustituyen a ifconfig y route; los cambios con ip son inmediatos y, por diseño, no persistentes.
  • La resolución pasa por nsswitch.conf y posiblemente por un stub resolver, de modo que getent hosts refleja lo que ven las aplicaciones mientras que dig refleja lo que dice el DNS.
  • ss -ltnp y su columna de dirección local responden a la mayoría de los casos de «servicio inalcanzable» antes incluso de que entre en juego el firewall.
  • nftables es el filtro del kernel; ufw y firewalld son front ends cómodos, y un security group del cloud es un segundo firewall invisible.
  • curl -v narra DNS, TCP, TLS y HTTP en un solo comando, y tcpdump zanja las discusiones sobre si los paquetes llegaron a salir.
  • ssh -L, -D y ProxyJump, escritos una sola vez en ~/.ssh/config, te dan acceso seguro a servicios privados sin exponerlos.

Fuentes y lecturas recomendadas

Preguntas frecuentes

¿Por qué no está ifconfig en mi servidor?
El paquete net-tools, que proporcionaba ifconfig, netstat, route y arp, lleva años sin mantenimiento y ya no se instala por defecto. Sus sustitutos vienen de iproute2: ip addr en lugar de ifconfig, ip route en lugar de route, ip neigh en lugar de arp y ss en lugar de netstat. Entienden funcionalidades que net-tools nunca llegó a soportar, como varias direcciones por interfaz o el enrutamiento basado en políticas, así que aprende los nombres nuevos en vez de instalar el paquete antiguo.
El servicio está en marcha pero no consigo acceder a él desde otra máquina. ¿Por dónde empiezo?
Comprueba a qué dirección está enlazado antes de echarle la culpa a la red. Ejecuta ss -ltnp y mira la columna de dirección local: 127.0.0.1:8080 solo acepta conexiones locales, mientras que 0.0.0.0:8080 las acepta todas. Si el enlace es correcto, prueba con curl desde el propio servidor, luego desde fuera, después revisa el firewall del host y, por último, cualquier security group del cloud. Avanzar hacia fuera en ese orden aísla la capa culpable en un par de minutos.
¿Por qué dig devuelve una respuesta cuando ping no consigue resolver el mismo nombre?
dig consulta directamente un servidor DNS e ignora el resto de la maquinaria de resolución. Los programas normales pasan por la biblioteca de C, que consulta /etc/nsswitch.conf y, por tanto, /etc/hosts, mDNS y cualquier stub resolver local antes del DNS o en su lugar. Usa getent hosts nombre para reproducir exactamente lo que ve una aplicación; si ambos resultados difieren, el problema está en la configuración, no en el DNS.
¿Qué diferencia hay entre la redirección de puertos SSH local y la remota?
La redirección local, ssh -L, abre un puerto en tu máquina y canaliza las conexiones a través del servidor SSH hasta un destino al que ese servidor puede llegar; es la forma habitual de acceder a una base de datos que solo escucha en una red privada. La redirección remota, ssh -R, abre un puerto en el servidor SSH y devuelve las conexiones a tu máquina, lo que expone algo local al lado remoto. Casi siempre lo que necesitas es la local.