Follow up on this after a lot of pouring through kernel source code. In include/net/fib_rules.h we find this function: static inline bool fib_rule_requires_fldissect(struct fib_rule *rule) { return rule->iifindex != LOOPBACK_IFINDEX && (rule->ip_proto || fib_rule_port_range_set(&rule->sport_range) || fib_rule_port_range_set(&rule->dport_range)); } This gets called in fib4_rules_early_flow_dissect in include/net/ip_fib.h which seems to get called in various places in the kernel routing logic. On a hunch, I changed my ip rule to not rely on tcp port and everything immediately started working as I wanted. My plan as a workaround is to make ip rule use packet marks and set the packet marks in nftables. This does still feel like a bug in kernel behaviour, but I am not at all equipped to figure it out. Not even sure if this is the correct mailing list for it, but maybe this information helps someone else. Kind regards, Nicolai Moore