Redes básicas: direcciones IP, puertos y NAT, explicados para principiantes

Cada vez que abres una web, la petición sale de tu equipo, pasa por tu router y cruza internet hasta un servidor. Para que llegue, y para que la respuesta encuentre el camino de vuelta, hacen falta tres cosas: una dirección IP, que dice a qué equipo va; un puerto, que dice a qué programa de ese equipo, y casi siempre NAT, el truco con el que tu router hace que toda tu casa comparta una sola dirección pública. Esta guía los explica desde cero, con esquemas y con una red de pruebas real.

La red de pruebas. Todo lo que verás se ejecutó en tres contenedores que imitan una casa y un trozo de internet: un portátil (192.168.77.20), un router (192.168.77.1 por dentro y 203.0.113.10 por fuera) y un servidor (203.0.113.80) con una pequeña web que responde diciendo desde qué dirección le llega cada visita. Las direcciones 203.0.113.x están reservadas para documentación: hacen de internet sin serlo.

Las redes en tres ideas

  • La IP es la dirección del equipo. Como la dirección de un edificio: sin ella, el paquete no sabe a dónde ir. En tu casa cada aparato tiene una; en internet, cada servidor también.
  • El puerto es la puerta del programa. En un mismo equipo pueden estar a la vez un servidor web, uno de SSH y una base de datos. El puerto (80, 22, 5432…) dice cuál de ellos recibe la conexión.
  • NAT comparte una dirección pública entre muchos. Los aparatos de tu casa usan direcciones privadas que no sirven en internet. El router sale en nombre de todos con la suya y reparte las respuestas.

La dirección IP

Una dirección IPv4, la más común todavía, son cuatro números del 0 al 255 separados por puntos, como 192.168.77.20. Cada número ocupa 8 bits, así que la dirección entera son 32 bits: hay algo más de 4,000 millones de combinaciones, que en internet se agotaron hace años. En Linux, tus direcciones se ven con ip addr:

ip addr show
salida en el portátil de pruebas1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host proto kernel_lo 
       valid_lft forever preferred_lft forever
2: eth0@if1939: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether fe:b7:e5:23:df:eb brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 192.168.77.20/24 brd 192.168.77.255 scope global eth0
       valid_lft forever preferred_lft forever

Hay dos interfaces: lo, la de bucle local (loopback), con la que el equipo se habla a sí mismo en 127.0.0.1, y eth0, la tarjeta de red, con la dirección 192.168.77.20/24. En un equipo normal la verás con nombres como enp3s0 o wlan0 (el @if… es cosa de los contenedores). La línea link/ether es la dirección MAC, el número de serie de la tarjeta, que solo cuenta dentro de tu red local. Si te basta con lo esencial, ip -brief addr lo resume en una línea por interfaz:

ip -brief addr show
salidalo               UNKNOWN        127.0.0.1/8 ::1/128 
eth0@if1939      UP             192.168.77.20/24 

En otros sistemas (estas órdenes no se ejecutaron en las pruebas, que eran en Linux):

SistemaVer mis direcciones
Windowsipconfig (o ipconfig /all para ver también la MAC y el DNS)
macOSifconfig, o ipconfig getifaddr en0 para la del wifi
Linuxip addr o ip -brief addr

La máscara: qué parte de la dirección es la red

El /24 que acompaña a la dirección es la máscara de red, escrita en notación CIDR. Dice cuántos bits, empezando por la izquierda, identifican la red; el resto identifican al equipo dentro de ella. Con /24, los tres primeros números son la red y el último es el equipo: todos los aparatos de la casa de pruebas empiezan por 192.168.77. y se distinguen por el final.

Una dirección IPv4 por dentro: 192.168.77.20/24 cuatro números de 0 a 255 separados por puntos; el /24 dice cuántos bits, contando desde la izquierda, son la red 192 11000000 8 bits . 168 10101000 8 bits . 77 01001101 8 bits . 20 00010100 8 bits la red: 24 bits, iguales en todos los equipos de tu casa el equipo: 8 bits Máscara 255.255.255.0 = /24 · red 192.168.77.0 · equipos de la .1 a la .254 · difusión .255 la primera dirección nombra a la red y la última sirve para hablar a todos a la vez: quedan 254 para equipos

Esquema: decodigo.com.

ipcalc hace las cuentas por ti y enseña los bits:

ipcalc 192.168.77.20/24
salidaAddress:   192.168.77.20        11000000.10101000.01001101. 00010100
Netmask:   255.255.255.0 = 24   11111111.11111111.11111111. 00000000
Wildcard:  0.0.0.255            00000000.00000000.00000000. 11111111
=>
Network:   192.168.77.0/24      11000000.10101000.01001101. 00000000
HostMin:   192.168.77.1         11000000.10101000.01001101. 00000001
HostMax:   192.168.77.254       11000000.10101000.01001101. 11111110
Broadcast: 192.168.77.255       11000000.10101000.01001101. 11111111
Hosts/Net: 254                   Class C, Private Internet

La primera dirección (.0) nombra a la red y la última (.255, la de difusión o broadcast) sirve para hablar a todos los equipos a la vez, así que quedan 254 para equipos. La máscara también se escribe a la antigua, 255.255.255.0, que es lo mismo: 24 unos seguidos de 8 ceros. Cuanto más pequeño el número tras la barra, más grande la red:

MáscaraTambién escritaDirecciones para equiposUso típico
/32255.255.255.2551una sola dirección, en reglas de cortafuegos
/24255.255.255.0254la red de casa o de una oficina pequeña
/16255.255.0.065,534una empresa o un campus
/8255.0.0.016,777,214toda la red privada 10.x.x.x
ipcalc -b 10.0.0.0/16
salida: con /16 caben 65,534 equiposAddress:   10.0.0.0             
Netmask:   255.255.0.0 = 16     
Wildcard:  0.0.255.255          
=>
Network:   10.0.0.0/16          
HostMin:   10.0.0.1             
HostMax:   10.0.255.254         
Broadcast: 10.0.255.255         
Hosts/Net: 65534                 Class A, Private Internet

Direcciones privadas, públicas y especiales

No todas las direcciones sirven en internet. Hay rangos privados, reservados para redes internas, que cualquiera puede usar en su casa o su empresa y que los routers de internet no encaminan. Por eso tu portátil y el de tu vecino pueden tener la misma 192.168.1.20 sin molestarse. Las públicas, en cambio, son únicas en todo internet y las reparten los proveedores.

RangoQué es
10.0.0.0/8privado: redes grandes, empresas, nubes
172.16.0.0/12 (172.16 a 172.31)privado: lo usa, por ejemplo, Docker
192.168.0.0/16privado: la gran mayoría de las redes de casa
127.0.0.0/8bucle local: 127.0.0.1, el propio equipo (localhost)
169.254.0.0/16enlace local: la dirección que se pone un equipo cuando no encuentra quién le dé una (si la ves, falla el DHCP)
100.64.0.0/10CGNAT: la que te da un proveedor que comparte una pública entre muchos clientes
el resto, casi todopúblico: 8.8.8.8, la IP de decodigo.com…

Python lo sabe clasificar con su módulo ipaddress:

import ipaddress
for d in ['192.168.77.20', '10.4.0.7', '172.20.1.5', '8.8.8.8', '127.0.0.1', '169.254.10.2', '203.0.113.80']:
    ip = ipaddress.ip_address(d)
    tipo = ('loopback' if ip.is_loopback else 'enlace local' if ip.is_link_local
            else 'documentación' if ip in ipaddress.ip_network('203.0.113.0/24')
            else 'privada' if ip.is_private else 'pública')
    print(f'{{d:15}} {{tipo}}')
salida192.168.77.20   privada
10.4.0.7        privada
172.20.1.5      privada
8.8.8.8         pública
127.0.0.1       loopback
169.254.10.2    enlace local
203.0.113.80    documentación

¿Quién le da la dirección a cada aparato? Normalmente el router, con DHCP: cuando un equipo se conecta, pide una dirección libre y el router se la presta durante un tiempo, junto con la máscara, la puerta de enlace y el servidor DNS. Para saber tu dirección pública, la que ve internet, tienes que preguntar a alguien de fuera, por ejemplo con curl ifconfig.me (no la publicamos: sería la de nuestra conexión).

IPv6 en dos minutos

Para acabar con la escasez se creó IPv6: direcciones de 128 bits escritas en hexadecimal, en ocho grupos separados por dos puntos. Hay tantas que no hace falta NAT. Como son largas, se abrevian: se quitan los ceros a la izquierda de cada grupo y una racha de grupos a cero se cambia por ::, una sola vez por dirección.

la forma completa y la abreviada, según el módulo ipaddress de Python2001:0db8:0000:0000:0000:ff00:0042:8329  ->  2001:db8::ff00:42:8329
0000:0000:0000:0000:0000:0000:0000:0001  ->  ::1

::1 es el equivalente de 127.0.0.1, y las que empiezan por fe80: son de enlace local. Si tu proveedor ya da IPv6, tu equipo tendrá a la vez una dirección de cada tipo: el registro AAAA del DNS es el que guarda las IPv6, como contamos en la guía de DNS.

La puerta de enlace: cómo se sale de tu red

Un equipo solo sabe llegar directamente a los de su misma red. Para todo lo demás usa la puerta de enlace (gateway), casi siempre el router. Lo dice la tabla de rutas:

ip route
salida en el portátildefault via 192.168.77.1 dev eth0 
192.168.77.0/24 dev eth0 proto kernel scope link src 192.168.77.20 

Se lee así: «lo de la red 192.168.77.0/24 lo entrego yo directamente por eth0; para todo lo demás (default), se lo paso a 192.168.77.1». ip route get te dice qué camino seguiría un paquete concreto:

ip route get 203.0.113.80
salida203.0.113.80 via 192.168.77.1 dev eth0 src 192.168.77.20 uid 0 
    cache 

Y ping comprueba si un equipo responde y cuánto tarda la ida y vuelta. Si no responde, no siempre es que esté apagado: muchos servidores y cortafuegos ignoran el ping a propósito.

ping -c 3 192.168.77.1
salidaPING 192.168.77.1 (192.168.77.1) 56(84) bytes of data.
64 bytes from 192.168.77.1: icmp_seq=1 ttl=64 time=0.079 ms
64 bytes from 192.168.77.1: icmp_seq=2 ttl=64 time=0.105 ms
64 bytes from 192.168.77.1: icmp_seq=3 ttl=64 time=0.086 ms

--- 192.168.77.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2044ms
rtt min/avg/max/mdev = 0.079/0.090/0.105/0.011 ms

tracepath (o traceroute; en Windows, tracert) enseña los saltos del camino. Desde el portátil hasta el servidor hay dos: el router y el destino.

tracepath -n 203.0.113.80
salida, ya con el NAT activado (lo verás más abajo) 1?: [LOCALHOST]                      pmtu 1500
 1:  192.168.77.1                                          0.075ms 
 1:  192.168.77.1                                          0.022ms 
 2:  203.0.113.80                                          0.044ms reached
     Resume: pmtu 1500 hops 2 back 2 

En Windows, la tabla de rutas se ve con route print, y en macOS con netstat -rn.

Los puertos: qué programa recibe la conexión

La IP te lleva hasta el equipo, pero en un equipo puede haber muchos programas esperando conexiones. El puerto, un número del 0 al 65,535, dice a cuál va. Un programa que espera conexiones escucha en un puerto; si no hay nadie escuchando, el puerto está cerrado.

La IP lleva al equipo; el puerto, al programa como un edificio: la dirección es la calle y el número, y el puerto es la puerta de cada oficina Tu portátil 192.168.77.20 el navegador sale por un puerto efímero, uno libre que elige el sistema, en Linux del 32768 al 60999 Servidor 203.0.113.80 :22 SSH sshd · escuchando :80 HTTP servidor web · escuchando :443 HTTPS servidor web · escuchando :5432 PostgreSQL nadie · cerrado: «connection refused» 203.0.113.80:80 IP + puerto = dónde y a quién una conexión queda identificada por cuatro datos: IP y puerto de origen, IP y puerto de destino

Esquema: decodigo.com.

Los servicios conocidos tienen su puerto por convenio, y por eso no hace falta escribirlo: https://decodigo.com va al 443 sin decirlo. Si quieres otro, se pone tras dos puntos: http://localhost:8000.

PuertoServicio
22SSH, el acceso remoto (lo explicamos en la guía de SSH)
53DNS
80 · 443HTTP y HTTPS, las webs
25 · 587 · 993correo: envío entre servidores, envío desde tu programa y lectura por IMAP
3306 · 5432 · 6379bases de datos: MySQL, PostgreSQL y Redis
3000 · 5173 · 8000 · 8080los típicos de los servidores de desarrollo

Los puertos por debajo del 1024 son «privilegiados»: en Linux, solo un administrador puede ponerse a escuchar en ellos. Cuando eres tú quien se conecta, tu equipo usa como origen un puerto efímero, uno libre cualquiera; en Linux, por omisión, del 32768 al 60999. Tienes una lista más larga de puertos conocidos en el blog.

Para ver qué está escuchando en tu equipo, usa ss (-t TCP, -l los que escuchan, -n en números, -p qué programa). En el portátil de pruebas pusimos dos servidores, uno en el 8000 y otro en el 8001:

ss -tlnp
salida, sin una línea interna de DockerState  Recv-Q Send-Q Local Address:Port  Peer Address:PortProcess                          
LISTEN 0      5          127.0.0.1:8000       0.0.0.0:*    users:(("python3",pid=105,fd=3))
LISTEN 0      5            0.0.0.0:8001       0.0.0.0:*    users:(("python3",pid=111,fd=3))

En Windows se usa netstat -ano, y en macOS lsof -iTCP -sTCP:LISTEN -n -P. Para comprobar desde otro equipo si un puerto está abierto, nc -zv intenta conectar sin enviar nada:

nc -zv 192.168.77.20 8001
nc -zv 192.168.77.20 9999
salida desde el routerConnection to 192.168.77.20 8001 port [tcp/*] succeeded!
nc: connect to 192.168.77.20 port 9999 (tcp) failed: Connection refused

TCP y UDP

Los puertos existen en dos protocolos de transporte distintos, y el 53 de TCP no es el mismo que el 53 de UDP.

TCPUDP
Cómo funcionaabre una conexión antes de hablar y confirma que cada dato llega, en ordenenvía cada mensaje suelto, sin conexión ni confirmación
Si se pierde algolo reenvíase pierde: lo arregla el programa, si quiere
Ventajafiablerápido y ligero
Lo usanwebs (HTTP/1.1 y HTTP/2), SSH, correo, bases de datosDNS, vídeollamadas, juegos en línea, HTTP/3 (QUIC)

Las órdenes de esta guía (curl, nc, ss -t) trabajan con TCP. Para ver lo que escucha en UDP, ss -uln.

127.0.0.1 o 0.0.0.0: para quién escucha un servicio

Es el problema de redes más común al programar: «en mi equipo funciona, pero desde el móvil o desde otro contenedor no». Un servidor no solo elige puerto, también elige en qué dirección escucha:

  • 127.0.0.1 (o localhost): solo acepta conexiones del propio equipo. Es lo que hacen por omisión muchos servidores de desarrollo, por seguridad.
  • 0.0.0.0: acepta conexiones por cualquiera de las direcciones del equipo, también desde la red.

En el ss de antes lo tienes: el servidor del 8000 escucha en 127.0.0.1 y el del 8001 en 0.0.0.0. Desde el propio portátil, el del 8000 responde:

curl http://127.0.0.1:8000/
salida en el portátilHola. Te veo llegar desde 127.0.0.1, puerto 47682

Pero desde el router, que está en la misma red, solo responde el del 8001:

curl http://192.168.77.20:8000/
curl http://192.168.77.20:8001/
salida desde el routercurl: (7) Failed to connect to 192.168.77.20 port 8000 after 0 ms: Could not connect to server
Hola. Te veo llegar desde 192.168.77.1, puerto 43580

Si tu servidor de desarrollo no se ve desde fuera, búscale la opción para escuchar en todas: --host 0.0.0.0 en Vite, flask run --host=0.0.0.0, uvicorn app:app --host 0.0.0.0… Dentro de un contenedor de Docker es obligatorio, porque para el contenedor tu propio equipo ya es «otra máquina».

NAT: muchos equipos, una sola IP pública

Las direcciones privadas no sirven en internet: si el portátil intenta hablar con el servidor tal cual, la petición puede salir, pero la respuesta no tiene cómo volver a una 192.168.77.20. En la red de pruebas, con el router encaminando pero sin NAT:

curl -m 5 http://203.0.113.80/
salida en el portátil, sin NATcurl: (28) Connection timed out after 5003 milliseconds

La solución es NAT (Network Address Translation, traducción de direcciones). Cuando un paquete sale de casa, el router cambia la dirección de origen por la suya pública, y si hace falta también el puerto, y apunta el cambio en una tabla. Cuando llega la respuesta, mira la tabla y la devuelve a quien la pidió. Todos los routers de casa lo hacen sin que tengas que tocar nada; en el de pruebas lo activamos con una regla de iptables:

iptables -t nat -A POSTROUTING -o eth1 -j MASQUERADE

Y ahora sí:

curl http://203.0.113.80/
salida en el portátil, dos veces, con NATHola. Te veo llegar desde 203.0.113.10, puerto 47430
Hola. Te veo llegar desde 203.0.113.10, puerto 47438

El servidor no ve al portátil: ve al router, 203.0.113.10. Cada petición sale por un puerto distinto, el que el router usa para saber a quién devolver la respuesta. Lo mismo te pasa a ti en internet: las webs que visitas ven la dirección pública de tu router, no la de tu equipo.

NAT: toda tu casa sale a internet con una sola dirección la red de pruebas de esta guía: el servidor nunca ve las direcciones de dentro, solo la del router Tu casa: red privada 192.168.77.0/24 Portátil 192.168.77.20 Móvil 192.168.77.21 Tele 192.168.77.30 Router dentro: 192.168.77.1 fuera: 203.0.113.10 cambia el remitente al salir y lo deshace con las respuestas Internet servidor 203.0.113.80:80 «Hola. Te veo llegar desde 203.0.113.10» no sabe nada de tu casa 1 2 3 4 La tabla del router (ejemplo con dos conexiones a la vez) dentro fuera destino 192.168.77.20:51000 ↔ 203.0.113.10:51000 203.0.113.80:80 192.168.77.21:51000 ↔ 203.0.113.10:62001 203.0.113.80:80 Redirección de puertos («abrir un puerto») lo que llegue a 203.0.113.10:8080 → se manda a 192.168.77.20:8001 sin ella, nadie de fuera puede empezar una conexión hacia tu casa

Esquema: decodigo.com. Los puertos de la tabla son un ejemplo; Linux conserva el puerto de origen si está libre y lo cambia si otra conexión ya lo usa.

El NAT tiene un efecto secundario que a menudo se confunde con seguridad: desde fuera nadie puede empezar una conexión hacia dentro, porque el router no sabe a qué equipo mandarla. El servidor ni siquiera tiene camino hacia la red privada:

ping -c 1 192.168.77.20
salida en el servidorping: connect: Network is unreachable

Abrir un puerto: la redirección

¿Y si quieres que alguien de fuera llegue a un servicio de tu casa, como un servidor de juegos o una cámara? Hay que pedirle al router una redirección de puertos (port forwarding), lo que en la configuración del router suele llamarse «abrir un puerto». Antes, el servidor llama al 8080 del router y no hay nadie:

curl http://203.0.113.10:8080/
salida en el servidorcurl: (7) Failed to connect to 203.0.113.10 port 8080 after 0 ms: Could not connect to server

Le decimos al router que lo que llegue a su puerto 8080 lo mande al 8001 del portátil:

iptables -t nat -A PREROUTING -i eth1 -p tcp --dport 8080 -j DNAT --to-destination 192.168.77.20:8001
la misma petición desde el servidor, despuésHola. Te veo llegar desde 203.0.113.80, puerto 50102

Ahora es el portátil quien ve al servidor, con su dirección real. Es exactamente lo que hace Docker cuando escribes docker run -p 8080:80: una redirección del puerto 8080 de tu equipo al 80 del contenedor (lo contamos en la guía de Docker).

Antes de abrir un puerto, piensa que lo que pongas detrás quedará expuesto a todo internet, y hay programas que recorren todas las direcciones buscando puertos abiertos. Abre solo lo imprescindible, mantén el servicio actualizado y, para entrar en tu red desde fuera, plantéate una VPN. Y si la dirección «de fuera» de tu router empieza por 100.64 a 100.127, estás detrás del CGNAT de tu proveedor: hay otro NAT por encima del tuyo y la redirección no funcionará sin pedírselo a él.

Los errores que más vas a ver

Connection refused: nadie escucha en ese puerto

Has llegado al equipo, pero no hay ningún programa en ese puerto, y el sistema lo dice al momento. Comprueba que el servicio está arrancado, que el puerto es el correcto y, si vienes de otra máquina, que no escucha solo en 127.0.0.1.

salida de nc y de curl contra un puerto sin nadienc: connect to 192.168.77.20 port 9999 (tcp) failed: Connection refused
curl: (7) Failed to connect to 192.168.77.20 port 9999 after 0 ms: Could not connect to server

Timed out: algo se traga los paquetes

No llega respuesta ninguna, ni siquiera un «no». Suele ser un cortafuegos que descarta los paquetes en silencio, una dirección equivocada o un problema de rutas, como el NAT que faltaba antes. Para probarlo, pusimos en el router una regla que descarta lo que va al portátil por la redirección:

iptables -I FORWARD -p tcp -d 192.168.77.20 --dport 8001 -j DROP
salida en el servidor, con time para medir la esperacurl: (28) Connection timed out after 5002 milliseconds

real	0m5.011s
user	0m0.004s
sys	0m0.005s

La diferencia es la pista: un refused llega en milisegundos (hay alguien al otro lado que dice que no); un timeout tarda lo que aguante el programa esperando.

Network is unreachable: no hay camino

El equipo no tiene ninguna ruta hacia esa dirección: no tiene puerta de enlace, el cable o el wifi están caídos o intentas llegar a una red privada ajena, como el servidor de antes.

Address already in use: el puerto ya está ocupado

Solo un programa puede escuchar en cada dirección y puerto. Si arrancas un segundo servidor en el 8001:

salida, última línea del error de PythonOSError: [Errno 98] Address already in use

Busca quién lo tiene con ss -tlnp y ciérralo, o usa otro puerto. Casi siempre es una copia anterior del mismo programa que se quedó abierta.

Resumen

Quiero…Orden (Linux)Windows · macOS
ver mis direcciones IPip -brief addripconfig · ifconfig
ver mi puerta de enlaceip routeroute print · netstat -rn
calcular una redipcalc 192.168.1.20/24
saber si un equipo respondeping -c 3 192.168.1.1ping
ver el camino hasta un equipotracepath -n 1.1.1.1tracert · traceroute
ver qué escucha en mi equiposs -tlnpnetstat -ano · lsof -iTCP -sTCP:LISTEN
comprobar un puerto remotonc -zv equipo 443Test-NetConnection equipo -Port 443 · nc -zv
saber mi IP públicacurl ifconfig.mecurl ifconfig.me

Esta guía forma serie con las de DNS, los métodos HTTP, HTTPS por dentro y SSH: el DNS convierte el nombre en una IP, y con esa IP y un puerto empieza cualquier conexión.

Las pruebas se ejecutaron el 14 de octubre de 2026 en tres contenedores desechables de Debian 13 (iproute2, iptables, ipcalc, curl y netcat) conectados por dos redes aisladas; las salidas están copiadas de esa ejecución, y los puertos efímeros y las direcciones MAC cambian en cada una. Las órdenes de Windows y macOS no se ejecutaron. Los rangos privados están definidos en el RFC 1918, los de documentación en el RFC 5737, el de CGNAT en el RFC 6598 y la notación CIDR en el RFC 4632; la lista oficial de puertos la mantiene la IANA.

Fuente: IETF, RFC 1918, «Address Allocation for Private Internets»