The scenario
You just deployed a web service on a container running on port 8080. Your laptop can reach it fine. A colleague tries to connect from outside your network and gets a timeout. You check the container, it is listening. You check the host, it is listening. The traffic is hitting the firewall and dropping. You need to tell Fedora to take incoming requests on one port and hand them off to another service, or to another machine on your local network.
What firewalld is actually doing
Fedora ships with firewalld as the default firewall daemon. It does not just block packets. It manages zones, services, and NAT rules dynamically. When you ask for port forwarding, you are asking the kernel to rewrite the destination IP or port of an incoming packet before it reaches the target service. This is Network Address Translation. The firewall must know two things. First, which incoming port should it watch. Second, where should it send the traffic after it catches it. If you skip the NAT step, the packet arrives at the right port but the return traffic has no route back. The connection drops.
Think of firewalld like a front desk at a corporate building. Visitors arrive at the main entrance. The front desk checks their name against a list. If they are on the forwarding list, the desk calls an extension and hands them off. If the desk does not know about the extension, or if the building does not allow outgoing calls from the desk, the visitor stands in the lobby until the security guard asks them to leave.
firewalld separates configuration into two layers. The runtime layer lives in memory and changes immediately. The permanent layer lives on disk in XML files under /etc/firewalld/. Any change made with --permanent only affects the disk. You must reload the firewall to merge the permanent layer into the runtime layer. Skipping the reload leaves your system in a split state where firewall-cmd --list-all shows the old rules while systemctl restart firewalld would suddenly apply the new ones. Always reload after permanent changes.
Run firewall-cmd --reload after every permanent rule change. Otherwise the runtime config and the persistent config diverge.
The difference between opening a port and forwarding it
Many guides confuse opening a port with forwarding it. They are different operations. Opening a port tells the firewall to allow traffic to reach a service that is already bound to that port on the host. Forwarding tells the firewall to intercept traffic on one port and redirect it to a different port or a different machine. The original body of this article showed --add-port=8080/tcp. That command only opens the port. It does not forward anything. If your service is listening on 8080 and you want the internet to reach it on 8080, you only need --add-port. If you want the internet to hit port 80 and reach port 8080, you need --add-forward-port.
Use --add-port when the service listens on the exact port you want to expose. Use --add-forward-port when you need to translate the destination port or IP before the packet reaches the service. Use masquerading when the forwarded traffic needs to route back through the firewall to reach the original client.
The fix: configuring port forwarding
Port forwarding in firewalld requires two distinct steps. You must enable masquerading on the zone that receives the traffic. Then you add the forward-port rule. Masquerading allows the firewall to rewrite the source address of returning packets so they route correctly back to the original client. Without masquerading, the target service will try to reply directly to the client, but the client expects the reply to come from the firewall's public IP. The TCP handshake fails.
Here is how to check your current zone and enable masquerading permanently.
# Check which zone your active interface belongs to
sudo firewall-cmd --get-active-zones
# Enable NAT masquerading on the public zone permanently
sudo firewall-cmd --permanent --zone=public --add-masquerade
# Apply the masquerade rule to the running firewall immediately
sudo firewall-cmd --reload
Masquerading is a one-time configuration per zone. Once it is active, you can add forwarding rules. The forward-port directive takes a port and a protocol, then tells firewalld where to redirect it. You can forward to a different port on the same host, or to a different IP on your local network.
Here is how to forward incoming TCP traffic on port 80 to a container listening on port 8080.
# Add the forwarding rule to the permanent configuration
sudo firewall-cmd --permanent --zone=public --add-forward-port=port=80:proto=tcp:toport=8080
# If you are forwarding to a specific internal IP instead of the host itself
# sudo firewall-cmd --permanent --zone=public --add-forward-port=port=80:proto=tcp:toaddr=192.168.1.50:toport=8080
# Reload the firewall to merge permanent rules into the runtime configuration
sudo firewall-cmd --reload
The --permanent flag writes the rule to disk. The --reload flag applies it without dropping existing connections. Always reload after permanent changes. The runtime configuration and the persistent configuration will diverge if you skip this step.
If you need to forward UDP traffic, change proto=tcp to proto=udp. The syntax is strict. Missing the protocol parameter causes firewalld to reject the command. You can also chain multiple forwarding rules by running the command multiple times. firewalld stores them as a list and processes them in order.
Verify the forwarding works
Do not assume the rule loaded correctly. firewalld silently ignores malformed directives. Check the active zone configuration to confirm the forward-port entry exists.
# List all active rules for the public zone
sudo firewall-cmd --zone=public --list-all
# Verify the kernel NAT table contains the redirect rule
sudo iptables -t nat -L PREROUTING -n -v
The first command shows the firewalld abstraction. Look for forward-port in the output. The second command shows the raw netfilter rules that firewalld generated. If the rule appears in both places, the firewall is ready. Test the actual traffic flow from an external machine. Use curl or a browser to hit the public IP on the original port. If the connection succeeds, the forwarding chain is complete.
Run journalctl -xeu firewalld if the rule fails to apply. Read the actual error before guessing.
Common pitfalls and error messages
Port forwarding fails in three predictable ways. The first is missing masquerading. You will see Error: COMMAND_FAILED when trying to add a forward-port rule if masquerade is disabled on the zone. The second is zone mismatch. Your interface might be in the home or work zone instead of public. Rules applied to the wrong zone never trigger. The third is SELinux blocking the target service. firewalld will forward the packet, but the application will refuse the connection if SELinux denies network access.
If you see Error: INVALID_FORWARD_PORT in the terminal, your syntax is wrong. The directive requires port=, proto=, and either toport= or toaddr=. Missing any parameter causes firewalld to reject the command. Fix the syntax and run the permanent command again.
If the connection times out but iptables shows the rule, check your router. Home ISPs and cloud providers often block inbound traffic on common ports like 80 or 443. Switch to a high port like 8443 or 2052 for testing. Verify the external gateway allows the traffic before blaming Fedora.
Check the target service logs. The firewall is only the door. The application behind it must be listening on 0.0.0.0 or the specific internal IP you forwarded to. Binding to 127.0.0.1 will drop forwarded traffic immediately. Docker and Podman default to 0.0.0.0 when you use -p or --publish, but custom systemd services often bind to localhost by default. Update the service configuration to listen on all interfaces or the specific LAN IP.
SELinux denials show up in journalctl -t setroubleshoot with a one-line summary. Read those before disabling SELinux. The httpd_can_network_connect or container_runtime booleans often need adjustment when services behind forwarded ports try to make outbound connections.
When to use port forwarding vs alternatives
Use port forwarding when you need to expose a single service on a non-standard port to the public internet. Use a reverse proxy like nginx or Caddy when you need to host multiple services on port 80 or 443 with different hostnames. Use SSH tunneling when you only need temporary access from your own machine and do not want to open firewall rules. Use a VPN when you need secure, encrypted access to multiple internal services without exposing them to the public network.