How To

UFW Firewall Commands with Examples on Ubuntu 24.04 / 22.04

UFW (Uncomplicated Firewall) is the default firewall management tool on Ubuntu and other Debian-based distributions. It provides a straightforward command-line interface on top of iptables (or nftables on newer kernels) for managing host-based firewall rules. Instead of writing raw iptables rules, UFW lets you allow or block traffic with simple, readable commands.

Original content from computingforgeeks.com - post 145106

This guide covers every common UFW operation – from basic allow/deny rules to rate limiting, Docker integration, port forwarding, and logging. All commands were tested on Ubuntu 24.04 with UFW 0.36.2.

Prerequisites

To follow this guide, you need:

  • Ubuntu 24.04 or 22.04 (server or desktop)
  • Root access or a user with sudo privileges
  • SSH access to the server (if remote)

Step 1: Install and Enable UFW

UFW comes pre-installed on Ubuntu. Verify it is present and check the version:

sudo apt install ufw

Check the installed version:

ufw version

Output:

ufw 0.36.2
Copyright 2008-2023 Canonical Ltd.

Before enabling UFW, set default policies. This is important because the defaults determine what happens to traffic that does not match any explicit rule. Deny all incoming connections and allow all outgoing:

sudo ufw default deny incoming
sudo ufw default allow outgoing

If you are connected over SSH, allow SSH traffic before enabling UFW. Skipping this step will lock you out of the server:

sudo ufw allow ssh

Now enable UFW:

sudo ufw enable

Output:

Command may disrupt existing ssh connections. Proceed with operation (y|n)? y
Firewall is active and enabled on system startup

Verify the firewall status with verbose output:

sudo ufw status verbose

Output:

Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)

To                         Action      From
--                         ------      ----
22/tcp                     ALLOW IN    Anywhere
22/tcp (v6)                ALLOW IN    Anywhere (v6)

Step 2: Allow Services and Ports

UFW accepts both service names (from /etc/services) and port numbers. Here are the most common ways to open ports.

Allow by service name:

sudo ufw allow http
sudo ufw allow https

Allow by port number and protocol:

sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

Allow a port with a comment (useful for tracking why a rule exists):

sudo ufw allow 8080/tcp comment 'Web app'

Allow a range of ports. You must specify the protocol when using ranges:

sudo ufw allow 6000:6100/tcp

Allow UDP traffic on a specific port (for example, DNS):

sudo ufw allow 53/udp

Step 3: Allow from Specific IPs and Subnets

For tighter security, restrict access to specific source IPs or subnets instead of opening ports to the entire internet.

Allow all traffic from a single IP address:

sudo ufw allow from 10.0.1.50

Allow all traffic from a subnet:

sudo ufw allow from 10.0.1.0/24

Allow a subnet to connect to a specific port (for example, MySQL on port 3306):

sudo ufw allow from 192.168.1.0/24 to any port 3306

Allow an IP to reach a specific port on a specific network interface:

sudo ufw allow in on eth0 from 10.0.1.0/24 to any port 22

This is useful on multi-homed servers where you want SSH access only on the management interface.

Step 4: Deny and Reject Traffic

UFW supports two ways to block traffic: deny and reject. Both prevent the connection, but they behave differently:

  • deny – silently drops the packet. The sender gets no response and eventually times out.
  • reject – drops the packet but sends an ICMP “port unreachable” response. The sender immediately knows the connection was refused.

Use deny for external-facing rules (attackers learn nothing) and reject for internal networks (faster feedback for legitimate users hitting the wrong port).

Deny all traffic from a specific IP:

sudo ufw deny from 203.0.113.100

Deny an entire subnet:

sudo ufw deny from 203.0.113.0/24

Deny outgoing traffic on a port (for example, block outbound SMTP to prevent spam):

sudo ufw deny out 25

Reject incoming traffic on a specific interface and port:

sudo ufw reject in on eth0 to any port 80

Step 5: Rate Limiting for Brute Force Protection

UFW has a built-in rate limiting feature that denies connections from an IP address if it attempts 6 or more connections within 30 seconds. This is effective against SSH brute force attacks without needing additional tools like Fail2ban.

Enable rate limiting on SSH:

sudo ufw limit ssh/tcp

Output:

Rule updated
Rule updated (v6)

If you previously had a plain allow rule for SSH, the limit command replaces it. You can verify with sudo ufw status – the Action column will show LIMIT IN instead of ALLOW IN.

For production servers exposed to the internet, rate limiting SSH is one of the first hardening steps you should apply. For more advanced blocking patterns (repeated failures across longer time windows, multiple services), install Fail2ban alongside UFW.

Step 6: Delete UFW Rules

There are two ways to delete rules: by rule number or by rule specification.

First, list all rules with their numbers:

sudo ufw status numbered

Output:

Status: active

     To                         Action      From
     --                         ------      ----
[ 1] 22/tcp                     LIMIT IN    Anywhere
[ 2] 80/tcp                     ALLOW IN    Anywhere
[ 3] 443/tcp                    ALLOW IN    Anywhere
[ 4] 8080/tcp                   ALLOW IN    Anywhere                   # Web app
[ 5] 6000:6100/tcp              ALLOW IN    Anywhere
[ 6] 53/udp                     ALLOW IN    Anywhere
[ 7] Anywhere                   ALLOW IN    10.0.1.50
[ 8] Anywhere                   ALLOW IN    10.0.1.0/24
[ 9] 3306                       ALLOW IN    192.168.1.0/24
[10] 22/tcp (v6)                LIMIT IN    Anywhere (v6)
[11] 80/tcp (v6)                ALLOW IN    Anywhere (v6)
[12] 443/tcp (v6)               ALLOW IN    Anywhere (v6)
[13] 8080/tcp (v6)              ALLOW IN    Anywhere (v6)
[14] 6000:6100/tcp (v6)         ALLOW IN    Anywhere (v6)
[15] 53/udp (v6)                ALLOW IN    Anywhere (v6)

Delete a rule by its number. For example, to remove the port range rule at position 5:

sudo ufw delete 5

Delete a rule by specification (matches the exact rule you originally added):

sudo ufw delete allow 6000:6100/tcp

When deleting by number, rule numbers shift after each deletion. If you need to delete multiple rules, delete from the highest number first to avoid renumbering issues.

Step 7: Insert Rules at a Specific Position

UFW evaluates rules in order – the first matching rule wins. This means rule position matters. If a deny rule appears before an allow rule for the same traffic, the deny takes precedence.

Insert a rule at a specific position using insert:

sudo ufw insert 1 allow from 10.0.1.50

This places the rule at position 1 (top of the list), ensuring it is evaluated before any other rules. This is useful when you have a general deny rule but want to add an exception for a trusted IP.

Step 8: Application Profiles

Application profiles are predefined rule sets stored in /etc/ufw/applications.d/. Packages like OpenSSH and Nginx install their own profiles automatically.

List available application profiles:

sudo ufw app list

Output (on a minimal Ubuntu install):

Available applications:
  OpenSSH

View details about a specific profile:

sudo ufw app info OpenSSH

Output:

Profile: OpenSSH
Title: Secure Shell Server
Description: OpenSSH is a free implementation of the Secure Shell protocol.

Ports:
  22/tcp

Allow traffic using the application profile name:

sudo ufw allow OpenSSH

Create Custom Application Profiles

You can create custom profiles for your own applications. Create a file in /etc/ufw/applications.d/:

sudo nano /etc/ufw/applications.d/mywebapp

Add the following content:

[MyWebApp]
title=My Custom Web Application
description=Web application running on port 8443
ports=8443/tcp

After creating the profile, reload the app list and allow it:

sudo ufw app update MyWebApp
sudo ufw allow MyWebApp

Step 9: UFW Logging

UFW logging helps you monitor blocked connections, detect scanning attempts, and troubleshoot connectivity issues.

Enable logging:

sudo ufw logging on

UFW supports five logging levels, each progressively more verbose:

  • off – no logging
  • low – logs blocked packets that do not match the default policy, plus packets matching logged rules
  • medium – same as low plus invalid packets, new connections, and rate-limited packets
  • high – same as medium plus all packets with rate limiting
  • full – logs everything without rate limiting (generates a lot of output)

Set the logging level:

sudo ufw logging medium

Output:

Logging enabled

UFW writes logs to /var/log/ufw.log. Each log entry contains useful information:

sudo tail -5 /var/log/ufw.log

A typical log line looks like:

Mar 23 10:15:42 server kernel: [UFW BLOCK] IN=eth0 OUT= MAC=... SRC=203.0.113.50 DST=10.0.1.10 LEN=44 TOS=0x00 PROTO=TCP SPT=54321 DPT=22 WINDOW=1024 SYN

Key fields to look at:

  • [UFW BLOCK] – the action taken (BLOCK, ALLOW, or AUDIT)
  • SRC – source IP address
  • DST – destination IP address
  • DPT – destination port
  • PROTO – protocol (TCP, UDP, ICMP)

Step 10: UFW with Docker (Critical Fix)

This is one of the most common pitfalls for Ubuntu servers running Docker. Docker manipulates iptables directly by inserting rules into the DOCKER chain, which bypasses UFW entirely. This means a container exposing port 8080 is accessible from the internet even if UFW has no rule allowing port 8080.

Fix 1: Use the DOCKER-USER Chain

Docker provides a DOCKER-USER chain that runs inside the FORWARD chain before Docker’s own published-port rules. Any packet going through the FORWARD chain (which is how traffic reaches a Docker container) passes through DOCKER-USER first. This gives you a hook to filter container traffic without fighting Docker’s own rules.

The block we install does three things: it creates the filtering chains, routes new TCP connections through UFW’s own user-forward chain so you can allow specific ports with regular UFW commands, and drops everything else with a rate-limited log entry.

Here is what each chain in the block does:

  • DOCKER-USER is Docker’s own user-editable filtering chain. Docker creates it at daemon startup. We declare it in after.rules as a safety net so the rules still load cleanly if UFW reloads before Docker is up.
  • ufw-user-forward is UFW’s internal chain for user-managed routed rules. It is empty until you run sudo ufw route allow .... By making DOCKER-USER jump to it first, container traffic becomes subject to whatever routed rules you add later, which lets you whitelist ports with normal UFW commands instead of re-editing after.rules.
  • ufw-docker-logging-deny is a custom chain that rate-limits log messages and then drops the packet. Keeping the log-and-drop pair in its own chain is cleaner than inlining it into DOCKER-USER.

The block goes in after.rules because that is where custom filter rules belong. At ufw enable or reload, UFW loads its rule files in this order: before.rules, then after.rules, then user.rules. The Docker block creates the DOCKER-USER to ufw-user-forward jump when after.rules loads, and by the time user.rules runs, any ufw route allow rules you have added get appended to the already-existing ufw-user-forward chain. At runtime, the chain reference and the user rules meet up correctly. Step 11 puts its NAT rules in before.rules instead because NAT table rules must run early in the packet pipeline.

Open the file:

sudo nano /etc/ufw/after.rules

Add the following block at the end of the file:

# BEGIN UFW AND DOCKER
*filter
:ufw-user-forward - [0:0]
:ufw-docker-logging-deny - [0:0]
:DOCKER-USER - [0:0]
-A DOCKER-USER -j ufw-user-forward

-A DOCKER-USER -j RETURN -s 10.0.0.0/8
-A DOCKER-USER -j RETURN -s 172.16.0.0/12
-A DOCKER-USER -j RETURN -s 192.168.0.0/16

-A DOCKER-USER -p udp -m udp --sport 53 --dport 1024:65535 -j RETURN

-A DOCKER-USER -p udp -m udp -j ufw-docker-logging-deny
-A DOCKER-USER -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -j ufw-docker-logging-deny
-A DOCKER-USER -j RETURN

-A ufw-docker-logging-deny -m limit --limit 3/min --limit-burst 10 -j LOG --log-prefix "[UFW DOCKER BLOCK] "
-A ufw-docker-logging-deny -j DROP

COMMIT
# END UFW AND DOCKER

The key lines are the two near the bottom of the DOCKER-USER chain:

  • -A DOCKER-USER -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -j ufw-docker-logging-deny drops only new TCP connections (packets with just the SYN flag set). Return traffic and data packets of already-established connections have different TCP flags and pass through untouched. This is why the block does not kill bidirectional communication.
  • -A DOCKER-USER -j RETURN at the end of the chain explicitly returns to the calling FORWARD chain for anything that was not dropped. Without this final RETURN, the default chain policy would apply to any unmatched packet.

The three RETURN -s 10.0.0.0/8, 172.16.0.0/12, and 192.168.0.0/16 lines mean traffic from any RFC1918 private network bypasses the deny-by-default logic entirely. This is intentional: container traffic to and from other containers, LAN hosts, and the Docker bridge networks all comes from RFC1918 addresses. If you want to block private network traffic to containers as well (strict zero-trust), remove those three RETURN lines.

Reload UFW to apply:

sudo ufw reload

After reload, public traffic to container ports is blocked unless you explicitly allow it. Internal traffic (RFC1918, container-to-container) still works normally so applications keep talking to each other.

Whitelisting a Container Port

To open a container port to public traffic, use ufw route allow. There is an important detail here that trips people up: the rule must match the container’s internal port, not the host-published port. Docker sets up a DNAT rule in PREROUTING that rewrites the destination from the host port to the container port before the packet ever reaches the FORWARD chain. By the time DOCKER-USER sees it, the destination port has already been translated.

For example, if you started a container with docker run -d -p 8080:80 nginx:alpine, the container listens on port 80 internally, and Docker publishes it on the host’s port 8080. To allow public access, you whitelist port 80, not 8080:

sudo ufw route allow proto tcp from any to any port 80

Verify the rule took effect by inspecting ufw-user-forward:

sudo iptables -L ufw-user-forward -n -v

The ACCEPT rule shows up with a packet counter that increases as external traffic hits the container:

Chain ufw-user-forward (2 references)
 pkts bytes target     prot opt in     out     source               destination
    7   454 ACCEPT     6    --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:80

You can restrict the source to a specific IP or subnet so only trusted hosts reach the container:

sudo ufw route allow proto tcp from 203.0.113.50 to any port 80

To revoke the whitelist, use ufw route delete with the exact same specification:

sudo ufw route delete allow proto tcp from any to any port 80

If two containers on the same host both expose port 80 internally (say, docker run -p 8080:80 nginx and docker run -p 9000:80 httpd), a single ufw route allow ... port 80 rule opens both. For per-container access control, use destination IPs: sudo ufw route allow proto tcp from any to 172.17.0.2 port 80 targets the container’s Docker bridge IP directly.

Fix 2: Disable Docker’s iptables Management

An alternative approach is to disable Docker’s iptables manipulation entirely. Edit or create /etc/docker/daemon.json:

sudo nano /etc/docker/daemon.json

Add this configuration:

{
  "iptables": false
}

Restart Docker:

sudo systemctl restart docker

With this approach, you manage all port exposure through UFW manually. Container-to-container networking still works over Docker networks, but published ports will not be reachable from external hosts unless you add UFW rules for them. This gives you full control but requires more manual configuration.

Step 11: Port Forwarding and NAT with UFW

UFW can handle port forwarding (DNAT) by adding rules to /etc/ufw/before.rules. This is useful when your server acts as a gateway and you need to forward incoming traffic to an internal server.

First, enable IP forwarding. Edit /etc/ufw/sysctl.conf:

sudo nano /etc/ufw/sysctl.conf

Uncomment or add this line:

net/ipv4/ip_forward=1

Next, add NAT rules to /etc/ufw/before.rules. Add the following block before the *filter section at the top of the file:

sudo nano /etc/ufw/before.rules

Add before the *filter line:

# NAT table rules
*nat
:PREROUTING ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]

# Forward port 80 to internal server 10.0.1.20
-A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 10.0.1.20:80

# Masquerade outgoing traffic from internal network
-A POSTROUTING -s 10.0.1.0/24 -o eth0 -j MASQUERADE

COMMIT

Also allow forwarded traffic in UFW. Change the default forward policy in /etc/default/ufw:

sudo nano /etc/default/ufw

Change this line:

DEFAULT_FORWARD_POLICY="ACCEPT"

Reload UFW to apply the changes:

sudo ufw reload

Step 12: Reset and Disable UFW

To disable UFW without removing any rules (rules are preserved and will be active again when you re-enable):

sudo ufw disable

Output:

Firewall stopped and disabled on system startup

To completely reset UFW, removing all rules and returning to default settings:

sudo ufw reset

Output:

Resetting all rules to installed defaults. This may disrupt existing ssh
connections. Proceed with operation (y|n)? y
Backing up 'user.rules' to '/etc/ufw/user.rules.20260323_101542'
Backing up 'before.rules' to '/etc/ufw/before.rules.20260323_101542'
Backing up 'after.rules' to '/etc/ufw/after.rules.20260323_101542'
Backing up 'user6.rules' to '/etc/ufw/user6.rules.20260323_101542'
Backing up 'before6.rules' to '/etc/ufw/before6.rules.20260323_101542'
Backing up 'after6.rules' to '/etc/ufw/after6.rules.20260323_101542'

UFW backs up your rules before resetting, so you can restore them if needed. After a reset, you need to re-enable UFW and add your rules from scratch.

Step 13: UFW vs iptables vs nftables vs firewalld

Linux has several firewall management tools. Here is how they compare:

FeatureUFWiptablesnftablesfirewalld
Ease of useVery easyComplexModerateEasy
Default onUbuntu, DebianLegacy LinuxDebian 11+, RHEL 9+RHEL, Rocky, Alma, Fedora
Backendiptables or nftablesNetfilter (legacy)Netfilter (modern)nftables (or iptables)
Zone supportNoNoNoYes
Dynamic reloadNo (requires reload)NoYesYes
IPv6 supportYes (automatic)Separate ip6tablesYes (unified)Yes (automatic)
Rich rulesLimitedFull controlFull controlRich rule syntax
Best forSingle servers, VPSAdvanced custom rulesModern custom rulesEnterprise RHEL servers

For Ubuntu servers, UFW is the right choice for most use cases. If you need zone-based rules or manage RHEL-family systems, see our guide on configuring firewalld on Rocky Linux / AlmaLinux / RHEL. For Ubuntu systems where you prefer firewalld over UFW, we also have a guide on installing firewalld on Ubuntu.

Quick Reference: Common UFW Commands

The table below summarizes every common UFW command covered in this guide for quick reference.

ActionCommand
Enable UFWsudo ufw enable
Disable UFWsudo ufw disable
Check statussudo ufw status verbose
Set default deny incomingsudo ufw default deny incoming
Set default allow outgoingsudo ufw default allow outgoing
Allow port (TCP)sudo ufw allow 80/tcp
Allow port (UDP)sudo ufw allow 53/udp
Allow service by namesudo ufw allow ssh
Allow port rangesudo ufw allow 6000:6100/tcp
Allow from IPsudo ufw allow from 10.0.1.50
Allow from subnet to portsudo ufw allow from 192.168.1.0/24 to any port 3306
Deny from IPsudo ufw deny from 203.0.113.100
Deny outgoing portsudo ufw deny out 25
Reject trafficsudo ufw reject in on eth0 to any port 80
Rate limit SSHsudo ufw limit ssh/tcp
Delete rule by numbersudo ufw delete 5
Delete rule by specsudo ufw delete allow 80/tcp
Insert rule at positionsudo ufw insert 1 allow from 10.0.1.50
List app profilessudo ufw app list
Show numbered rulessudo ufw status numbered
Enable loggingsudo ufw logging medium
Reset all rulessudo ufw reset
Reload rulessudo ufw reload

Conclusion

UFW provides a straightforward way to manage firewall rules on Ubuntu servers. For production deployments, keep these practices in mind:

  • Always set default policies (deny incoming, allow outgoing) before adding specific rules
  • Allow SSH before enabling UFW on remote servers – getting locked out is painful
  • Use rate limiting on SSH (sudo ufw limit ssh/tcp) as a first line of defense against brute force
  • If running Docker, apply the DOCKER-USER chain fix – Docker bypasses UFW by default
  • Restrict database ports (3306, 5432, 6379) to specific IPs or subnets, never open them to the world
  • Enable logging at medium level for good visibility without excessive noise
  • Review rules periodically with sudo ufw status numbered and remove anything no longer needed

For securing SSH access on your Ubuntu server beyond firewall rules, see our guide on installing and configuring SSH server on Ubuntu. If you run a WireGuard VPN, remember to allow the VPN port (typically 51820/udp) in UFW as well.

Related Articles

Security What is WPA2 Encryption and How to Implement It on a Linux Machine Security 5 Ways to Avoid Becoming a Victim of Phishing Attacks Debian Install Oracle Java 18 (OpenJDK 18) on Ubuntu / Debian Elasticseach How to delete Elasticsearch Index data with curl

5 thoughts on “UFW Firewall Commands with Examples on Ubuntu 24.04 / 22.04”

  1. Could you elaborate more on why we introduce the extra chains in step 10, fix 1?
    Especially ufw-user-forward has no rules added to it, what is its purpose? Is it for a more complex setup where one might already have other rules that go there?
    Also why are those lines added in after.rules and not before.rules?

    Thanks for the great article, works like a charm.

    Reply
    • Hi Power, great questions. The chain interactions are not obvious, so here is the full picture.

      Why the extra chains (and why ufw-user-forward looks empty):

      ufw-user-forward is UFW’s own internal chain for user-managed routed rules. It is not truly empty in practice. It gets populated when you run commands like:

      sudo ufw route allow proto tcp from any to any port 8080

      That adds a rule to ufw-user-forward. Because DOCKER-USER jumps to it first (-A DOCKER-USER -j ufw-user-forward), Docker container traffic becomes subject to whatever routed rules you have defined with ufw route. That is exactly the point. It lets you manage Docker container exposure using normal UFW commands instead of editing iptables rules by hand.

      The :ufw-user-forward – [0:0] line does not create the chain from scratch. It is a declaration that guarantees the chain exists at rule load time. UFW already creates it during its own rule generation, so the declaration is a safety net that prevents the -A DOCKER-USER -j ufw-user-forward line from failing if the chain is not there yet.

      ufw-docker-logging-deny is the opposite, a custom chain defined from scratch because we want rate-limited logging plus a DROP at the end. You could inline those rules into DOCKER-USER directly, but keeping them in a dedicated chain makes the logic cleaner.

      DOCKER-USER is declared for the same safety reason. Docker creates it at daemon startup, but if UFW loads its rules before Docker has started (on boot, for example), the reference would fail. The declaration guarantees it exists.

      Why after.rules and not before.rules:

      UFW processes files in this order at rule generation time:

      1. before.rules (before UFW’s generated rules)
      2. UFW’s own rules generated from ufw allow/deny/route commands (this is where ufw-user-forward gets populated)
      3. after.rules (after UFW’s generated rules)

      The Docker fix belongs in after.rules for two reasons. First, by the time after.rules is processed, ufw-user-forward already exists with whatever routed rules you have added via ufw route. Second, the DOCKER-USER filtering logically belongs after UFW’s own rules have been set up, not before them.

      before.rules is reserved for things that must run earlier in the packet pipeline: NAT table rules (PREROUTING, POSTROUTING, MASQUERADE), mangle table edits, or low-level filtering that needs to run before UFW. That is why Step 11 puts its DNAT port forwarding rules in before.rules. NAT has to be set up early in the packet flow.

      Thanks for the detailed question, and glad the guide helped.

      Reply
    • Quick follow-up: I updated Step 10 Fix 1 to cover all of this directly in the article. It now lists what each chain does (DOCKER-USER, ufw-user-forward, ufw-docker-logging-deny), explains why the block goes in after.rules rather than before.rules, and shows how to whitelist a container port with sudo ufw route allow proto tcp from any to any port 8080. Thanks for flagging the gap.

      Reply
    • One more update, Power. I spun up a fresh Ubuntu 24.04 VM with Docker 29.1.3 and actually tested the full flow (run nginx with -p 8080:80, verify bypass, apply Fix 1, verify block, test ufw route allow). Two corrections compared to what I posted earlier:

      1. The original iptables block had a bug. A plain -A DOCKER-USER -j ufw-docker-logging-deny drops ALL non-DNS non-RFC1918 traffic, including return packets from the container, which breaks bidirectional communication. The corrected block (now in the article) uses –tcp-flags FIN,SYN,RST,ACK SYN so only new TCP connections are dropped, and ends with a final -j RETURN so non-matching packets fall back to the calling chain.

      2. ufw route allow must match the containers INTERNAL port, not the host-published port. Docker DNAT rewrites the destination port in PREROUTING before the packet reaches the FORWARD chain, so by the time DOCKER-USER sees it, the destination is already the container port. For docker run -p 8080:80, the correct whitelist is sudo ufw route allow proto tcp from any to any port 80, not port 8080.

      3. The file load order I mentioned was also slightly off. UFW actually loads before.rules, then after.rules, then user.rules (where ufw route allow rules live). The block still works because after.rules just creates the DOCKER-USER to ufw-user-forward jump, and user.rules populates ufw-user-forward later. At runtime the chain reference and the user rules meet up correctly.

      Article Step 10 Fix 1 is now rewritten and fully tested end to end. Thanks again for pushing on this, it caught real issues.

      Reply

Leave a Comment

Press ESC to close