How to open a port in the firewall¶
A service on your server listens on TCP port 8080, and you want other hosts to reach it through the host firewall, now and after every reboot. This guide opens the port with each distribution family's usual firewall tool, and installs and enables the firewall first where none is active, without ever closing SSH.
For another port or for UDP, replace 8080/tcp (or tcp dport 8080) with your port and protocol.
Before you begin¶
- You can run commands with
sudoon the server. - The service listens on an address other hosts can reach.
Check with
sudo ss -tlnp 'sport = :8080': a local address of0.0.0.0,[::],*or the server's own address works;127.0.0.1or[::1]means only the server itself can connect, and no firewall rule changes that. -
Use only one tool to manage the firewall on a host. This guide uses:
Family Tool Debian/Ubuntu ufw. Ubuntu installs it by default; on Debian this guide installs it too. If a Debian host already has an nftables ruleset loaded by nftables.service, this guide doesn't cover it: add the port to that ruleset rather than installing ufw alongside it.RHEL/Alma/Rocky firewalld, the default on these distributions Arch nftables directly, with /etc/nftables.confandnftables.serviceGentoo nftables directly, with the nftablesservice saving and restoring the live ruleset
Don't lock yourself out
If the firewall isn't active yet, the steps below enable it with a default of blocking incoming connections. Each tab allows SSH (TCP port 22) before the firewall becomes active, so your session survives. If your SSH server listens on another port, allow that port instead. Keep a console (for example your hypervisor's or provider's web console) open until you've checked that a new SSH login works.
Steps¶
-
Install the firewall if it's missing, and make sure SSH is allowed:
If ufw is inactive,
ufw allowonly records the rule; it takes effect when you enable ufw in step 3.Skip this step if
sudo firewall-cmd --statealready printsrunning. Otherwise install firewalld, allow SSH in its configuration while it's still stopped, and start it:sudo dnf install -y firewalld sudo firewall-offline-cmd --add-service=ssh sudo systemctl enable --now firewalldThe default zone,
public, normally allows SSH already, sofirewall-offline-cmdmay answerWarning: ALREADY_ENABLED: ssh.nftables is usually installed already; this makes sure:
The
/etc/nftables.confshipped with the package already allows SSH, butnftables.serviceisn't enabled by default, so it isn't active yet. If you've written your own ruleset, make sure it accepts TCP port 22.If
sudo nft list rulesetshows that the host has no ruleset yet, load a basic one that drops unsolicited incoming connections but allows SSH:sudo nft -f - <<'EOF' table inet filter { chain input { type filter hook input priority filter; policy drop; ct state established,related accept ct state invalid drop iif lo accept meta l4proto { icmp, ipv6-icmp } accept tcp dport 22 accept } } EOFThe ruleset is active immediately, and your SSH session continues because it's an established connection.
-
Allow TCP port 8080 permanently:
ufw stores the rule in
/etc/ufw/user.rulesanduser6.rules, so it covers IPv4 and IPv6 and persists across reboots.--permanentwrites the change to the zone's configuration in/etc/firewalld/zones/but doesn't apply it yet. This adds the port to the default zone; add--zone=<ZONE>if the interface that clients connect through is in another zone (sudo firewall-cmd --get-active-zonesshows which).nftables.serviceloads/etc/nftables.confat boot. Addtcp dport 8080 acceptto the input chain, after the rule that accepts SSH and before any rule that rejects or drops.If the file is still the one shipped with the package, its SSH rule is
tcp dport ssh accept, followed by a rate-limitedreject. Check that the rule is there, then add the new rule directly after it:grep -n 'tcp dport ssh accept' /etc/nftables.conf sudo sed -i '/tcp dport ssh accept/a\ tcp dport 8080 accept comment "allow port 8080"' /etc/nftables.confIf you maintain your own ruleset, add the line in the same place with a text editor. If the file doesn't exist, create it with this complete ruleset, which allows SSH and port 8080 and drops other incoming connections:
#!/usr/bin/nft -f destroy table inet filter table inet filter { chain input { type filter hook input priority filter; policy drop; ct state established,related accept ct state invalid drop iif lo accept meta l4proto { icmp, ipv6-icmp } accept tcp dport 22 accept tcp dport 8080 accept } }destroy table(nftables 1.0.7 and Linux 6.3 or later) removes the previous version of this table when the file is loaded again, without touching tables that other software created; the shipped file uses it too. Check the file without applying it; no output means it's valid: -
Apply the configuration and make sure it loads at boot:
--forceskips the question about disrupting SSH connections, which you've already answered by allowing port 22. Enabling ufw also makes it start at boot. If ufw was already active, the rules from step 2 are already in effect and this command changes nothing.This loads the file now and at every boot. The Arch unit has no reload action, so
reload-or-restartruns it again, and thedestroy tableline keeps that from duplicating rules.Save the running ruleset to
/var/lib/nftables/rules-saveand add the service to the default runlevel, so the ruleset is restored at boot:By default the service also saves the ruleset again when it stops (
SAVE_ON_STOP="yes"in/etc/conf.d/nftables). If you change rules later withnft, runsudo rc-service nftables saveagain.With the systemd profile (not verified), save the ruleset to the same file and enable the units that restore it at boot and save it at shutdown:
Verify¶
-
List the firewall configuration and check that port 8080 is allowed, alongside SSH:
With the ruleset shipped with the package:
$ sudo nft list chain inet filter input table inet filter { chain input { type filter hook input priority filter; policy drop; ct state invalid drop comment "early drop of invalid connections" ct state { established, related } accept comment "allow tracked connections" iif "lo" accept comment "allow from loopback" meta l4proto { icmp, ipv6-icmp } accept comment "allow icmp" tcp dport 22 accept comment "allow sshd" tcp dport 8080 accept comment "allow port 8080" meta pkttype host limit rate 5/second burst 5 packets counter packets 0 bytes 0 reject with icmpx admin-prohibited counter packets 0 bytes 0 } }$ sudo nft list chain inet filter input table inet filter { chain input { type filter hook input priority filter; policy drop; ct state established,related accept ct state invalid drop iif "lo" accept meta l4proto { icmp, ipv6-icmp } accept tcp dport 22 accept tcp dport 8080 accept } }Check that the service is in the default runlevel, so it restores the ruleset at boot:
With the systemd profile (not verified), check with
systemctl is-enabled nftables-loadinstead. -
From another host, connect to the port, where
<SERVER>is the server's host name or address:Any HTTP response, or for a service that doesn't speak HTTP any answer other than a timeout, shows the firewall let the connection through. Testing from the server itself proves nothing: connections from a host to itself travel over the loopback interface, which these firewalls always allow.
-
Open a new SSH session to the server to confirm SSH is still allowed.
Troubleshooting¶
The rule is listed, but clients still can't connect.
Check that the service listens on a reachable address (see Before you begin).
Then run sudo nft list ruleset and look for tables from more than one tool, such as inet firewalld next to ip filter tables from ufw or your own inet filter.
A packet must be accepted by every one of them, so a second firewall manager can still drop traffic the first one allows; disable all but one.
Finally, check for a firewall outside the host, such as a cloud security group or a network ACL.
firewalld: the port works until the next reload or reboot, or only after one.
A rule added without --permanent lives only in the runtime configuration and disappears on reload; a rule added with --permanent takes effect only after firewall-cmd --reload.
Compare sudo firewall-cmd --list-ports with sudo firewall-cmd --permanent --list-ports.
firewalld: the port is open, but in the wrong zone.
sudo firewall-cmd --get-active-zones shows which zone each interface or source is in; add the port to that zone with --zone=<ZONE>.
nftables: the rule is there, but traffic is still dropped.
Rules are checked in order, so a drop or reject rule earlier in the chain wins over an accept rule appended after it.
List the chain with handles (sudo nft -a list chain inet filter input) and insert the rule before the blocking one with sudo nft insert rule inet filter input handle <HANDLE> tcp dport 8080 accept, where <HANDLE> is the handle of the blocking rule; then save the ruleset again.
All rules disappeared after stopping nftables.service.
This applies only if you manage nftables through nftables.service, not through ufw or firewalld.
On Debian, Ubuntu and RHEL, stopping nftables.service flushes the entire ruleset.
Use systemctl reload nftables to apply changes to the service's file, not stop and start.
You've locked yourself out.
Log in on the console and turn the firewall off: sudo ufw disable, sudo systemctl stop firewalld, or sudo nft flush ruleset for nftables.
ufw disable and stopping firewalld last until you enable them again, but nft flush ruleset only empties the running ruleset: if nftables.service is enabled, it loads /etc/nftables.conf again at the next boot.
Before you reboot, fix the SSH rule in /etc/nftables.conf (Arch) or, on Gentoo, load a corrected ruleset and run sudo rc-service nftables save.
Then allow SSH and enable the firewall again.
Related¶
- Tutorial: Build your first nftables firewall
- Reference: nftables syntax
- Explanation: Netfilter architecture