Skip to content
Current coupon:10% offUse it in your order

Firewall inside the VPS: UFW (and nftables for advanced users)

Set up the UFW firewall right inside your VPS without locking yourself out: default policy, rules for services, limiting by IP address, limit, IPv6, logs. Why Docker bypasses UFW and how to use nftables.

  • advanced
  • ~20 min
  • Updated 1 Oct 2026

UFW (Uncomplicated Firewall) is a firewall right inside the VPS that you control with easy-to-read commands. You'll set it up so that only what you want is reachable from outside, without locking yourself out of SSH. At the end you'll find out why Docker bypasses UFW, plus a sample nftables configuration for advanced users.

What you'll need

  • A StormHost VPS with Ubuntu 24.04 or Debian 13
  • An SSH connection as root (guide)
  • The browser console open, just in case
  • A list of the ports your services use
  • About 20 minutes

UFW in a nutshell#

Linux filters packets in the kernel (the netfilter subsystem, controlled through nftables or iptables). Writing rules directly is complicated; UFW does it for you: it turns the command ufw allow 22/tcp into the right rules for both IPv4 and IPv6.

UFW is one of two options. The other is the firewall in the customer area, which filters traffic before it even reaches the VPS. If you use both, a port has to be open in both.

Installation and default policy#

Ubuntu 24.04

UFW is installed but turned off. Check it:

bash
ufw status

The output Status: inactive means the firewall isn't blocking anything yet.

Debian 13

Install UFW:

bash
apt install ufw

After installation it's turned off (ufw status prints Status: inactive).

Set the default policy, meaning what happens to traffic no rule mentions:

bash
ufw default deny incoming
ufw default allow outgoing
  • ufw default deny incoming – block new incoming connections unless a rule allows them.
  • ufw default allow outgoing – allow outgoing connections from the VPS (updates, DNS, downloads).

UFW is stateful: replies to connections the VPS opened itself get through even with deny incoming.

Allow SSH before you turn the firewall on#

Without an SSH rule you lock yourself out when you turn it on

ufw enable with the deny incoming policy immediately blocks everything that isn't allowed. If you don't have a rule for SSH at that moment, the connection drops. You can then get into the VPS only through the browser console. If your SSH runs on a port other than 22, allow that one.

bash
ufw allow 22/tcp comment 'SSH'
  • ufw allow – adds an allow rule for incoming traffic.
  • 22/tcp – port 22, TCP protocol.
  • comment 'SSH' – a note that shows in the rule list.

Alternatively you can use an application profile. ufw app list lists the profiles (for example OpenSSH, and after installing Nginx also Nginx Full). You then add the rule with ufw allow OpenSSH.

Turning it on and checking#

  1. Turn the firewall on:

    bash
    ufw enable
  2. UFW asks Command may disrupt existing ssh connections. Proceed with operation (y|n)?. If you have the SSH rule, type y and press Enter.

  3. You'll see Firewall is active and enabled on system startup. The firewall is now running and also starts after a restart.

  4. In a new window, check that you can connect over SSH.

  5. Show the detailed status:

    bash
    ufw status verbose
    Outputtext
    Status: active
    Logging: on (low)
    Default: deny (incoming), allow (outgoing), disabled (routed)
    New profiles: skip
    
    To                         Action      From
    --                         ------      ----
    22/tcp                     ALLOW IN    Anywhere                   # SSH
    22/tcp (v6)                ALLOW IN    Anywhere (v6)              # SSH
LineMeaning
Status: activeThe firewall is running.
Logging: on (low)Writes blocked packets to the log, with a limit on the number of entries.
Default: deny (incoming)Incoming traffic without a rule is dropped.
allow (outgoing)Outgoing traffic is allowed.
disabled (routed)UFW doesn't handle forwarded traffic (the VPS as a router).
New profiles: skipNew application profiles aren't allowed automatically.
22/tcp ALLOW IN AnywherePort 22/TCP is allowed from anywhere over IPv4.
(v6)The same rule for IPv6. UFW creates it automatically.

Rules for services#

What you wantCommand
Web (HTTP and HTTPS)ufw allow proto tcp from any to any port 80,443 comment 'Web'
Minecraft Javaufw allow 25565/tcp comment 'Minecraft'
Bedrock (Geyser)ufw allow 19132/udp comment 'Geyser'
FiveM (TCP and UDP)ufw allow 30120 comment 'FiveM'
Rust (game and query)ufw allow proto udp from any to any port 28015,28017 comment 'Rust'
Port rangeufw allow proto tcp from any to any port 25565:25600 comment 'Game servers'
Port only from one IP addressufw allow proto tcp from 198.51.100.25 to any port 3306 comment 'DB from office'
Block an IP addressufw insert 1 deny from 198.51.100.25 comment 'Attacker'

How to read the parts of the commands:

  • 25565/tcp – port and protocol. Without a protocol (ufw allow 30120) the rule applies to both TCP and UDP.
  • proto tcp – the protocol in the extended syntax. A range or list of ports (80,443, 25565:25600) requires a protocol.
  • from any / from 198.51.100.25 – where the connection comes from: anywhere, or from one address (it can also be a range 198.51.100.0/24).
  • to any port 3306 – which VPS address and port. any = all VPS addresses.
  • insert 1 – inserts the rule in first place. UFW evaluates rules from the top and the first match wins, so blocking an address must come above the allow rules. You block an IPv6 address with ufw prepend deny from 2001:db8::/64, which puts the rule at the start of the IPv6 rules.
  • deny – silently drop. The reject variant tells the sender the connection was refused.

In comments (comment), use only plain letters without accents, digits and spaces.

Limiting the number of connections (limit)#

bash
ufw limit 22/tcp comment 'SSH'
  • ufw limit – allows the port but blocks an IP address that tries to open 6 or more connections within 30 seconds. This slows down password guessing.

If you already have an allow rule for port 22, delete it first (see the next section), otherwise the older rule would win. Limit isn't suitable for services with many legitimate connections (web, games). For SSH, fail2ban is better, because it reacts to actual failed logins.

Numbered rules and deleting#

bash
ufw status numbered
Outputtext
     To                         Action      From
     --                         ------      ----
[ 1] 22/tcp                     ALLOW IN    Anywhere                   # SSH
[ 2] 25565/tcp                  ALLOW IN    Anywhere                   # Minecraft
[ 3] 22/tcp (v6)                ALLOW IN    Anywhere (v6)              # SSH
[ 4] 25565/tcp (v6)             ALLOW IN    Anywhere (v6)              # Minecraft

Deleting a rule by number:

bash
ufw delete 2
  • ufw delete 2 – deletes rule number 2. UFW asks for confirmation (y).

After each deletion the numbers shift, so run ufw status numbered again before deleting the next one. The IPv4 and IPv6 variants are two rules. You delete both at once by writing out the rule: ufw delete allow 25565/tcp.

Other useful commands:

CommandWhat it does
ufw reloadReloads the rules (after editing the files in /etc/ufw/ by hand).
ufw disableTurns the firewall off; the rules stay saved.
ufw --dry-run allow 80/tcpShows what the command would do, but changes nothing.
ufw resetTurns the firewall off and deletes all rules. Use it only when starting over.

IPv6#

UFW has IPv6 turned on by default: the file /etc/default/ufw has the line IPV6=yes. Every rule without a specific IPv4 address is then also created for IPv6 (the (v6) lines). Don't set IPV6=no. The VPS has a public IPv6 address, and without IPv6 rules IPv6 traffic wouldn't be filtered by your rules.

Docker bypasses UFW#

When you publish a port in Docker (-p 8080:80 or ports: in compose.yaml), it's reachable from the internet even if UFW blocks it. Docker redirects the traffic to the container before UFW's rules get to it. Docker's documentation describes this as an incompatibility between Docker and UFW.

How to avoid it:

  1. Publish ports only on 127.0.0.1. Then they're reachable only inside the VPS and you expose them through Nginx (see Nginx and HTTPS):

    compose.yamlYAML
    services:
      app:
        ports:
          - "127.0.0.1:8080:80"
    • 127.0.0.1:8080:80 – port 8080 on the VPS, but only on the local address, forwarded to port 80 in the container.
  2. Don't publish databases and internal services at all. Containers talk to each other over Docker's network; they don't need a port to the outside.

  3. Use the firewall in the customer area. It filters traffic before it reaches the VPS, so it also catches ports published by Docker.

Don't use the Docker setting "iptables": false; it breaks container networking. Advanced filtering of traffic to containers (the DOCKER-USER chain) is described in Docker's documentation (opens in a new window). More about Docker in the Docker and Docker Compose guide.

Logging#

bash
ufw logging low
  • low – logs blocked packets, with a limit on the number of entries. Higher levels (medium, high, full) log more and the log grows fast. ufw logging off turns logging off.

Where to find the entries:

Ubuntu 24.04

bash
tail -f /var/log/ufw.log
  • tail -f – keeps printing new lines of the file. Quit with Ctrl+C.

Debian 13

bash
journalctl -k -f --grep "UFW"
  • journalctl -k – kernel messages (by default Debian has no separate /var/log/ufw.log file).
  • -f – keep printing new entries.
  • --grep "UFW" – only lines from UFW.

Example entry: [UFW BLOCK] IN=eth0 … SRC=198.51.100.25 DST=203.0.113.10 … PROTO=TCP SPT=51234 DPT=23. SRC is the sender's address, DST your VPS, PROTO the protocol and DPT the port it tried to connect to.

For advanced users: nftables#

nftables is the modern tool for firewall rules in the kernel. On Debian it's the default framework, and UFW itself uses it under the hood (through the iptables-nft layer). Writing rules directly in nftables is useful when you need more than UFW can do.

Don't mix UFW and your own nftables

Use either UFW or your own /etc/nftables.conf. Turn UFW off (ufw disable) before switching to nftables. The configuration below starts with the flush ruleset command, which deletes all rules in the kernel, including Docker's and fail2ban's rules. So after every restart of the nftables service, restart Docker and fail2ban too. With Docker on the same VPS, you're better off staying with UFW or the firewall in the customer area.

A sample file for a server with SSH and a website:

/etc/nftables.conftext
#!/usr/sbin/nft -f

flush ruleset

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 { 80, 443 } accept
    }

    chain forward {
        type filter hook forward priority filter; policy drop;
    }

    chain output {
        type filter hook output priority filter; policy accept;
    }
}
LineWhat it does
flush rulesetDeletes all existing rules so the file always loads from a clean state.
table inet filterThe filter table for the inet family, meaning IPv4 and IPv6 at once.
type filter hook input … policy dropThe chain for incoming traffic. Anything no rule allows is dropped.
ct state established,related acceptLets through replies to connections already established. Without this, nothing the VPS opens itself would work.
ct state invalid dropDrops damaged or nonsensical packets.
iif "lo" acceptAllows traffic inside the VPS (the loopback, 127.0.0.1). Without it, local databases and services stop working.
meta l4proto { icmp, ipv6-icmp } acceptAllows ICMP: ping and the messages needed for the network to work properly. IPv6 doesn't work without ICMPv6 (neighbor discovery).
tcp dport 22 acceptSSH. If you use a different port, change it.
tcp dport { 80, 443 } acceptWeb. A set of ports in curly braces. For UDP use udp dport.
chain forward … policy dropThe VPS won't forward traffic (it isn't a router).
chain output … policy acceptOutgoing traffic without restrictions.

Deployment (applies to both Debian 13 and Ubuntu 24.04):

  1. Install the tool: apt install nftables.

  2. Turn UFW off if it's running: ufw disable.

  3. Save the configuration to /etc/nftables.conf (nano /etc/nftables.conf).

  4. Check it without applying it:

    bash
    nft -c -f /etc/nftables.conf
    • -c – check only; nothing changes. If everything is fine, it prints nothing.
    • -f – load the rules from a file.
  5. Enable the service that loads the rules right away and after every boot:

    bash
    systemctl enable --now nftables
    • enable – load the rules when the VPS boots (the service is off by default).
    • --now – and load them right away.
  6. Check SSH in a new window. nft list ruleset prints the current rules.

Verification#

  • ufw status verbose shows Status: active and rules for all your services.
  • You can connect from your computer over SSH and the services work.
  • A port that isn't allowed doesn't respond from outside (test from your computer: on Windows Test-NetConnection <VPS_IP_ADDRESS> -Port 3306, on macOS and Linux nc -vz -w 5 <VPS_IP_ADDRESS> 3306; a closed port ends with a timeout).

If you lock yourself out#

  1. Open the browser console and log in as root.
  2. Allow SSH: ufw allow 22/tcp, or turn the firewall off temporarily: ufw disable.
  3. With nftables: systemctl stop nftables and then nft flush ruleset (deletes all rules; traffic will be allowed).
  4. Fix the rules and turn the firewall back on.

Troubleshooting#

ERROR: Must specify 'tcp' or 'udp' with multiple ports#

A range or list of ports requires a protocol. Write the rule like this: ufw allow proto tcp from any to any port 25565:25600. For both TCP and UDP, add two rules.

The rule is added, but the port is still closed#

  • Check the order (ufw status numbered). There must be no deny rule above it that drops the same traffic.
  • Check that the application listens on the port: ss -tulpn | grep <PORT>. The address 127.0.0.1 means it's reachable only inside the VPS.
  • Check the firewall in the customer area. If it's in the Block everything except allowed mode, the port must be allowed there too.

A Docker port is open even though UFW blocks it#

That's the expected behavior, see the Docker bypasses UFW section.

ERROR: initcaps when turning it on#

UFW can't use the kernel modules. Typically you updated the kernel and the VPS isn't running the new one yet. Restart the VPS and turn UFW on again.

Docker or fail2ban stopped working after restarting the nftables service#

flush ruleset deleted their rules. Restart them: systemctl restart docker fail2ban.

Summary

  • First ufw allow 22/tcp, only then ufw enable.
  • Default policy: deny incoming, allow outgoing; rules apply from the top and the first match wins.
  • ufw status numbered and ufw delete <number> for managing rules, ufw status verbose for checking.
  • IPv6 is on (IPV6=yes); rules are created for both versions.
  • Ports published by Docker bypass UFW: publish on 127.0.0.1, or use the firewall in the customer area.
  • nftables only for advanced users and never together with UFW.

Search the guides