EQVPS
PanelEmpezar

Cómo comprobar los puertos abiertos de un VPS: qué escucha, qué ve Internet y la sorpresa de Docker

Mira cada puerto en el que escucha tu servidor, encuentra el proceso que hay detrás, escanea el servidor desde fuera como lo haría un atacante y cierra lo que no necesitas. Incluye el puerto de Docker que sigue abierto aunque el firewall esté activo. Cada cifra, medida en un servidor Ubuntu 24.04 recién creado.

Un puerto abierto y olvidado es la forma clásica de entrar en un servidor: una base de datos en una dirección pública, un panel de depuración, un contenedor que publicó un puerto que nadie quería publicar. Comprobarlo lleva un minuto si conoces dos comandos y una trampa. Hicimos todo lo siguiente en un servidor Ubuntu 24.04 nuevo de nuestra imagen y lo escaneamos desde una segunda máquina, así que las salidas son las que verás en tu propio servidor.

1. Qué está escuchando

El comando es ss. Las letras significan TCP, UDP, sockets en escucha, puertos numéricos y nombre del proceso:

sudo ss -tulpn

En un servidor nuevo imprime seis líneas:

Netid State  Local Address:Port  Process
udp   UNCONN    127.0.0.54:53    systemd-resolve
udp   UNCONN 127.0.0.53%lo:53    systemd-resolve
tcp   LISTEN    127.0.0.54:53    systemd-resolve
tcp   LISTEN 127.0.0.53%lo:53    systemd-resolve
tcp   LISTEN       0.0.0.0:22    sshd
tcp   LISTEN          [::]:22    sshd

La columna que importa es Local Address. Te dice quién puede conectarse:

DirecciónQuién llega
127.0.0.1:… o 127.0.0.53Solo los programas de este servidor
0.0.0.0:…Cualquiera por IPv4, salvo que el firewall lo bloquee
[::]:…Cualquiera por IPv6 (y normalmente también IPv4)
10.0.0.5:… (una dirección concreta)Cualquiera que llegue a esa dirección

Así que el servidor nuevo expone exactamente una cosa: SSH en el puerto 22, por IPv4 e IPv6. El resolvedor DNS en 127.0.0.53 es privado.

Para listar solo lo que Internet podría ver, filtra las direcciones de loopback:

sudo ss -tlnp | awk 'NR>1 && $4 !~ /^(127\.|\[::1\])/ {print $4, $6}'

Ejecútalo después de cada instalación. Instalamos nginx, Docker y una pequeña aplicación en Python, y la lista pasó de 2 líneas a 10, entre ellas un docker-proxy en 0.0.0.0:8081 y un socket de containerd en un puerto localhost aleatorio.

2. Qué proceso ocupa un puerto

Cuando un puerto está ocupado y quieres saber por quién:

sudo lsof -i :80 -P -n
sudo fuser 80/tcp
sudo ss -tlnp 'sport = :80'

lsof es el más legible. Para nginx mostró un proceso maestro como root y un worker como www-data, ambos en *:80, para IPv4 e IPv6. fuser imprime solo los PID, útil en scripts. Si necesitas liberar el puerto, detén el servicio en lugar de matar el PID; si no, systemd lo reinicia: sudo systemctl stop nginx.

3. Qué ve Internet

ss muestra la vista del servidor. La de Internet puede ser distinta, porque hay un firewall en medio o porque un servicio está ligado a 127.0.0.1. Para ver la realidad, escanea el servidor desde otra máquina, tu portátil o un segundo servidor. La herramienta es nmap (apt install nmap, o el instalador de nmap.org en Windows y macOS).

nmap your-server-ip
nmap -p- your-server-ip
nmap -p 22,80,443,3306 your-server-ip

La primera forma revisa los 1.000 puertos más comunes, la segunda los 65.535, la tercera una lista. En nuestro servidor nuevo, antes de cualquier firewall:

Not shown: 999 closed tcp ports (reset)
PORT   STATE SERVICE
22/tcp open  ssh
Nmap done: 1 IP address (1 host up) scanned in 6.83 seconds

El escaneo completo de 65.535 puertos tardó 8,9 segundos y encontró el mismo único puerto. Los puertos cerrados responden al instante con un reset, por eso escanearlos es rápido.

Importan tres estados:

  • open: un programa aceptó la conexión. Es tu superficie de ataque.
  • closed: el servidor respondió que allí no escucha nada. Seguro, pero confirma que el servidor existe y lo que no ejecuta.
  • filtered: sin respuesta. Un firewall descartó el paquete. El escáner no puede saber si hay un servicio detrás.

Para un solo puerto sin instalar nada, nc -zv ip-del-servidor 443 responde succeeded o refused, y se queda esperando hasta el tiempo límite en un puerto filtrado. Para UDP, añade -sU a nmap y espera lentitud; en nuestro servidor los tres puertos UDP habituales, 53, 123 y 161, estaban closed.

Una nota para los planes NAT: desde Internet solo llega el puerto SSH que te redirigimos. Un escaneo de la dirección compartida muestra nuestro borde, no tu servidor, y los demás puertos son inaccesibles, escuche lo que escuche en ellos. En un plan con su propia IPv4, cada puerto abierto lo gestionas tú.

4. Cierra lo que no necesitas

Tres formas, de la mejor a la más amplia.

Ligarlo a localhost. Si solo usan un servicio los programas del servidor, haz que escuche en 127.0.0.1. Iniciamos una pequeña aplicación web con --bind 127.0.0.1 8080: ss la mostró en 127.0.0.1:8080 y nmap desde fuera informó 8080/tcp closed. Para bases de datos es el valor por defecto correcto: Redis, MySQL y PostgreSQL escuchan en localhost de serie en Ubuntu, y nuestras guías de Redis y MySQL lo dejan así.

Detener el servicio. sudo systemctl disable --now nombre quita el listener para siempre. Comprueba con ss que ha desaparecido.

Activar el firewall. Permite primero SSH, luego lo que sirves, y después actívalo:

sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw enable
sudo ufw status verbose

El escaneo desde fuera cambió por completo:

Not shown: 997 filtered tcp ports (no-response)
PORT     STATE SERVICE
22/tcp   open  ssh
80/tcp   open  http
8081/tcp open  blackice-icecap
Nmap done: 1 IP address (1 host up) scanned in 10.18 seconds

Cada puerto no permitido pasó de closed a filtered, y el escaneo completo de 65.535 puertos tardó 110 segundos en vez de 8,9. Más tiempo por puerto para el escáner, menos información sobre tu servidor. Nuestra guía de UFW cubre límites de frecuencia, permitir una sola IP e IPv6.

Pero mira la tercera línea.

5. La sorpresa de Docker

El puerto 8081 siguió abierto con el firewall activo y sin regla que lo permitiera. Ese puerto pertenece a un contenedor que iniciamos con docker run -p 8081:80 nginx:alpine. Docker añade sus propias reglas al firewall del kernel, y se evalúan antes que las de ufw. La regla que escribió en nuestro servidor:

-A DOCKER ! -i docker0 -p tcp --dport 8081 -j DNAT --to-destination 172.17.0.2:80

Cualquier paquete al puerto 8081 se reenvía al contenedor antes de que ufw tenga voz. Lo hace cada contenedor con -p o una entrada en ports:, y ss lo muestra con honestidad como docker-proxy en 0.0.0.0:8081.

La solución es publicar solo en localhost:

docker rm -f web
docker run -d --name web -p 127.0.0.1:8081:80 nginx:alpine

En Compose, escribe "127.0.0.1:8081:80" en ports:. Tras el cambio, ss mostró 127.0.0.1:8081, la regla de Docker ganó -d 127.0.0.1/32 y el escaneo desde fuera informó 8081/tcp filtered. Pon nginx como proxy inverso delante para la parte pública, con HTTPS, de modo que los únicos puertos abiertos del servidor sean 22, 80 y 443.

Lista de comprobación tras cada cambio

  1. sudo ss -tulpn: ¿cada línea con 0.0.0.0 o [::] es algo que querías exponer?
  2. nmap ip-del-servidor desde tu portátil: ¿coinciden los puertos abiertos?
  3. Una base de datos, caché o panel de administración en una dirección pública: muévelo hoy a 127.0.0.1 o detrás del firewall.
  4. Cada entrada ports: de Docker: ¿empieza por 127.0.0.1: si no es el puerto web público?
  5. SSH expuesto a todos: añade fail2ban o acceso solo con clave.

En Windows Server, la misma comprobación es Get-NetTCPConnection -State Listen, y las reglas viven en el Firewall de Windows. Cada plan Linux, desde el Nano de $3, tiene una versión con su propia dirección IPv4, y ahí lo que ves en ss es lo que ve el mundo, menos tu firewall.

Preguntas frecuentes

¿Qué puertos están abiertos en un VPS nuevo?

En nuestra imagen de Ubuntu 24.04, uno: SSH en el puerto 22. Lo único más que escucha es el resolvedor DNS local en 127.0.0.53 y 127.0.0.54, al que no se llega desde Internet. Un escaneo de los 65.535 puertos TCP desde otra máquina encontró exactamente un puerto abierto.

¿Qué diferencia hay entre un puerto closed y uno filtered?

Closed significa que el servidor respondió que allí no escucha nada, así que el escáner lo sabe al instante. Filtered significa que no hay respuesta, que es lo que hace un firewall cuando descarta paquetes. Ambos dejan fuera a los intrusos, pero filtered además oculta qué servicios existen y hace que un escaneo completo sea unas doce veces más lento en nuestro servidor.

¿Por qué ss muestra un puerto que nmap dice que está closed?

El servicio escucha solo en 127.0.0.1, la dirección de loopback. Los programas del propio servidor pueden conectarse; nadie desde fuera. En ss, mira la columna Local Address: 127.0.0.1:8080 es privado, 0.0.0.0:8080 o [::]:8080 es accesible desde Internet si el firewall lo permite.

Activé ufw, ¿por qué mi puerto de Docker sigue abierto?

Docker escribe sus propias reglas de firewall, que se evalúan antes que las de ufw. Un contenedor iniciado con -p 8081:80 era accesible desde fuera en nuestro servidor con ufw activo y sin regla para el 8081. Publica en localhost, -p 127.0.0.1:8081:80, y pon nginx o Caddy delante para la parte pública.

¿Cómo compruebo un puerto desde mi ordenador?

Ejecuta nc -zv ip-del-servidor 443 para un puerto, o instala nmap y ejecuta nmap ip-del-servidor para los mil puertos más comunes. En un plan NAT solo responde desde Internet tu puerto SSH redirigido; todo lo demás es inaccesible por diseño.

Comentarios

Aún no hay comentarios. Sé el primero.

Deja un comentario

Los comentarios se moderan antes de aparecer.