NAT port forwarding on bastion host not working after switching to NFTables

Network topology: Virtual private server (VPS) hosts wireguard server and performs NAT over ports 80 and 443 to a home machine which hosts my webserver.

I’ve been successfully using iptables to perform port forwarding for a while but during a revamp of my DNS setup I wanted to finally migrate to NFTables as it is the modern replacement for IPtables as I understand it.

From what I can tell the firewall rules that nix generates for each should accomplish the same thing but NFTables doesn’t seem to be forwarding the traffic.

IPTables Nix Config:

{
  networking.firewall = {
    enable = true;
    allowedUDPPorts = [ 80 443 ];
    allowedTCPPorts = [80 443 ];
  };
  networking.nat = {
    enable = true;
    externalInterface = "eth0";
    internalInterfaces = [ "wg0" ];
    externalIP = "<bastion IP>";
    extraCommands = ''
      iptables -t nat -A POSTROUTING -d 10.100.0.4 -p tcp -m tcp --dport 80 -j MASQUERADE;
      iptables -t nat -A POSTROUTING -d 10.100.0.4 -p tcp -m tcp --dport 443 -j MASQUERADE;
      iptables -t nat -A POSTROUTING -d 10.100.0.4 -p udp -m udp --dport 80 -j MASQUERADE;
      iptables -t nat -A POSTROUTING -d 10.100.0.4 -p udp -m udp --dport 443 -j MASQUERADE;
    '';
    forwardPorts = [
      {
        destination = "10.100.0.4:80";
        proto = "tcp";
        sourcePort = 80;
      }
      {
        destination = "10.100.0.4:443";
        proto = "tcp";
        sourcePort = 443;
      }
      {
        destination = "10.100.0.4:80";
        proto = "udp";
        sourcePort = 80;
      }
      {
        destination = "10.100.0.4:443";
        proto = "udp";
        sourcePort = 443;
      }
    ];
  };
}

Output from sudo iptables -L -t nat:

Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination
nixos-nat-pre  all  --  anywhere             anywhere

Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
nixos-nat-out  all  --  anywhere             anywhere

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination
MASQUERADE  tcp  --  anywhere             <home webserver hostname>  tcp dpt:http
MASQUERADE  tcp  --  anywhere             <home webserver hostname>  tcp dpt:https
MASQUERADE  udp  --  anywhere             <home webserver hostname>  udp dpt:http
MASQUERADE  udp  --  anywhere             <home webserver hostname> udp dpt:https
nixos-nat-post  all  --  anywhere             anywhere

Chain nixos-nat-out (1 references)
target     prot opt source               destination

Chain nixos-nat-post (1 references)
target     prot opt source               destination
SNAT       all  --  anywhere             anywhere             mark match 0x1 to:192.155.91.91

Chain nixos-nat-pre (1 references)
target     prot opt source               destination
MARK       all  --  anywhere             anywhere             MARK set 0x1
DNAT       tcp  --  anywhere             anywhere             tcp dpt:http to:10.100.0.4:80
DNAT       udp  --  anywhere             anywhere             udp dpt:http to:10.100.0.4:80
DNAT       tcp  --  anywhere             anywhere             tcp dpt:https to:10.100.0.4:443
DNAT       udp  --  anywhere             anywhere             udp dpt:https to:10.100.0.4:443

NFTables Nix Config:

{
  networking.nftables.enable = true;
  networking.firewall = {
    enable = true;
    allowedUDPPorts = [ 80 443 ];
    allowedTCPPorts = [80 443 ];
  };
  networking.nat = {
    enable = true;
    externalInterface = "eth0";
    internalInterfaces = [ "wg0" ];
    externalIP = "<bastion IP>";
    # It looks like nix handles building these rules for me?
    #extraCommands = ''
    #  iptables -t nat -A POSTROUTING -d 10.100.0.4 -p tcp -m tcp --dport 80 -j MASQUERADE;
    #  iptables -t nat -A POSTROUTING -d 10.100.0.4 -p tcp -m tcp --dport 443 -j MASQUERADE;
    #  iptables -t nat -A POSTROUTING -d 10.100.0.4 -p udp -m udp --dport 80 -j MASQUERADE;
    #  iptables -t nat -A POSTROUTING -d 10.100.0.4 -p udp -m udp --dport 443 -j MASQUERADE;
    '';
    forwardPorts = [
      {
        destination = "10.100.0.4:80";
        proto = "tcp";
        sourcePort = 80;
      }
      {
        destination = "10.100.0.4:443";
        proto = "tcp";
        sourcePort = 443;
      }
      {
        destination = "10.100.0.4:80";
        proto = "udp";
        sourcePort = 80;
      }
      {
        destination = "10.100.0.4:443";
        proto = "udp";
        sourcePort = 443;
      }
    ];
  };
}

Output from sudo nft list ruleset:

table inet nixos-fw {
    chain rpfilter {
        type filter hook prerouting priority mangle + 10; policy drop;
        meta nfproto ipv4 udp sport . udp dport { 68 . 67, 67 . 68 } accept comment "DHCPv4 client/server"
        meta ipsec exists accept comment "decrypted packets from an IPsec VPN"
        fib saddr . mark . iif oif exists accept
        jump rpfilter-allow
    }

    chain rpfilter-allow {
    }

    chain input {
        type filter hook input priority filter; policy drop;
        iifname "lo" accept comment "trusted interfaces"
        ct state vmap { invalid : drop, established : accept, related : accept, new : jump input-allow, untracked : jump input-allow }
        tcp flags syn / fin,syn,rst,ack log prefix "refused connection: " level info
    }

    chain input-allow {
        tcp dport { 22, 80, 443} accept
        udp dport { 80, 443, 51820 } accept
        iifname "wg0" tcp dport { 9154, 9166 } accept
        icmp type echo-request accept comment "allow ping"
        icmpv6 type != { nd-redirect, 139 } accept comment "Accept all ICMPv6 messages except redirects and node information queries (type 139).  See RFC 4890, section 4.4."
        ip6 daddr fe80::/64 udp dport 546 accept comment "DHCPv6 client"
    }
}
table ip nixos-nat {
    chain pre {
        type nat hook prerouting priority dstnat; policy accept;
        iifname "eth0" meta l4proto { tcp, udp } dnat ip to meta l4proto . th dport map { tcp . 80 : 10.100.0.4 . 80, udp . 80 : 10.100.0.4 . 80, tcp . 443 : 10.100.0.4 . 443, udp . 443 : 10.100.0.4 . 443 } comment "port forward"
    }

    chain post {
        type nat hook postrouting priority srcnat; policy accept;
        iifname "wg0" oifname "eth0" masquerade comment "from internal interfaces"
        iifname != "eth0" ip daddr . meta l4proto . th dport { 10.100.0.4 . tcp . 80, 10.100.0.4 . udp . 80, 10.100.0.4 . tcp . 443, 10.100.0.4 . udp . 443 } masquerade comment "port forward loopback snat"
    }

    chain out {
        type nat hook output priority mangle; policy accept;
    }
}

Is nix’s expression of port forwarding not handling something?