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.
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 foreverHay 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):
| Sistema | Ver mis direcciones |
|---|---|
| Windows | ipconfig (o ipconfig /all para ver también la MAC y el DNS) |
| macOS | ifconfig, o ipconfig getifaddr en0 para la del wifi |
| Linux | ip 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.
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áscara | También escrita | Direcciones para equipos | Uso típico |
|---|---|---|---|
/32 | 255.255.255.255 | 1 | una sola dirección, en reglas de cortafuegos |
/24 | 255.255.255.0 | 254 | la red de casa o de una oficina pequeña |
/16 | 255.255.0.0 | 65,534 | una empresa o un campus |
/8 | 255.0.0.0 | 16,777,214 | toda 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.
| Rango | Qué es |
|---|---|
10.0.0.0/8 | privado: redes grandes, empresas, nubes |
172.16.0.0/12 (172.16 a 172.31) | privado: lo usa, por ejemplo, Docker |
192.168.0.0/16 | privado: la gran mayoría de las redes de casa |
127.0.0.0/8 | bucle local: 127.0.0.1, el propio equipo (localhost) |
169.254.0.0/16 | enlace 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/10 | CGNAT: la que te da un proveedor que comparte una pública entre muchos clientes |
| el resto, casi todo | pú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.
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.
| Puerto | Servicio |
|---|---|
22 | SSH, el acceso remoto (lo explicamos en la guía de SSH) |
53 | DNS |
80 · 443 | HTTP y HTTPS, las webs |
25 · 587 · 993 | correo: envío entre servidores, envío desde tu programa y lectura por IMAP |
3306 · 5432 · 6379 | bases de datos: MySQL, PostgreSQL y Redis |
3000 · 5173 · 8000 · 8080 | los 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.
| TCP | UDP | |
|---|---|---|
| Cómo funciona | abre una conexión antes de hablar y confirma que cada dato llega, en orden | envía cada mensaje suelto, sin conexión ni confirmación |
| Si se pierde algo | lo reenvía | se pierde: lo arregla el programa, si quiere |
| Ventaja | fiable | rápido y ligero |
| Lo usan | webs (HTTP/1.1 y HTTP/2), SSH, correo, bases de datos | DNS, 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(olocalhost): 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.
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).
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 IP | ip -brief addr | ipconfig · ifconfig |
| ver mi puerta de enlace | ip route | route print · netstat -rn |
| calcular una red | ipcalc 192.168.1.20/24 | |
| saber si un equipo responde | ping -c 3 192.168.1.1 | ping |
| ver el camino hasta un equipo | tracepath -n 1.1.1.1 | tracert · traceroute |
| ver qué escucha en mi equipo | ss -tlnp | netstat -ano · lsof -iTCP -sTCP:LISTEN |
| comprobar un puerto remoto | nc -zv equipo 443 | Test-NetConnection equipo -Port 443 · nc -zv |
| saber mi IP pública | curl ifconfig.me | curl 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»
