LearnRHCSA (EX200)Security

firewalld: Zones and Rich Rules

firewall-cmd - a hands-on Linux lab on a real virtual machine.

Go deeper than firewalld first contact: a zone is a named trust level bound to interfaces and source addresses. See the live ones with firewall-cmd --get-active-zones, then write a rich rule to open a service to some addresses and not others: firewall-cmd --permanent --add-rich-rule with family, source address, service name, and accept, made live with --reload and verified with --list-rich-rules. Open a raw port with --add-port, and aim any command at a specific zone with --zone. Serves EX200 security: configure firewall settings using firewall-cmd (zones, rich rules, ports).

A task lands on your desk: open SSH, but only for the machines on the office network 192.168.50.0/24. Everyone else stays locked out. You already know the fast move for opening a service, so you reach for it: firewall-cmd --permanent --add-service=ssh, reload, done. Except it is not done. That command opened SSH to the entire world, every address on the planet, not just the office. The task asked for a scalpel and you swung a hammer.

The plain --add-service you learned in firewalld first contact is all-or-nothing: a service is open to everyone or closed to everyone. To say allow this service, but only from these addresses, you need two deeper ideas: zones, which are named trust levels, and rich rules, which express allow-or-deny by source, service, and port together. This lesson takes you from the blunt open-to-all switch to precise, sourced control. It is one of the most-tested security patterns on the exam.

The black boxes below are a practice terminal: a safe sandbox that checks the one command each step teaches. The outputs are real, captured from a live RHEL 10 machine (AlmaLinux 10.2). A few values depend on the specific machine, above all the interface names bound to a zone (this host shows dummy0 ens3), and yours will differ. What is stable everywhere is the SHAPE. --get-active-zones names the live zones and their interfaces. A rich rule reads back exactly as you wrote it. And the --permanent then --reload rhythm you already know still governs every change.

A zone is a named trust level. Every network interface, and every source address, is assigned to exactly one zone, and that zone decides how much to trust traffic arriving there. Think of a building with several doors. The public door faces the street and trusts almost nothing. A trusted door opens onto a private courtyard and lets everything through. An internal door sits somewhere between. firewalld ships about nine of these preset zones, each a different default posture.

You do not usually invent zones on the exam. You work with the ones firewalld already provides and you move interfaces or sources between them. The everyday default is public: cautious, only a short list of services allowed, which is exactly where a hardened server wants to sit. The reason zones matter for this lesson is that a rich rule, and a service, and a port, all attach TO a zone. When you open something, you are always opening it in some zone, whether you name one or not.

Most of firewalld's preset zones sit idle with nothing bound to them. A zone is active only when at least one interface or source address is assigned to it. firewall-cmd --get-active-zones lists just the live ones and, indented under each, the interfaces and sources it governs. This is how you learn, in one line, which zone your real traffic is actually landing in.

Before you run it, know what to look for: the zone name on its own line, then an indented interfaces: line naming the network cards bound to it. See which zone is live:

firewall-cmd --get-active-zones

prompt: [root@servera ~]# answer: firewall-cmd --get-active-zones output: public interfaces: dummy0 ens3 hint: The active-zone query is firewall-cmd --get-active-zones, no other arguments.

The first line, public, is the name of the one active zone on this machine. The indented interfaces: dummy0 ens3 line lists the network cards bound to it, so every packet arriving on dummy0 or ens3 is judged by the public zone's rules. Because only public is listed, every other preset zone is idle: it exists but governs nothing. This is your orientation command. Before you add a rule, run this to confirm WHICH zone your traffic uses, so you add the rule in the right place.

The interface names here, dummy0 and ens3, are specific to this machine. Yours will differ: RHEL 10 names cards by their hardware slot, so you may see enp1s0, eth0, or others. What is stable is the shape: a zone name, then an indented interfaces: line. Read the zone name and trust the interface list to be whatever your hardware reports.

A rich rule is a single sentence of firewall policy that combines conditions the plain switches cannot. --add-service opens a service to the whole zone. A rich rule instead lets you say: for this address family, from this source address, allow this service, and accept it. Every word is a clause, and you read it left to right like a sentence.

Here is the anatomy, clause by clause. rule opens it. family="ipv4" says these are IPv4 addresses, not IPv6. source address="192.168.50.0/24" is the WHO: only traffic from that network matches. service name="ssh" is the WHAT: which service this rule governs. accept is the verdict: let it through (the alternatives are reject and drop). String those together and you have opened SSH to exactly one network and no one else. That is the scalpel the opening task needed.

You build a rich rule with firewall-cmd --permanent --add-rich-rule= followed by the whole rule text in single quotes. The single quotes matter: the rule contains double quotes and spaces, and the shell must hand the entire string to firewall-cmd as one argument. Like every persistent change, it takes a --reload to become live.

Add the rule, then reload, then read it back. Add the rule that allows SSH only from the office network:

firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.50.0/24" service name="ssh" accept'

prompt: [root@servera ~]# answer: firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.50.0/24" service name="ssh" accept' output: hint: The flag is --add-rich-rule= and the whole rule goes in single quotes: --add-rich-rule='rule family="ipv4" source address="..." service name="ssh" accept'

No output, which is success for a --permanent change: firewalld wrote the rule into the permanent config and stayed quiet. But permanent does not mean live. A --permanent edit sits in the on-disk config and does nothing to the running firewall until you run firewall-cmd --reload, which you already met in first contact. Only after the reload is the rule actually enforcing. This split, permanent config versus running state, is the single most common exam mistake: people add the rule, forget the reload, and the firewall never changes.

Watch the quoting. The rule text is wrapped in single quotes '...' precisely because it contains double quotes around each value. If you use double quotes on the outside, the shell tries to interpret the inner quotes and the command breaks. And do not forget the --reload: a rich rule added with --permanent but never reloaded is invisible to the running firewall. Add, then reload, every time.

Now make the rule live and confirm it. firewall-cmd --reload re-reads the permanent config into the running firewall, activating everything you added. Then firewall-cmd --list-rich-rules prints every rich rule currently in force, one per line. This is your verify step: the rule you typed should read back word for word.

First reload to activate the pending change:

firewall-cmd --reload

prompt: [root@servera ~]# answer: firewall-cmd --reload output: hint: The activate step is firewall-cmd --reload, no arguments. It pulls the permanent config into the live firewall.

--reload normally prints success and returns you to the prompt; on this capture it produced no line, which is equally fine, the reload still ran. Either way, the permanent rich rule you added a moment ago is now enforced by the running firewall. Reload is non-disruptive: it re-applies the whole permanent config without dropping established connections. Now that the rule is live, list it to prove it took.

With the rule reloaded, firewall-cmd --list-rich-rules shows exactly what the running firewall is now enforcing. Read the output against what you typed: same family, same source, same service, same verdict. If it matches, the scalpel is in place.

List the rich rules the firewall is enforcing:

firewall-cmd --list-rich-rules

prompt: [root@servera ~]# answer: firewall-cmd --list-rich-rules output: rule family="ipv4" source address="192.168.50.0/24" service name="ssh" accept hint: The query is firewall-cmd --list-rich-rules, no arguments. It prints one line per active rich rule.

The one line, rule family="ipv4" source address="192.168.50.0/24" service name="ssh" accept, is your rule, echoed back verbatim by the running firewall. Read it clause by clause to confirm each part landed: family="ipv4" (IPv4), source address="192.168.50.0/24" (only the office network), service name="ssh" (SSH), accept (allowed). SSH is now open to that one network and closed to everyone else, which is exactly what the task asked for. When a rich-rule task says configure and verify, this list command is the verify.

Two more moves round out the toolkit. First, a raw port. Not every service has a firewalld name; sometimes a task just says open TCP 8080. You do that with --add-port=8080/tcp, the same --permanent plus --reload rhythm, then confirm with firewall-cmd --list-ports, which prints the open ports like 8080/tcp. You met --add-port in first contact; it lives here too alongside services and rich rules.

Second, targeting. Every command in this lesson acted on the default zone because you never named one. To aim at a specific zone instead, add --zone=<name> to any of these commands, for example firewall-cmd --permanent --zone=internal --add-service=ssh or firewall-cmd --zone=internal --list-all. Without --zone, firewalld uses the default zone, which is why public quietly received every change so far.

Open TCP port 8080 permanently:

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

prompt: [root@servera ~]# answer: firewall-cmd --permanent --add-port=8080/tcp output: hint: The flag is --add-port=PORT/PROTO, for example --add-port=8080/tcp, with --permanent, then reload.

Silent success again: the port 8080/tcp was written into the permanent config. As always, it does nothing until firewall-cmd --reload pulls it into the running firewall, after which firewall-cmd --list-ports prints 8080/tcp. A port and a rich rule are two different tools for two different jobs. Use a raw port when a task names a bare port number with no service. Use a rich rule when a task adds a condition like from this source, or reject rather than allow. Both attach to a zone, both follow the permanent-then-reload rhythm.

Scaffolding off. No command is printed this time. You have every piece you need.

A task tells you to configure and verify a firewall rule that allows SSH, but only from the network 192.168.50.0/24, and to leave it closed to everyone else. A plain --add-service=ssh will not do, because that opens SSH to the whole zone. You need the one command form that combines an address family, a source network, a service name, and an accept verdict into a single persistent rule, added in one line. Which firewall-cmd command, added with --permanent, writes that sourced allow rule?

prompt: [root@servera ~]# answer: firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.50.0/24" service name="ssh" accept' output: hint: It is a rich rule, not a plain service. Use --permanent --add-rich-rule='rule family="ipv4" source address="192.168.50.0/24" service name="ssh" accept'

firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.50.0/24" service name="ssh" accept' writes exactly the sourced allow rule the task asked for: SSH open to 192.168.50.0/24 and closed to everyone else. Then firewall-cmd --reload makes it live and firewall-cmd --list-rich-rules reads it back to verify. A plain --add-service=ssh would have opened SSH to the whole world; only the rich rule carries the source condition. That add, reload, list loop is the full pattern for every sourced-access task on the exam.

You earned this cheat sheet. Every row is a form you just ran or built:

The one thing to burn in for the exam: a rich rule is how you open a service to SOME addresses and not others, and like every firewalld change it is --permanent to write it, then --reload to make it live. When a task adds a from this network condition, reach for --add-rich-rule.

Two habits keep rich-rule tasks clean. Wrap the whole rule in single quotes so the inner double quotes survive the shell, and always finish with --reload then --list-rich-rules to prove the rule is live and reads back word for word. If a rule does not appear in the list, you almost certainly forgot the reload.

The practice terminal has walked you through the deeper half of firewalld. You used --get-active-zones to see which trust level your traffic lands in. You used --add-rich-rule to open a service to one network and no other. You used --reload and --list-rich-rules to activate and verify, --add-port for a bare port, and --zone to aim any of it at a specific zone. Every one of those you typed yourself.

Operation Lockdown, the security capstone, is where you run these against a real RHEL 10 machine. A full VM boots for you, with a live firewall, SELinux Enforcing, and real users to harden. The mission hands you objectives that use exactly what you practiced here and across the security module. Lock a service down to one network with a rich rule. Open the ports a task names, and verify each change reads back correctly. One difference from this lesson: the mission shows no commands. You read the objective, recall the --add-rich-rule and --reload and --list-rich-rules forms, and type them cold. That recall is what makes it stick on exam day.

Finish the other security lessons, then go lock a real machine down in Operation Lockdown.

Practice firewalld: Zones and Rich Rules in a real Linux terminal at The Linux Camp. Progress is verified automatically as you type commands on the machine.