UFW se ideó para que un cortafuegos se configurara con tres comandos, y en eso acierta. El problema está en otro sitio: ufw status muestra intenciones, no resultados. Una regla puede figurar en la lista y no cerrar absolutamente nada, por tres motivos distintos que se dan con regularidad en servidores reales.

Un comienzo que no lo deja fuera

El orden de los comandos importa. Primero permitir SSH, después activar:

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw enable

ufw enable antes de permitir SSH corta su propia sesión, y después solo queda la consola del proveedor. Si el servidor ya está configurado y no está seguro, mantenga una segunda conexión abierta: sobrevivirá a un cambio fallido.

Mire el estado detallado; el corto oculta la política por omisión:

sudo ufw status verbose

Error uno: la regla existe y el puerto está abierto al mundo

La línea más frecuente en configuraciones ajenas:

sudo ufw allow 3306

Así se abre MySQL «para poder conectarme desde casa», y se abre a todo internet. Hay dos variantes correctas, y ambas son mejores:

sudo ufw allow from 203.0.113.25 to any port 3306

o, más fiable aún, no dejar salir el servicio en absoluto: bind-address = 127.0.0.1 en la configuración de MySQL y acceso externo por un túnel SSH. El cortafuegos es la segunda línea; la primera es que el servicio no escuche en una dirección pública. Una regla de UFW puede borrarse por error, bind-address no cambia sola.

Error dos: IPv6

Una regla del tipo ufw allow from 203.0.113.25 solo se refiere a IPv4. Si el servidor tiene dirección IPv6 —y la mayoría de VPS la tiene, activada—, el servicio sigue siendo accesible por ella. El proveedor asignó la dirección, usted no la recuerda, el escáner la conoce.

Compruebe que el filtrado v6 está activado (IPV6=yes en /etc/default/ufw) y que el servicio no escucha en :: sin necesidad:

ss -tulpn | grep ':::'

Las reglas que nombran una dirección explícita hay que escribirlas por separado para cada versión del protocolo.

Error tres: Docker

El más irritante. Docker publica puertos escribiendo sus propias reglas en las cadenas de iptables antes que aquellas donde escribe UFW. Como resultado, un contenedor lanzado con -p 5432:5432 es accesible desde internet aunque UFW muestre Status: active y una política deny incoming. El cortafuegos no está roto: sencillamente no le llega el turno.

Eso no se arregla en UFW sino en la forma de publicar el puerto:

ports:
  - "127.0.0.1:5432:5432"

La vinculación a la dirección local es la solución más simple y fiable. Hacia fuera debe mirar solo lo que atiende de verdad a los visitantes: normalmente los puertos 80 y 443 de un proxy inverso.

El orden de las reglas

UFW aplica la primera regla que coincide y ahí se detiene. Una denegación añadida después de un permiso no funcionará: nunca se llega a ella. Mire la numeración e inserte donde corresponde:

sudo ufw status numbered
sudo ufw insert 1 deny from 198.51.100.0/24
sudo ufw delete 7

Las reglas se borran por número, pero los números se desplazan tras cada borrado: borre de una en una y vuelva a leer la lista.

La comprobación desde fuera

Los comandos locales muestran lo que está configurado. Lo que ha quedado de verdad solo se ve desde otra máquina:

nmap -Pn -p- 203.0.113.25
nc -zv 203.0.113.25 3306

Sirve cualquier otro servidor o su ordenador de casa. Es la única comprobación que no miente, y merece la pena tras cada reconfiguración apreciable, sobre todo después de instalar algo que «configura la red por su cuenta»: Docker, paneles de gestión, VPN.

Los registros

Por omisión, UFW casi no escribe nada. Se activa así:

sudo ufw logging low

Las entradas van a /var/log/ufw.log, pero solo si en el sistema está rsyslog. En Debian 12 y Ubuntu 24.04 puede faltar, y entonces todo va al diario de systemd:

sudo journalctl -k | grep -i '\[UFW'

Un /var/log/ufw.log vacío no significa nada por sí solo: mire primero en el diario.

Qué esperar de un cortafuegos

UFW cierra lo que no debe ser accesible. No examina el contenido de las peticiones hacia lo que está abierto: el puerto 443 está abierto para todos, y lo que llegue al sitio llegará sin obstáculos. Ese es el trabajo de otras herramientas: un WAF en el nivel de la aplicación, un IDS en el del tráfico. La tarea del cortafuegos es más modesta y más importante: que la lista de puertos abiertos coincida con lo que usted cree de ella. Cómo se ve esa lista junto con las reglas activas en una página lo muestra la demostración de abajo.