Skip to content

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 sudo on the server.
  • The service listens on an address other hosts can reach. Check with sudo ss -tlnp 'sport = :8080': a local address of 0.0.0.0, [::], * or the server's own address works; 127.0.0.1 or [::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.conf and nftables.service
    Gentoo nftables directly, with the nftables service 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

  1. Install the firewall if it's missing, and make sure SSH is allowed:

    sudo apt update
    sudo apt install -y ufw
    sudo ufw allow 22/tcp
    

    If ufw is inactive, ufw allow only records the rule; it takes effect when you enable ufw in step 3.

    Skip this step if sudo firewall-cmd --state already prints running. 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 firewalld
    

    The default zone, public, normally allows SSH already, so firewall-offline-cmd may answer Warning: ALREADY_ENABLED: ssh.

    nftables is usually installed already; this makes sure:

    sudo pacman -S --needed --noconfirm nftables
    

    The /etc/nftables.conf shipped with the package already allows SSH, but nftables.service isn't enabled by default, so it isn't active yet. If you've written your own ruleset, make sure it accepts TCP port 22.

    sudo emerge --noreplace net-firewall/nftables
    

    If sudo nft list ruleset shows 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
        }
    }
    EOF
    

    The ruleset is active immediately, and your SSH session continues because it's an established connection.

  2. Allow TCP port 8080 permanently:

    sudo ufw allow 8080/tcp
    

    ufw stores the rule in /etc/ufw/user.rules and user6.rules, so it covers IPv4 and IPv6 and persists across reboots.

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

    --permanent writes 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-zones shows which).

    nftables.service loads /etc/nftables.conf at boot. Add tcp dport 8080 accept to 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-limited reject. 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.conf
    

    If 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:

    sudo nft --check --file /etc/nftables.conf
    

    Add the rule to the running ruleset:

    sudo nft add rule inet filter input tcp dport 8080 accept
    

    If your ruleset uses another table or chain name, use those instead of inet filter input. The nftables service makes the rule permanent in the next step.

  3. Apply the configuration and make sure it loads at boot:

    sudo ufw --force enable
    

    --force skips 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.

    sudo firewall-cmd --reload
    

    The reload makes the permanent configuration the running one.

    sudo systemctl enable nftables
    sudo systemctl reload-or-restart nftables
    

    This loads the file now and at every boot. The Arch unit has no reload action, so reload-or-restart runs it again, and the destroy table line keeps that from duplicating rules.

    Save the running ruleset to /var/lib/nftables/rules-save and add the service to the default runlevel, so the ruleset is restored at boot:

    sudo rc-service nftables save
    sudo rc-update add nftables default
    sudo rc-service nftables start
    

    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 with nft, run sudo rc-service nftables save again.

    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:

    sudo systemctl start nftables-store
    sudo systemctl enable nftables-load nftables-store
    

Verify

  1. List the firewall configuration and check that port 8080 is allowed, alongside SSH:

    $ sudo ufw status
    Status: active
    
    To                         Action      From
    --                         ------      ----
    22/tcp                     ALLOW       Anywhere
    8080/tcp                   ALLOW       Anywhere
    22/tcp (v6)                ALLOW       Anywhere (v6)
    8080/tcp (v6)              ALLOW       Anywhere (v6)
    
    $ sudo firewall-cmd --list-ports
    8080/tcp
    $ sudo firewall-cmd --list-services
    cockpit dhcpv6-client 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:

    $ rc-update show default | grep nftables
                   nftables | default
    

    With the systemd profile (not verified), check with systemctl is-enabled nftables-load instead.

  2. From another host, connect to the port, where <SERVER> is the server's host name or address:

    curl -I http://<SERVER>:8080/
    

    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.

  3. 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.