Red Hat Certified System Administrator (RHCSA) #12: firewalld and SSH key authentication

11 min read

If #11 Users/Groups had you locking down user security with UID/GID and sudo, ACLs, and password policy, this time we cover the two pillars of controlling access coming in from outside the system. One is deciding which ports and services to open with firewalld, and the other is using SSH key authentication to connect remotely and securely without a password.

Both topics are RHCSA staples. firewalld shows up almost every time in the form “permanently allow this service (or port),” and SSH key authentication appears as the task “set this user up to log in with a key.” And both tasks are zero points if you skip making them permanent. The moment you miss --permanent for firewalld, or the permissions and sshd restart for SSH, the grading script fails. So this post lays out each command from the angle of permanent application.

What is firewalld #

RHEL’s default firewall is firewalld. Internally it drives nftables (iptables in the past), but we work with abstracted rules through a command called firewall-cmd. firewalld’s biggest distinguishing feature is the concept of a zone.

A zone is a set of rules grouped by how much you trust a given network. Even for the same port 80, you can allow it when traffic comes from a trusted internal network and block it when it comes from an untrusted external source. The commonly used zones are as follows.

zoneCharacter
publicThe default zone. An untrusted public network. Only what you explicitly opened is allowed
trustedAll traffic allowed. A fully trusted network
dropDrops every incoming packet with no response
blockRejects incoming connections but sends a response
internal / dmz / workPredefined zones for specific purposes

In RHCSA you mostly add rules to the default zone, public. That said, a source-based question like “process traffic from a particular source address in a different zone” can come up, so you should understand the zone concept itself.

Checking firewalld status #

Before working, let’s first confirm that firewalld is running and which zone is active.

# Check the firewalld service status
systemctl status firewalld

# If it isn't active, start it and register it to auto-start at boot
systemctl enable --now firewalld

# Check the currently active zone and the interfaces bound to it
firewall-cmd --get-active-zones

# Check the default zone
firewall-cmd --get-default-zone

--get-active-zones shows the zones that actually carry traffic and the interfaces bound to them. For example, the output looks like this.

public
  interfaces: ens160

This output means that traffic coming in on the ens160 interface follows the public zone rules. So unless something else is specified, putting rules into public is enough.

Opening services and ports with firewall-cmd #

There are broadly two ways to allow something in firewalld: opening by service name and opening by port number.

Opening by service #

firewalld carries definitions that group the ports of well-known services under a name. Just specify the name — http for 80, https for 443, ssh for 22 — and the corresponding port opens. You can see the list of available services with the following.

# The list of service names firewalld knows
firewall-cmd --get-services

The command to add a service is as follows.

# Allow the http service in the public zone (applies to the current session only; gone after reboot)
firewall-cmd --add-service=http

This command works right now, but it disappears after a reboot. We’ll cover permanent application separately in a moment.

Opening by port #

To open an arbitrary port that isn’t a standard service, specify the port and protocol directly.

# Allow port 8080/tcp (applies to the current session only)
firewall-cmd --add-port=8080/tcp

# You can also specify a range (5900〜5910/tcp)
firewall-cmd --add-port=5900-5910/tcp

--add-port is always in port/protocol form. The command fails if you leave out the protocol (tcp or udp), so be careful.

–permanent and –reload: the heart of permanent application #

This is where you lose points most often in RHCSA. firewalld’s rules have two separate sets of configuration.

  • runtime (currently applied): the rules loaded in memory and running right now. They disappear after a reboot.
  • permanent (persistent configuration): the rules saved to disk that persist across reboots. However, saving them doesn’t apply them immediately.

If you do only --add-service=http without --permanent, it opens now but closes after a reboot. Conversely, if you add only --permanent, it’s saved to disk but isn’t applied right now. So the standard pattern that satisfies both is these two steps.

# 1) Save the rule to the permanent configuration
firewall-cmd --permanent --add-service=http

# 2) Re-read the permanent configuration into runtime so it applies now too
firewall-cmd --reload

--reload re-applies the entire permanent configuration to runtime. Skip this one line and you end up in the odd state of “it doesn’t work now but does after a reboot.” If the grading script checks without rebooting, it can fail — so make it a habit to always --reload after saving with --permanent.

The trap: forgetting –permanent #

When you’re pressed for time in the exam, you absent-mindedly type something like this.

# A common mistake. Applies to runtime only and disappears after a reboot
firewall-cmd --add-service=https

This command prints success, so it looks like it worked. But the rule disappears after a reboot, so it’s zero points in grading. When you see a problem that says “allow something in the firewall,” it’s safest to reflexively think of --permanent first. The safe standard procedure is as follows.

# Permanent + immediate application as one set
firewall-cmd --permanent --add-service=https
firewall-cmd --permanent --add-port=8080/tcp
firewall-cmd --reload

Checking rules: –list-all #

After working, always confirm the result. The command that shows the rules actually applied to a zone at a glance is --list-all.

# Check all runtime rules of the default zone (usually public)
firewall-cmd --list-all

# Check by specifying a particular zone
firewall-cmd --zone=public --list-all

The output looks something like this.

public (active)
  target: default
  interfaces: ens160
  sources:
  services: ssh dhcpv6-client http https
  ports: 8080/tcp
  ...

Look at the services and ports lines here to confirm the intended rules went in. One thing to watch is that --list-all shows runtime by default. To also confirm that the permanent configuration was saved properly, add --permanent.

# Check the rules saved in the permanent configuration
firewall-cmd --permanent --list-all

When both runtime and permanent come out as intended, that task is completely done.

rich rule: a fine-grained rule in one line #

When you need to go beyond a simple allow and apply finer conditions — like “allow only a specific service from a specific source” — that’s a rich rule. RHCSA doesn’t probe it deeply, but the one-line form is useful to learn.

# Permanently allow only http coming from the 192.168.10.0/24 range
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="192.168.10.0/24" service name="http" accept'
firewall-cmd --reload

A rich rule needs --permanent and --reload just like any other rule. You can check registered rich rules with the following.

firewall-cmd --list-rich-rules

Assigning a zone by source #

You can also bind a source address to a zone instead of an interface. This is useful for a problem like “process traffic from this address under a more trusted zone.”

# Bind the 192.168.20.0/24 range to the trusted zone and apply permanently
firewall-cmd --permanent --zone=trusted --add-source=192.168.20.0/24
firewall-cmd --reload

# Check the result. The range appears on the sources line of the trusted zone
firewall-cmd --zone=trusted --list-all

With this, traffic from that range follows the trusted zone rules rather than public. Adding a source makes that zone appear in the active zone list, so you can also confirm it with --get-active-zones.

SSH key authentication #

Now for the second pillar. SSH key authentication is a method of proving identity with a key pair (public key + private key) instead of a password. The client holds the private key, and once you register the public key on the server you can log in without a password. It’s the practical standard because it resists password-guessing attacks and is good for automation.

The flow has three steps: create the key pair (ssh-keygen), register the public key on the server (ssh-copy-id), and get the permissions right.

1) Generate a key pair: ssh-keygen #

You create the key pair on the client (the side that connects).

# Generate an RSA key pair (saved in the default location ~/.ssh/)
ssh-keygen

# The form that spells out options. Specify the type and a comment
ssh-keygen -t rsa -b 4096 -C "user@workstation"

When you run ssh-keygen, it asks for the save location (~/.ssh/id_rsa) and a passphrase. If you proceed with the defaults, two files are created.

FileRole
~/.ssh/id_rsaThe private key. Must never be exposed externally
~/.ssh/id_rsa.pubThe public key. The file you register on the server

A passphrase is an extra password that protects the private key itself. It’s good for security, but if the exam requires passwordless automatic login, leave the passphrase empty.

2) Registering the public key: ssh-copy-id #

The easiest way to register the public key you made on the server is ssh-copy-id. This command adds the public key to the server’s ~/.ssh/authorized_keys and even sets the permissions for you.

# Register the public key on the server's user account (authenticate once with the password this time)
ssh-copy-id user@server

After running this command once, you log in with the key without a password from then on.

# Connect with key authentication. Success if it doesn't ask for a password
ssh user@server

3) Manual registration and permissions: authorized_keys #

In an environment where you can’t use ssh-copy-id, you have to put the public key into authorized_keys directly. The key here is getting the permissions exactly right. SSH refuses key authentication for security if the permissions are loose.

# Assuming you work as the target user on the server
mkdir -p ~/.ssh
chmod 700 ~/.ssh

# Append the public key content to authorized_keys
cat id_rsa.pub >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys

The permission rules are worth memorizing.

TargetPermission
The ~/.ssh directory700 (drwx——)
The ~/.ssh/authorized_keys file600 (-rw——-)
The home directory itselfNo write permission for group or other

Ownership matters too. A user’s .ssh directory and the files inside it must be owned by that user. If you created another user’s key as root, use chown -R user:user /home/user/.ssh to fix the ownership. The most common cause of key authentication not working is permissions and ownership, so if it doesn’t work, check here first. On a system with SELinux on, the context also has an effect, which we’ll cover with restorecon in #13 SELinux in depth.

4) Turning off password authentication (sshd config) #

To force key authentication and block password login, change the sshd config. The key is this one line.

# Set in /etc/ssh/sshd_config or /etc/ssh/sshd_config.d/*.conf
PasswordAuthentication no

On RHEL 9, a drop-in file under /etc/ssh/sshd_config.d/ often takes precedence over the main config, so check whether the same key exists there too. After changing the config, you have to restart sshd for it to apply.

# Restart sshd to apply the configuration
systemctl restart sshd

One thing to watch is to first confirm that key authentication actually works before turning off password authentication. If you block the password while keys aren’t working, you create a situation where you can’t log in yourself.

Exam points #

  • A firewalld task is one set of two steps: save with --permanent and apply with --reload. Miss even one and you lose points.
  • When you see a problem that says “allow in the firewall,” reflexively think of --permanent. Applying only to runtime means it disappears after a reboot — zero points.
  • A service is --add-service=name, a port is --add-port=port/protocol. Always attach the protocol (tcp/udp) to a port.
  • After working, check both runtime with firewall-cmd --list-all and the permanent configuration with firewall-cmd --permanent --list-all.
  • Handle active zones and interfaces with --get-active-zones, and source-based zone assignment with --add-source.
  • For SSH key authentication, the fastest flow is to create it with ssh-keygen and register it with ssh-copy-id.
  • For manual registration, permissions are key. ~/.ssh is 700, authorized_keys is 600, and the owner must be that user.
  • To turn off password authentication, you have to go all the way through PasswordAuthentication no and then systemctl restart sshd. Confirm key login works before turning it off.

Wrap-up #

What this post locked in:

  • firewalld. Divide trust levels by zone, and allow services and ports with firewall-cmd.
  • Permanent application. The two steps of saving with --permanent and applying immediately with --reload are mandatory.
  • Confirmation. Check the active zone with --get-active-zones and the applied rules with --list-all.
  • Fine-grained rules. Apply source and service conditions with a rich rule, and assign a zone with --add-source.
  • SSH key authentication. The ssh-keygenssh-copy-id flow; for manual registration, get the 700/600 permissions and ownership right.
  • Blocking the password. PasswordAuthentication no then restart sshd. Verify key login first.

firewalld and SSH key authentication aren’t hard in terms of the commands themselves, but the traps of permanent application and permissions are what decide the score. Close out both tasks with the habit of asking yourself, “does this persist after a reboot?” and “are the permissions exactly right?”

Next: SELinux in depth #

Once you’ve controlled outside access with the firewall and SSH, what controls what a process can do inside the system is SELinux. It’s cited as the trickiest area in RHCSA, but once you know the patterns it becomes a source of points.

In #13 SELinux in depth: contexts, booleans, troubleshooting (audit2allow), we’ll type through SELinux troubleshooting firsthand — semanage fcontext and restorecon for handling file contexts, the booleans that toggle behavior, and audit2allow for reading denial logs and building a policy.

X