Another gotcha is that ipv4 and ipv6 has different firewall tables. So if you for example have blocked all traffic besides port 80, you also need to do it on ipv6 !
Simple example:
tcp_inbound = "{ ssh, domain, http, https }"
tcp_outbound = "{ domain, http, https, imaps, smtps }"
udp_services = "{ domain, ntp }"
block all
pass proto udp to any port $udp_services keep state
pass out proto tcp to any port $tcp_outbound keep state
pass in proto tcp to any port $tcp_inbound keep stateFYI, nftables has the 'inet' table which processes both IPv4 and IPv6 packets, and 'ip' and 'ip6' which processes each respectively.
Ubuntu created the ufw "uncomplicated firewall" wrapper for a reason :D
It has always seemed reasonably straightforward to me, although I will concede that not being able to combine tcp & udp into the same rule is a bit annoying (though also rarely needed).