Configurar el firewall UFW sin quedarte fuera del servidor

Publicado el 19 min de lectura

El orden correcto al montar UFW, las reglas IPv6, nftables como backend, la limitación de tasa con ufw limit y la vía de rescate por consola cuando aun así algo sale mal.

Un filtro de paquetes no es un extra opcional en un servidor root, es equipamiento básico. UFW (Uncomplicated Firewall) lo pone cómodamente fácil, pero tiene una particularidad que cada año deja a miles de administradores fuera de sus propios servidores: el comando que activa el firewall es el mismo que puede cortar la sesión SSH en curso. Esta guía muestra el orden con el que eso no ocurre y, casi más importante, el camino de vuelta si ocurre de todos modos.

Todos los datos se refieren a Debian 13 (trixie), Debian 12 (bookworm), Ubuntu 24.04 LTS y Ubuntu 22.04 LTS. Los comandos están escritos para ejecutarse como root. Si trabajas como usuario normal, antepón sudo a cada comando.

Por qué el orden lo decide todo

El error clásico tiene esta pinta: alguien fija primero la política por defecto en "descartar todo el tráfico entrante", activa el firewall y piensa añadir después, con calma, la regla de SSH. Justo en ese intervalo está la ventana de tiempo en la que el servidor deja de ser accesible.

El motivo por el que ese error pasa desapercibido tantas veces es más traicionero que el error en sí. UFW incluye en /etc/ufw/before.rules una regla que deja pasar las conexiones ya establecidas:

-A ufw-before-input -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

Tu sesión SSH actual es una conexión ya establecida. Por eso sobrevive a la activación del firewall incluso cuando no existe ninguna regla para SSH. El prompt sigue ahí, todo parece correcto. Es el siguiente intento de conexión, normalmente tu inicio de sesión de la mañana siguiente, el que acaba en un timeout. Por eso vale esta norma: mientras el firewall no esté verificado con una segunda sesión abierta desde cero, no cierres la primera sesión.

Regla práctica: primero permitir, luego descartar, luego activar, luego comprobar con una segunda sesión, y solo después cerrar la primera sesión.

Antes del primer comando: vía de rescate e inventario

Antes de cambiar nada en el filtrado de paquetes, aclara dos cosas.

1. ¿Cómo entras al servidor sin SSH?

En los servidores root KVM y los servidores dedicados de KernelHost tienes la consola VNC directamente en el área de cliente. Esa consola no depende de la pila de red del sistema huésped, sino de la capa de virtualización o de la propia conexión de red. Por eso ninguna regla de firewall dentro del huésped puede bloquearla. Inicia sesión una vez antes a través de esa consola y asegúrate de conocer la contraseña de root. Una vía de rescate que se prueba por primera vez durante la emergencia no es una vía de rescate.

2. ¿Qué está escuchando realmente en este servidor?

Las reglas para servicios que no existen son inofensivas. En cambio, un servicio que se te haya pasado por alto te cuesta el acceso o una caída. Hazte una idea de conjunto:

ss -lntup

La columna Local Address:Port distingue con claridad entre 0.0.0.0:22 (solo IPv4), [::]:22 (IPv6 y, a través del socket dual stack, normalmente también IPv4) y 127.0.0.1:3306 (solo local, no necesita ninguna regla de firewall). Todo lo que esté asociado a 127.0.0.1 o ::1 no hay que abrirlo.

Especialmente importante es el puerto SSH real. No lo adivines, compruébalo. El comando necesita permisos de lectura sobre las claves de host, así que se ejecuta como root o con sudo:

sudo sshd -T | grep -i "^port "

Un detalle que muchas guías se callan: en Ubuntu 24.04 el servicio SSH se inicia por activación de socket a través de ssh.socket, no de ssh.service. Allí systemctl is-enabled ssh.socket devuelve enabled y ssh.service devuelve disabled. En Debian 12, Debian 13 y Ubuntu 22.04 ocurre justo lo contrario, allí rige el servicio permanente clásico.

La consecuencia para este tema es incómodamente concreta: en Ubuntu 24.04, el puerto que informa sshd -T no es necesariamente el puerto en el que se está escuchando de verdad. Lo determinante allí es ListenStream en /lib/systemd/system/ssh.socket o en un archivo complementario bajo /etc/systemd/system/ssh.socket.d/. Quien haya cambiado su puerto SSH y se fíe de sshd -T abrirá en UFW el número de puerto equivocado y se quedará fuera en el siguiente inicio de sesión. En los cuatro sistemas, lo único fiable es mirar el proceso que realmente escucha:

sudo ss -lntp | grep sshd

3. El interruptor de hombre muerto

Por si algo sale mal, prepara antes del cambio arriesgado un temporizador que desactive el firewall por sí solo al cabo de diez minutos:

nohup sh -c 'sleep 600; ufw disable' >/dev/null 2>&1 &

Si todo ha funcionado, cancélalo:

pkill -f 'sleep 600; ufw disable'

El pkill alcanza solo al proceso de shell que lo envuelve. El sleep sigue corriendo como proceso huérfano y termina sin consecuencias, porque ya no queda nadie que pueda invocar después ufw disable.

El orden que no te deja fuera

En Debian, UFW no suele venir preinstalado; en Ubuntu Server casi siempre sí. Instalarlo no hace daño en ningún caso:

apt-get update
apt-get install -y ufw
ufw version

Ahora el orden, y exactamente así:

ufw allow 22/tcp comment 'SSH'
ufw default deny incoming
ufw default allow outgoing
ufw --force enable

Cuatro apuntes al respecto:

  • La regla de permiso va antes que todo lo demás. UFW acepta reglas también en estado inactivo y las guarda en /etc/ufw/user.rules. Al activarse están disponibles de inmediato.
  • En lugar de 22/tcp puedes usar un perfil de aplicación, por ejemplo ufw allow OpenSSH. Pero no confíes ciegamente en eso: los perfiles no vienen de UFW, sino de los paquetes instalados. En Ubuntu, /etc/ufw/applications.d/ está vacío mientras no haya servicios instalados y ufw app list muestra allí solo el encabezado Available applications: y ni una sola entrada. En Debian, el propio paquete ufw ya trae unos 38 perfiles y allí el perfil de SSH se llama SSH. El perfil OpenSSH procede en ambas distribuciones del paquete openssh-server, o sea, lo habitual en servidores. Con un puerto SSH distinto del estándar, el perfil no sirve de nada de todos modos. Los perfiles que conoce tu sistema los muestra ufw app list; en cambio, la forma basada en puertos ufw allow 22/tcp funciona igual en los cuatro sistemas y por eso es la opción más fiable.
  • ufw enable pregunta de forma interactiva: Command may disrupt existing ssh connections. Proceed with operation (y|n)?. En scripts y en roles de Ansible usa ufw --force enable, de lo contrario la ejecución se queda colgada.
  • El comentario que sigue a comment aparece en ufw status. Si no, dentro de medio año ya no sabrás para qué está abierto el puerto 8443.

Los demás servicios se añaden después, por ejemplo un servidor web:

ufw allow 80/tcp comment 'HTTP'
ufw allow 443/tcp comment 'HTTPS'

Y solo ahora abres un segundo terminal y vuelves a iniciar sesión. La cosa no está resuelta hasta que ese inicio de sesión funcione.

IPv6: la segunda familia de direcciones que se olvida

Todo servidor moderno tiene IPv6, y casi siempre sin que nadie lo haya configurado de forma activa. Quien solo piensa en IPv4 acaba con un firewall que regula exactamente la mitad del tráfico y deja pasar la otra mitad. Comprueba primero si hay siquiera direcciones IPv6 globales configuradas:

ip -6 addr show scope global

La buena noticia: en las cuatro distribuciones tratadas aquí, /etc/default/ufw trae de fábrica IPV6=yes. UFW mantiene entonces en paralelo una regla IPv6 por cada regla IPv4. Verificar en lugar de confiar:

grep '^IPV6' /etc/default/ufw

Una segunda prueba, más contundente, es la propia política por defecto de la cadena IPv6:

ip6tables -L INPUT -n

En la primera línea debe aparecer Chain INPUT (policy DROP). Si allí pone policy ACCEPT y debajo no hay cadenas de ufw, tu servidor está completamente abierto por IPv6, por muy buenas que parezcan las reglas IPv4.

Dos trampas:

  • Un cambio en IPV6 dentro de /etc/default/ufw no surte efecto con ufw reload. Hace falta ufw disable seguido de ufw enable. Justo en ese hueco te quedas un momento sin firewall, así que no lo hagas de pasada en un sistema expuesto.
  • Quien bloquea ICMPv6 en bloque destruye su propia conectividad. Neighbor Discovery y "Packet too big" no son opcionales en IPv6, forman parte del protocolo. UFW ya permite los tipos necesarios en /etc/ufw/before6.rules. Toca ese archivo solo si sabes exactamente lo que haces.

Cuando una regla basada en puertos se ha creado correctamente en ambas familias de direcciones, UFW informa con dos líneas al añadirla: Rule added y Rule added (v6). Si falta la segunda línea, falta la mitad de la protección. Una excepción son las reglas restringidas por origen: con ufw allow from 203.0.113.10 to any port 22 proto tcp aparece solo Rule added, y así es como debe ser, porque una dirección de origen IPv4 no tiene equivalente en IPv6.

Lo que UFW escribe realmente: nftables como backend

Aquí circula mucha media verdad. La situación en Debian 12, Debian 13, Ubuntu 22.04 y Ubuntu 24.04 es uniforme: UFW sigue hablando la sintaxis de iptables, pero el comando iptables es en los cuatro sistemas la herramienta de compatibilidad iptables-nft. Las reglas acaban, por tanto, en el subsistema nftables del kernel. La prueba en una línea:

iptables -V

La salida termina en (nf_tables). Si allí pone (legacy), tu sistema trabaja con el backend antiguo. Funciona, sí, pero hace que convivan en el kernel reglas de dos mundos distintos que se tapan entre sí. Qué variante está configurada lo muestra update-alternatives --display iptables.

Visto desde el lado de nftables, la cosa queda así. Es importante que no mires hasta después de haber activado el firewall:

apt-get install -y nftables
nft list tables

Y es que, mientras UFW no esté activo, la tabla sencillamente no existe: iptables-nft la crea solo cuando se cargan reglas de verdad, o sea, como muy pronto con ufw --force enable. Antes de eso, nft list tables queda vacío y un nft list table ip filter aborta con Error: No such file or directory. No es un fallo, es el estado esperado. Solo cuando en la lista aparecen table ip filter y table ip6 filter merece la pena mirar dentro:

nft list table ip filter

La salida empieza con la línea Warning: table ip filter is managed by iptables-nft, do not touch!, y va en serio: mirar sí, modificar a mano no. Debajo encuentras cadenas como ufw-before-input, ufw-user-input y ufw-after-input. Aquí es donde se hace visible el conflicto más importante: no mezcles UFW con reglas nft escritas a mano. Un nft flush ruleset borra todas las reglas de UFW del kernel sin que UFW se entere de nada. Después, ufw status sigue informando Status: active mientras en la práctica no se aplica ni una sola regla. Es una de las fuentes de error más desagradables que existen, porque la herramienta con la que compruebas te está mintiendo. El camino de vuelta:

ufw reload

Por eso, después de cualquier intervención con otras herramientas de firewall (Docker, Kubernetes, software de VPN, iptables-persistent), no compruebes el estado de UFW, sino el conjunto de reglas real del kernel.

ufw limit contra la fuerza bruta, y dónde deja de servir

Para SSH, UFW ofrece una limitación de tasa:

ufw limit 22/tcp comment 'SSH rate limit'

La semántica está claramente definida en el manual: las conexiones se permiten con normalidad, pero se descartan en cuanto una sola IP de origen establece seis o más conexiones nuevas en un intervalo de 30 segundos. Los valores están fijados en el código y no se pueden cambiar desde la interfaz de UFW. Por debajo, esto funciona con el módulo recent, visible en iptables -S. Para IPv6, UFW crea una regla equivalente siempre que el módulo del kernel esté disponible, cosa que ocurre en las cuatro distribuciones.

Si antes ya habías puesto ufw allow 22/tcp, ahora surge una segunda regla. La antigua tiene que desaparecer, porque de lo contrario se aplica primero y la limitación no sirve de nada:

ufw status numbered
ufw delete allow 22/tcp

Si en lugar de eso borras por número (ufw delete 3), ten en cuenta dos peculiaridades. Primera: ufw status numbered numera las reglas IPv4 e IPv6 de forma correlativa en una única lista, y tras cada borrado se desplazan todos los números siguientes. Por eso borra siempre una sola regla, vuelve a mostrar la lista después, o trabaja desde el número más alto hacia abajo. Segunda: UFW pregunta durante el proceso de forma interactiva: Proceed with operation (y|n)?.

Y ahora la valoración honesta que falta en la mayoría de las guías:

  • Contra ataques distribuidos no sirve. El recuento se hace por IP de origen. Una botnet con mil direcciones hace cinco intentos por dirección y se queda por debajo del umbral.
  • Afecta a tus propios automatismos. Un script de backup con muchas llamadas sueltas a rsync, una ejecución de Ansible con varios forks o un job de CI pueden chocar con ese mismo límite. Entonces no dejas fuera al atacante, sino a tu propio pipeline de despliegue. Para esas fuentes conviene poner delante una excepción explícita, por ejemplo ufw allow from 203.0.113.10 to any port 22 proto tcp con la dirección real de tu servidor de build.
  • No sustituye a una configuración SSH limpia. El paso más eficaz contra la adivinación de contraseñas es desactivar las contraseñas: PasswordAuthentication no en /etc/ssh/sshd_config o en un archivo bajo /etc/ssh/sshd_config.d/. Quien no acepta contraseñas tampoco puede dejar que las adivinen. Como complemento tiene sentido fail2ban, que a diferencia de ufw limit reacciona a las entradas de log y bloquea durante más tiempo.

Cómo reconocer que de verdad ha funcionado

"El comando se ejecutó sin errores" no es una prueba. Cuatro comprobaciones que sí dicen algo:

Primera, el estado general.

ufw status verbose

Se espera una salida con esta forma:

Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
New profiles: skip

To                         Action      From
--                         ------      ----
22/tcp                     LIMIT IN    Anywhere
22/tcp (v6)                LIMIT IN    Anywhere (v6)

Lo decisivo es la segunda línea con el añadido (v6). Sin ella falta la cobertura de IPv6.

Segunda, el reinicio. Un firewall que no sobrevive a un reinicio no vale nada.

systemctl is-enabled ufw

La respuesta debe ser enabled. Y después reinicia el servidor de verdad una vez e inicia sesión. Es la única prueba que responde realmente a la pregunta.

Tercera, la mirada desde fuera. Comprueba desde otro host si un puerto que no has abierto a propósito está realmente cerrado, por ejemplo con nc -zv TU-IP 3306. Importante: una prueba desde localhost no demuestra nada, porque UFW deja pasar por principio el tráfico que va por la interfaz de loopback.

Cuarta, los logs. Aquí hay una diferencia real entre distribuciones. Con el ajuste por defecto low, UFW registra los paquetes bloqueados a través del log del kernel. En Ubuntu 22.04 y 24.04 rsyslog está presente y los mensajes acaban además en /var/log/ufw.log. En Debian 12 y, muy especialmente, en Debian 13, rsyslog falta en las instalaciones mínimas y allí ese archivo sencillamente no existe. La vía que funciona en todas partes:

journalctl -k -n 50

Se buscan líneas que empiecen por [UFW BLOCK]. Si quieres ver más, sube el nivel (ufw logging medium) y vuelve después a ufw logging low. En un servidor con tráfico de público, el nivel más alto llena el disco más rápido de lo que uno piensa.

Mensajes de error al pie de la letra

ERROR: problem running ufw-init: el desencadenante más frecuente es un conflicto con una segunda herramienta de firewall, normalmente nftables.service o iptables-persistent, o una mezcla del backend legacy con el de nft. Comprueba iptables -V y desactiva los servicios que compiten. UFW incluye además un script de comprobación que repasa uno a uno los requisitos del kernel e informa de qué requisito de módulo falla.

ERROR: Could not find a profile matching 'OpenSSH': falta el perfil de aplicación porque openssh-server no está instalado o porque se ha eliminado el archivo bajo /etc/ufw/applications.d/. En Ubuntu, ese directorio está vacío de todos modos mientras no haya servicios instalados. Usa el número de puerto en su lugar.

ERROR: Bad port: casi siempre una errata o un nombre de servicio que /etc/services no conoce. Los números de puerto siempre son inequívocos.

Skipping adding existing rule o Skipping adding existing rule (v6): no es un mensaje de error, sino el aviso de que la regla ya está ahí. Si crees haber cambiado una regla pero sigue igual, esta es la razón.

ERROR: Invalid position '0': al borrar e insertar, UFW cuenta desde 1, no desde 0. Los números salen de ufw status numbered y se desplazan tras cada borrado. Por eso borra siempre desde el número más alto hacia abajo.

WARN: Rules updated but not applied: la regla está en la configuración, pero el firewall está inactivo. Falta un ufw enable.

Diferencias entre distribuciones de un vistazo

  • Debian 13 (trixie): hay que instalar UFW. Backend nf_tables. IPV6=yes de fábrica. rsyslog falta en las instalaciones mínimas, así que los logs van por journalctl -k. SSH mediante ssh.service.
  • Debian 12 (bookworm): hay que instalar UFW. Backend nf_tables. IPV6=yes de fábrica. rsyslog está presente según la variante de instalación, así que /var/log/ufw.log no está garantizado. SSH mediante ssh.service.
  • Ubuntu 24.04 LTS: UFW viene en las imágenes de servidor, pero inactivo. Backend nf_tables. IPV6=yes de fábrica. /var/log/ufw.log presente. SSH por activación de socket a través de ssh.socket, así que comprueba el puerto en escucha siempre con sudo ss -lntp | grep sshd y no con sshd -T.
  • Ubuntu 22.04 LTS: UFW viene en las imágenes de servidor, pero inactivo. Backend nf_tables. IPV6=yes de fábrica. /var/log/ufw.log presente. SSH mediante ssh.service.

Lo que es igual en los cuatro sistemas: tras la instalación, UFW siempre está inactivo. Nadie te activa el firewall a escondidas, y nadie lo desactiva a escondidas.

Docker deja a UFW sin efecto

Si en el servidor corre Docker, rige una limitación importante: los puertos de contenedor publicados ignoran tus reglas de UFW. Docker crea sus propias cadenas y trabaja con traducción de direcciones de destino, de modo que los paquetes pasan por delante de las cadenas de UFW en INPUT. Un docker run -p 8080:80 queda accesible desde fuera aunque ufw status muestre un impecable "deny incoming".

La contramedida más sencilla y robusta consiste en no publicar en todas las direcciones, sino solo en local, y encaminar el acceso a través de un reverse proxy:

docker run -d -p 127.0.0.1:8080:80 nginx

En un archivo Compose eso corresponde a la indicación de puerto "127.0.0.1:8080:80". Como alternativa existe la posibilidad de enganchar UFW con reglas propias en la cadena DOCKER-USER, reservada por Docker. Es eficaz, pero exige mucho mantenimiento y hay que revisarlo de nuevo con cada actualización de Docker. La vinculación a 127.0.0.1 resuelve el problema de raíz.

Si aun así te has quedado fuera

Ha pasado, SSH ya no responde. Por orden:

  1. No reinicies. Un reinicio no ayuda, porque UFW está activado como servicio de systemd y restaura sus reglas al arrancar. El servidor vuelve exactamente igual de cerrado que se apagó.
  2. Abre la consola VNC en el área de cliente e inicia sesión allí como root.
  3. Desactiva el firewall: ufw disable. Con eso vuelves a estar dentro, pero también otra vez sin protección.
  4. Encuentra la causa, no la adivines. Mira ufw status numbered y el puerto SSH real que sale de ss -lntup. En nueve de cada diez casos la causa es una de estas tres: nunca hubo una regla de permiso para SSH, la regla apunta al puerto 22 mientras sshd escucha en otro puerto, o la apertura estaba restringida a una dirección IP que tu conexión ya no tiene (asignación dinámica de direcciones en el acceso a internet).
  5. Corrige y vuelve a activar, esta vez en el orden correcto y con el interruptor de hombre muerto puesto.

Si quieres restablecer por completo el conjunto de reglas, existe ufw reset. Conviene saber esto: ese comando desactiva el firewall y deja copias de seguridad de los archivos de reglas anteriores bajo /etc/ufw/, con marca de tiempo en el nombre del archivo. Así que, en caso de duda, puedes consultar qué había antes. No lo ejecutes nunca a través de una conexión SSH sin tener la consola abierta al lado.

Una configuración de partida razonable

Para un servidor web típico, la secuencia completa queda así:

apt-get update
apt-get install -y ufw
ufw default deny incoming
ufw default allow outgoing
ufw limit 22/tcp comment 'SSH rate limit'
ufw allow 80/tcp comment 'HTTP'
ufw allow 443/tcp comment 'HTTPS'
ufw --force enable
ufw status verbose

Fíjate en que aquí default deny incoming sí va antes de la regla de SSH, pero la activación ocurre al final del todo. Mientras UFW esté inactivo, la política por defecto no causa ningún daño. Lo crítico es exclusivamente el estado en el momento del enable, y para entonces la regla de SSH lleva mucho tiempo registrada.

Quien además quiera que los accesos de administración solo sean alcanzables desde su propia red trabaja con reglas restringidas por origen siguiendo el patrón ufw allow from 203.0.113.0/24 to any port 22 proto tcp. Todavía más limpio es no exponer los servicios de administración a la red abierta y conectarlos a través de una VPN. Una instancia de WireGuard se configura en pocos minutos en cualquiera de los cuatro sistemas y sustituye toda una pila de excepciones de firewall por un único puerto UDP abierto.

Y para terminar, una valoración que conviene tener presente: UFW filtra en el propio servidor. Contra los ataques volumétricos que saturan la conectividad no sirve por principio, porque los paquetes ya han pasado por la línea cuando tu kernel los descarta. Para eso hace falta filtrado en la red anterior. En KernelHost se encarga de ello la infraestructura previa del centro de datos maincubes en Fráncfort del Meno, con filtrado en tiempo real de Arbor. Tu firewall local y la protección de red resuelven dos problemas distintos, y necesitas los dos.

Preguntas frecuentes

¿"ufw enable" me deja fuera de inmediato de mi sesión SSH en curso?
No, y justo ahí está la trampa. UFW permite en /etc/ufw/before.rules las conexiones ya establecidas (estado RELATED,ESTABLISHED). Tu sesión actual sobrevive por tanto a la activación incluso cuando no existe ninguna regla para SSH. Es el siguiente intento de conexión el que falla. Por eso comprueba siempre con una segunda sesión abierta desde cero antes de cerrar la primera.
¿Tengo que activar IPv6 en UFW por separado?
En Debian 13, Debian 12, Ubuntu 24.04 y Ubuntu 22.04, /etc/default/ufw trae IPV6=yes ya de fábrica. UFW crea entonces de forma automática una equivalencia IPv6 para cada regla, reconocible por el mensaje "Rule added (v6)" y por el añadido "(v6)" en ufw status. Puedes comprobarlo a fondo con ip6tables -L INPUT -n: allí debe aparecer "policy DROP". Un cambio en IPV6 solo surte efecto tras ufw disable seguido de ufw enable, un ufw reload no basta.
¿UFW usa iptables o nftables?
Ambos, en cierto sentido. UFW sigue hablando la sintaxis de iptables, pero el comando iptables es en las cuatro distribuciones la capa de compatibilidad iptables-nft. Las reglas acaban por tanto en el subsistema nftables del kernel y, con el firewall activo, son visibles con nft list table ip filter. Mientras UFW no esté activado, esa tabla no existe y el comando informa "Error: No such file or directory". El backend se comprueba con iptables -V, la salida termina en (nf_tables). No mezcles UFW con reglas nft escritas a mano: un nft flush ruleset borra todas las reglas de UFW mientras ufw status sigue informando "active".
¿Qué hace exactamente ufw limit?
ufw limit permite las conexiones con normalidad, pero las descarta en cuanto una sola IP de origen establece seis o más conexiones nuevas en un intervalo de 30 segundos. Los valores son fijos y no se pueden cambiar desde la interfaz de UFW. Contra ataques distribuidos no sirve, porque el recuento se hace por IP de origen. Además puede afectar a tus propios automatismos, por ejemplo scripts de backup o jobs de CI con muchas conexiones SSH en paralelo. Para esas fuentes, pon delante una excepción explícita.
Ya no puedo entrar al servidor por SSH. ¿Ayuda un reinicio?
No. UFW está activado como servicio de systemd y restaura sus reglas al arrancar, así que el servidor vuelve igual de cerrado. Usa en su lugar la consola VNC del área de cliente, inicia sesión allí como root y ejecuta ufw disable. Después busca la causa: falta la regla de SSH, puerto equivocado o una restricción de origen a una dirección IP que tu conexión ya no tiene.
¿Por qué mis contenedores Docker son accesibles desde fuera pese a tener el firewall UFW activo?
Docker crea sus propias cadenas y trabaja con traducción de direcciones de destino, de modo que los puertos de contenedor publicados pasan por delante de las cadenas de UFW. El "deny incoming" no se aplica a ese tráfico. La solución más robusta es publicar los puertos solo en local, es decir -p 127.0.0.1:8080:80 en lugar de -p 8080:80, y encaminar el acceso a través de un reverse proxy.
¿Por qué no existe /var/log/ufw.log en mi servidor Debian?
Porque allí rsyslog no está instalado. Debian 13 ya no entrega ningún daemon de syslog en las instalaciones mínimas y se apoya en systemd-journald, y Debian 12 tampoco lo hace según la variante de instalación. Los mensajes están igualmente ahí: los lees con journalctl -k y buscas líneas con [UFW BLOCK]. En Ubuntu 22.04 y 24.04 rsyslog está presente y allí el archivo sí existe.

UFW Firewall Linux Debian Ubuntu nftables IPv6 SSH Seguridad de servidores Servidor root