# How to configure firewalld on AlmaLinux and Rocky Linux

> Turn on firewalld, open services and ports for good, limit SSH to your own addresses with a zone, and use rich rules, without locking yourself out.

Source: https://vpsnine.com/tutorials/firewalld-almalinux-rocky · Updated: 2026-10-21

Difficulty: beginner · Time: about 20 minutes · Tested on: AlmaLinux 10, Rocky Linux 9

Prerequisites:

- A server running AlmaLinux 9 or 10 or Rocky Linux 9
- Root or a user with sudo

To configure firewalld on AlmaLinux or Rocky Linux, start it with `systemctl enable --now firewalld`, open what you need with `firewall-cmd --permanent --add-service=https` (or `--add-port=8080/tcp`), and apply the change with `firewall-cmd --reload`. Everything else, from limiting SSH to your office to forwarding ports, is the same three moves on a different zone or rule.

firewalld is the standard firewall on RHEL-family systems, in the role ufw plays on Ubuntu. It writes nftables rules for you. This tutorial was run on AlmaLinux 10 (firewalld 2.4) and Rocky Linux 9 (firewalld 1.3); the commands are the same on both and on AlmaLinux 9.

## How firewalld thinks

Three ideas cover almost everything:

| Idea | What it means |
| --- | --- |
| **Zone** | A set of rules with a trust level. Each network interface, and optionally each source address range, belongs to one zone. On a server, `public` is the default, and the one that matters. |
| **Service** | A named group of ports, like `ssh` (22/tcp) or `https` (443/tcp). `firewall-cmd --get-services` lists the predefined ones. |
| **Runtime vs permanent** | A command without `--permanent` changes the running firewall only, and is lost on reload or reboot. With `--permanent` it is saved, but only takes effect after `--reload`. |

Anything not allowed in a zone is rejected. Replies to connections the server makes itself are always let back in.

## Install and start firewalld

Some cloud images ship without firewalld. Install it if `firewall-cmd` isn't found, then start it now and at every boot:

```bash
sudo dnf install -y firewalld
sudo systemctl enable --now firewalld
```

The `public` zone allows `ssh` out of the box, so starting firewalld doesn't cut off your session.

### Check it worked

```bash
sudo firewall-cmd --state
sudo firewall-cmd --list-all
```

The first prints `running`. The second shows the default zone's rules, typically:

```text
public (default, active)
  target: default
  interfaces: eth0
  services: cockpit dhcpv6-client ssh
  ports:
  rich rules:
```

`cockpit` is the web console's port, 9090. If you don't run Cockpit, close it (next section).

## Open and close services and ports

Use a service name when one exists, a port when it doesn't. Add `--permanent` and reload:

```bash
sudo firewall-cmd --permanent --add-service=http --add-service=https
sudo firewall-cmd --permanent --add-port=51820/udp
sudo firewall-cmd --permanent --remove-service=cockpit
sudo firewall-cmd --reload
```

Each command prints `success`. Adding something that is already open prints `Warning: ALREADY_ENABLED: http` and changes nothing, which is harmless.

To see what a service opens before you add it:

```bash
firewall-cmd --info-service=ssh
```

### Check it worked

```bash
sudo firewall-cmd --list-services
sudo firewall-cmd --list-ports
```

You should see `dhcpv6-client http https ssh` and `51820/udp`. Then confirm from outside, for example with `curl -I http://203.0.113.10` from your computer, or with the port checks in [check open ports with ss and nmap](https://vpsnine.com/tutorials/check-open-ports-linux).

## Try a rule before you keep it

A runtime rule with a timeout removes itself, which makes it safe for experiments:

```bash
sudo firewall-cmd --add-port=8080/tcp --timeout=10m
```

If you like the result, save the whole runtime state as the permanent one:

```bash
sudo firewall-cmd --runtime-to-permanent
```

Don't run that while a timed rule you don't want to keep is still active: it saves everything that's running.

## Allow SSH only from your own addresses

The cleanest way is a zone for trusted sources. Packets from a source in that zone get its rules; everyone else falls through to `public`. Here `198.51.100.0/24` stands for your office or VPN range:

```bash
sudo firewall-cmd --permanent --new-zone=admin
sudo firewall-cmd --permanent --zone=admin --add-source=198.51.100.0/24
sudo firewall-cmd --permanent --zone=admin --add-service=ssh
sudo firewall-cmd --reload
```

### Check it worked

Open a **new** SSH session from an address in that range before you change anything else:

```bash
sudo firewall-cmd --get-active-zones
sudo firewall-cmd --zone=admin --list-all
```

`admin` is listed with `sources: 198.51.100.0/24` and `services: ssh`. Only when your new session works, remove SSH from the public zone:

```bash
sudo firewall-cmd --permanent --zone=public --remove-service=ssh
sudo firewall-cmd --reload
```

Now SSH answers only from your range. If your home address changes, you'll need the [serial console](https://vpsnine.com/help/serial-console) to get back in, so use a range you control (an office or a VPN such as [WireGuard](https://vpsnine.com/tutorials/wireguard-vpn-server-almalinux)).

## Rich rules for one-off cases

A rich rule says exactly who may reach which port. For example, PostgreSQL from one network only:

```bash
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="198.51.100.0/24" port port="5432" protocol="tcp" accept'
sudo firewall-cmd --reload
sudo firewall-cmd --list-rich-rules
```

To remove it, repeat the command with `--remove-rich-rule` and the same quoted rule. For more than two or three of these, a source zone like `admin` above is easier to read.

## Forward a port

To send traffic arriving on port 80 to an app listening on 8080 on the same server:

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

A reverse proxy (nginx, Caddy) is usually the better tool for web traffic, because it can also handle HTTPS.

## Where the rules live

Permanent rules are XML files under `/etc/firewalld/zones/`, one per zone you changed (`public.xml`, `admin.xml`). You can read them to see the whole picture, and back them up with the rest of `/etc`. Edit them with `firewall-cmd` rather than by hand; if you do edit one, run `firewall-cmd --reload` afterwards.

## Troubleshooting

**`FirewallD is not running`.** The service is stopped. `sudo systemctl enable --now firewalld`, then `journalctl -u firewalld -n 30` if it won't start.

**`Error: INVALID_SERVICE: Zone 'public': 'xyz' not among existing services`.** There's no predefined service by that name. Use `--add-port=PORT/tcp` instead, or check the name with `firewall-cmd --get-services`.

**A rule works until the next reboot, then disappears.** It was added without `--permanent`. Add it again with `--permanent` and reload, or run `--runtime-to-permanent` while it's active.

**A `--permanent` rule does nothing.** Permanent changes wait for `sudo firewall-cmd --reload`.

**`You're performing an operation over default zone ('public'), but your connections/interfaces are in zone 'admin'`.** Rocky Linux 9's firewalld prints this once your SSH session comes from a source in the `admin` zone. It's a reminder, not an error: the change went to `public`. If you meant the admin zone, repeat the command with `--zone=admin`.

**Docker containers are reachable even though the port isn't open.** Docker publishes ports with its own nftables rules, in a zone called `docker`. Bind published ports to `127.0.0.1` (`-p 127.0.0.1:8080:80`) and put a reverse proxy in front, or filter them before they reach the server with the panel firewall (below).

## Next steps

- Check from outside what's actually reachable with [ss and nmap](https://vpsnine.com/tutorials/check-open-ports-linux).
- Add a [sudo user and turn off root login](https://vpsnine.com/tutorials/create-a-sudo-user-almalinux-rocky).
- On Ubuntu or Debian, use [ufw](https://vpsnine.com/help/firewall-with-ufw) instead.

## Doing it on VPSNine

VPSNine servers also have a [panel firewall](https://vpsnine.com/help/server-firewall) that filters traffic before it reaches the server, so it covers Docker's ports too and still applies if firewalld is stopped. Using both is fine: open a port in both places, and keep SSH allowed in both. Every [plan](https://vpsnine.com/plans) runs [AlmaLinux 10](https://vpsnine.com/os/almalinux-10), [AlmaLinux 9](https://vpsnine.com/os/almalinux-9) and [Rocky Linux 9](https://vpsnine.com/os/rocky-9).
