firewalld es el gestor de cortafuegos por defecto en Rocky Linux, AlmaLinux, CentOS Stream y RHEL. En lugar de editar reglas de nftables a mano, agrupas interfaces y direcciones de origen en zonas y decides qué servicios acepta cada una. En este tutorial configurarás firewalld en Rocky Linux 9 para un servidor web: abrirás los servicios necesarios, restringirás SSH a tus IP de confianza, bloquearás direcciones concretas y redirigirás puertos, todo sin cortarte el acceso.

Los comandos son idénticos en AlmaLinux 9, CentOS Stream 9 y RHEL 9, y también funcionan en las versiones 8 y 10.

Requisitos previos

  • Un servidor con Rocky Linux 9 o AlmaLinux 9, por ejemplo un VPS de CubePath.
  • Un usuario no root con privilegios sudo.
  • Acceso a una consola alternativa (consola VNC o de emergencia del proveedor) por si una regla bloquea tu sesión SSH.
  • La IP pública desde la que te conectas. Puedes consultarla con curl -4 ifconfig.me desde tu equipo local.

Paso 1: Instalar y arrancar firewalld

La mayoría de imágenes de Rocky Linux traen firewalld instalado. Si no está, instálalo:

sudo dnf install firewalld

Habilítalo para que arranque con el sistema y arráncalo ahora:

sudo systemctl enable --now firewalld

Comprueba que está en ejecución:

sudo firewall-cmd --state
running

Paso 2: Revisar la zona activa

Cada interfaz de red pertenece a una zona, y la zona determina qué tráfico entrante se acepta. Consulta la zona por defecto y las zonas activas:

sudo firewall-cmd --get-default-zone
sudo firewall-cmd --get-active-zones
public
public
  interfaces: eth0

El nombre de la interfaz puede ser eth0, ens18 u otro según el hipervisor. Muestra toda la configuración de la zona:

sudo firewall-cmd --list-all
public (active)
  target: default
  icmp-block-inversion: no
  interfaces: eth0
  sources:
  services: cockpit dhcpv6-client ssh
  ports:
  protocols:
  forward: yes
  masquerade: no
  forward-ports:
  source-ports:
  icmp-blocks:
  rich rules:

Las zonas predefinidas más útiles son estas:

ZonaComportamiento por defecto
dropDescarta todo el tráfico entrante sin responder
blockRechaza el tráfico entrante con un mensaje ICMP
publicSolo acepta los servicios listados. Es la zona por defecto
internalPensada para redes internas, admite más servicios
trustedAcepta todo el tráfico

En todas ellas se permite siempre el tráfico de respuesta a conexiones que inicia el propio servidor.

Paso 3: Entender la configuración runtime y permanente

firewalld mantiene dos configuraciones:

  • Runtime: la que está activa ahora. Se pierde al recargar o reiniciar.
  • Permanente: la guardada en /etc/firewalld/. Se aplica al arrancar o al ejecutar --reload.

Un comando sin --permanent solo cambia la configuración runtime. Uno con --permanent solo cambia la permanente y no tiene efecto hasta el siguiente --reload. Esta separación te da una forma segura de trabajar con reglas arriesgadas:

  1. Aplica el cambio sin --permanent y comprueba que todo funciona.
  2. Guarda el estado actual con sudo firewall-cmd --runtime-to-permanent.

Si algo sale mal, sudo firewall-cmd --reload descarta los cambios runtime y vuelve a la configuración permanente. Para cambios sencillos, como abrir HTTP, es más directo usar --permanent y recargar.

Paso 4: Permitir servicios

firewalld incluye definiciones de servicio con sus puertos (están en /usr/lib/firewalld/services/). Lista las disponibles:

sudo firewall-cmd --get-services

Para un servidor web, permite HTTP y HTTPS de forma permanente y recarga:

sudo firewall-cmd --permanent --add-service=http --add-service=https
sudo firewall-cmd --reload
success
success

Si no usas Cockpit (la consola web en el puerto 9090), quita ese servicio de la zona:

sudo firewall-cmd --permanent --remove-service=cockpit
sudo firewall-cmd --reload

Comprueba los servicios permitidos:

sudo firewall-cmd --list-services
dhcpv6-client http https ssh

Para ver qué puertos abre un servicio concreto:

sudo firewall-cmd --info-service=https
https
  ports: 443/tcp
  protocols:
  source-ports:
  modules:
  destination:
  includes:
  helpers:

Paso 5: Abrir puertos concretos

Cuando una aplicación no tiene definición de servicio, abre el puerto directamente. Por ejemplo, para una aplicación en el puerto 8080/tcp:

sudo firewall-cmd --permanent --add-port=8080/tcp
sudo firewall-cmd --reload

También puedes abrir rangos, como los puertos pasivos de un servidor FTP:

sudo firewall-cmd --permanent --add-port=40000-40100/tcp
sudo firewall-cmd --reload

Verifica los puertos abiertos:

sudo firewall-cmd --list-ports
8080/tcp 40000-40100/tcp

Para cerrar un puerto, usa --remove-port con el mismo valor.

Crear un servicio propio

Si una aplicación usa varios puertos, es más legible definir un servicio con nombre. Este ejemplo crea myapp con los puertos 9000/tcp y 9001/tcp:

sudo firewall-cmd --permanent --new-service=myapp
sudo firewall-cmd --permanent --service=myapp --set-description="Mi aplicación"
sudo firewall-cmd --permanent --service=myapp --add-port=9000/tcp
sudo firewall-cmd --permanent --service=myapp --add-port=9001/tcp
sudo firewall-cmd --reload

La definición se guarda en /etc/firewalld/services/myapp.xml. Ahora puedes añadirla a una zona como cualquier otro servicio:

sudo firewall-cmd --permanent --add-service=myapp
sudo firewall-cmd --reload

Paso 6: Restringir SSH a tus IP de confianza

Dejar SSH abierto a todo Internet atrae intentos de fuerza bruta constantes. Una forma limpia de restringirlo es asignar tus IP de confianza a la zona internal, que permite SSH, y quitar SSH de la zona public.

En los comandos siguientes, sustituye 203.0.113.10 por tu IP pública. Como este cambio puede bloquear tu acceso, hazlo primero en runtime.

Añade tu IP como origen de la zona internal:

sudo firewall-cmd --zone=internal --add-source=203.0.113.10/32

Quita SSH de la zona public:

sudo firewall-cmd --zone=public --remove-service=ssh

Abre una segunda sesión SSH desde tu equipo sin cerrar la actual. Si conecta, el cambio funciona y puedes hacerlo permanente:

sudo firewall-cmd --runtime-to-permanent

Si la nueva sesión no conecta, ejecuta sudo firewall-cmd --reload en la sesión que sigue abierta para deshacer los cambios.

Comprueba el resultado:

sudo firewall-cmd --get-active-zones
internal
  sources: 203.0.113.10/32
public
  interfaces: eth0

El tráfico que llega desde 203.0.113.10 se evalúa con las reglas de internal; el resto sigue en public, que ya no permite SSH.

Paso 7: Usar reglas enriquecidas

Las reglas enriquecidas (rich rules) permiten condiciones que no cubren los servicios y puertos simples: origen concreto, registro, límites de frecuencia o rechazo explícito.

Bloquear una IP o una red

Para descartar todo el tráfico de una dirección:

sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="198.51.100.23" drop'
sudo firewall-cmd --reload

Funciona igual con una red completa, por ejemplo 198.51.100.0/24. Una alternativa equivalente es asignar el origen a la zona drop:

sudo firewall-cmd --permanent --zone=drop --add-source=198.51.100.0/24
sudo firewall-cmd --reload

Permitir un puerto solo desde una IP

Para que solo tu servidor de aplicaciones 10.0.0.5 pueda conectar a MySQL (puerto 3306):

sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.0.0.5" port port="3306" protocol="tcp" accept'
sudo firewall-cmd --reload

No añadas el servicio mysql a la zona, o el puerto quedará abierto para todos.

Limitar la frecuencia de conexiones

Esta regla acepta como máximo 10 conexiones nuevas por minuto al servicio SSH. Tiene sentido si mantienes SSH abierto en public:

sudo firewall-cmd --permanent --remove-service=ssh
sudo firewall-cmd --permanent --add-rich-rule='rule service name="ssh" accept limit value="10/m"'
sudo firewall-cmd --reload

Primero se quita el servicio ssh de la zona, porque si no se aceptaría todo el tráfico SSH antes de llegar a la regla con límite. Las conexiones que superen el límite se rechazan.

Listar y eliminar reglas enriquecidas

sudo firewall-cmd --list-rich-rules
rule family="ipv4" source address="198.51.100.23" drop
rule family="ipv4" source address="10.0.0.5" port port="3306" protocol="tcp" accept
rule service name="ssh" accept limit value="10/m"

Para eliminar una, copia la línea exacta en --remove-rich-rule:

sudo firewall-cmd --permanent --remove-rich-rule='rule family="ipv4" source address="198.51.100.23" drop'
sudo firewall-cmd --reload

Paso 8: Redirigir puertos

firewalld puede redirigir un puerto entrante a otro puerto local. Esto es útil cuando una aplicación escucha en 8080 y quieres servirla en el 80 sin ejecutarla como root:

sudo firewall-cmd --permanent --add-forward-port=port=80:proto=tcp:toport=8080
sudo firewall-cmd --reload

Para redirigir a otra máquina (por ejemplo, 10.0.0.20 en tu red privada), también necesitas activar el enmascaramiento (NAT):

sudo firewall-cmd --permanent --add-masquerade
sudo firewall-cmd --permanent --add-forward-port=port=2222:proto=tcp:toport=22:toaddr=10.0.0.20
sudo firewall-cmd --reload

Verifica las redirecciones:

sudo firewall-cmd --list-forward-ports
port=80:proto=tcp:toport=8080:toaddr=
port=2222:proto=tcp:toport=22:toaddr=10.0.0.20

Paso 9: Registrar el tráfico rechazado

Para diagnosticar qué bloquea el cortafuegos, activa el registro de paquetes denegados:

sudo firewall-cmd --set-log-denied=unicast

Este ajuste se guarda automáticamente en la configuración permanente. Los paquetes rechazados aparecen en el registro del kernel:

sudo journalctl -k -g 'REJECT' --since "10 minutes ago"
Sep 25 10:12:03 server kernel: filter_IN_public_REJECT: IN=eth0 OUT= SRC=192.0.2.44 DST=203.0.113.50 PROTO=TCP SPT=51544 DPT=3306 ...

Cuando termines de diagnosticar, desactívalo para no llenar el diario:

sudo firewall-cmd --set-log-denied=off

Solución de problemas

Una regla con --permanent no tiene efecto. Los cambios permanentes no se aplican hasta recargar. Ejecuta sudo firewall-cmd --reload y comprueba con --list-all.

Una regla sin --permanent desaparece al reiniciar. Solo estaba en runtime. Guárdala con sudo firewall-cmd --runtime-to-permanent.

Te has quedado sin acceso SSH. Entra por la consola VNC o de emergencia del proveedor y ejecuta sudo firewall-cmd --reload (si el cambio era solo runtime) o revisa la zona con sudo firewall-cmd --list-all --zone=public y vuelve a añadir SSH con sudo firewall-cmd --permanent --add-service=ssh seguido de --reload.

El puerto está abierto pero no responde. Comprueba que la aplicación escucha en todas las interfaces y no solo en 127.0.0.1 con sudo ss -tlnp. Si la aplicación escucha correctamente, revisa SELinux: un puerto no estándar puede necesitar su etiqueta, por ejemplo sudo semanage port -a -t http_port_t -p tcp 8080 para un servidor web.

Error ALREADY_ENABLED o NOT_ENABLED. Es un aviso, no un fallo: la regla ya existía o ya no estaba. La configuración no cambia.

Conclusión

Has configurado firewalld con una zona public que solo expone los servicios necesarios, acceso SSH limitado a tus IP de confianza, reglas enriquecidas para casos concretos y redirección de puertos. Trabajar primero en runtime y guardar con --runtime-to-permanent te permite probar cualquier cambio sin riesgo de quedarte fuera.

Como siguientes pasos, puedes instalar Fail2ban con la acción firewallcmd-rich-rules para bloquear IP que fallan el inicio de sesión, cambiar el puerto SSH añadiendo el nuevo puerto a firewalld y a SELinux, o revisar la configuración de SELinux de tu servidor.