New UFW Rule, Old Connection: Established State Wins
A scoped UFW route deny skips traffic that the before rules already accepted. See where to insert it and what established state still lets through.
Adding a deny rule and watching an open download continue is not a UFW bug. The rule you appended sits after rules that already accepted the flow, and a connection established earlier keeps matching them. Fixing that means inserting at the right position and understanding what the earlier rules promise. The narrow version of that change is what follows.
Order decides, not the existence of the rule
UFW is a front end that writes netfilter rules in stages: a framework section that runs before user rules, then the user rules themselves, then a section that runs after them. Stock before rules accept traffic belonging to established and related connections, and those rules are evaluated before anything you add with a normal allow or deny. The UFW command reference documents the insertion syntax and the numbered status view this change depends on.
That ordering explains the surprise. Your new rule is real and it is in the right table, but it is not the first decision the packet meets. A connection that was already tracked as established never reaches it.
Use route rules for this job, because the packet is being forwarded through the gateway rather than addressed to the gateway's own services. An input rule would filter the wrong traffic and appear to do nothing.
Insert above the broad permission
Preview the insertion before you commit it, and confirm the rule does not already exist elsewhere in the list. Duplicating an identical policy makes later removal ambiguous, and the person who removes the wrong copy will not notice for weeks.
sudo ufw --dry-run route insert 1 deny in on tun0 proto tcp from 10.88.0.55/32 to 192.0.2.88/32
If the preview shows exactly one rule in the position you expect, apply the same command without the dry-run flag. Position 1 places it above the broader user allowances, not above the framework rules reviewed earlier. Confirm the interface name matches the tunnel device on this gateway, because a rule scoped to an interface that no longer exists will sit in the list looking correct while matching nothing. Rename the device reference before you apply, not after the test fails.
No destination port appears in that rule, so it denies every TCP port at that address rather than only HTTPS. If the intent is one service, name the port; a rule that is broader than the policy is a future incident waiting for its first reader.
Restrict the source to a single address only if that address is bound to the intended identity. A VPN pool with dynamic assignment can hand the same address to another client tomorrow, and the deny would follow the address rather than the person.
Established flows survive the new rule
Connection tracking is the reason the existing session continues. Once a flow is recorded as established, the before rules accept its packets before your deny is consulted, and the transfer finishes as though nothing changed. Testing with a browser that reuses an open connection therefore tells you nothing about the new policy. A deny can look useless minutes after deployment and completely effective for the next user who connects.
If the requirement includes cutting live traffic, this user-rule change cannot deliver it. Revoking tracked state is a separate action with its own blast radius, and it belongs to the firewall owner rather than to whoever added the rule. Flushing every connection on a gateway to make one session disappear is the kind of fix that generates a second incident.
Remember the boundaries too. This rule says nothing about ICMP, UDP or IPv6, so a successful ping is not evidence that the deny failed. One IPv4 address is also not a website: a name can resolve to several addresses, and only the one you named is covered.
Test with a fresh flow
Read the numbered user rules and the effective raw view, then open a new connection from the selected client:
sudo ufw status numbered
sudo ufw show raw
# from 10.88.0.55, a new TCP connection to 192.0.2.88 should fail
The raw view matters because status omits framework rules that were written by hand, so it can make the ordering look different from what the kernel enforces. Compare the two before concluding that a rule is in the wrong place.
Then check the two controls that prove the rule is scoped rather than broad: a permitted destination from the same client still works, and a second client still reaches the denied destination if its policy allows it. A deny that breaks both tests is a policy change, not a targeted one.
When the test is over, remove only the rule you added, by restating it exactly. If the policy is permanent, hand it to the firewall owner with the client identity, the destination owner and a review date, so a reassigned tunnel address does not leave a stale deny behind.