I'm going to guess that it is your use of the egress group in your nat-to rule. If the carp(4) interface is not part of the egress group, then this rule will not apply. And, normally, carp(4) interfaces are part of the carp group.

It's just a guess, of course.

----

Edited to add: the egress group is assigned to interfaces that use the default route. So if this is the problem, you may have a routing issue via the carp(4) interface.

- match out on carp inet from !(carp:network) to any nat-to (carp:0)
- match out on carp0 inet from !(carp0:network) to any nat-to carp0

Same result, no go.

I also went really specific but the result was the same unfortunately:

Code:

match out on carp0 inet from carp1:network to any nat-to carp0

LE: AFAIR the rules are supposed to be written for the physical interface and not the carp if.
LE2:

Code:

Ruleset Tips
Filter the physical interface. As far as PF is concerned, network traffic comes from the physical interface, not the CARP virtual interface (i.e., carp0). So, write your rule sets accordingly. Don't forget that an interface name in a PF rule can be either the name of a physical interface or an address associated with that interface. For example, this rule could be correct:
pass in on fxp0 inet proto tcp from any to carp0 port 22
but replacing the fxp0 with carp0 would not work as you desire.

I'm still a bit confused as to how the configuration works with the backup.

Right now I have a pair of routers and CARP is working on the internal interfaces and I want to add CARP on the public interface.

I get that doing NAT to the CARP address works on the active/master router because it has that address. The passive/backup router won't get any traffic to route so the only thing going out would be internally generated. But if the backup router has a NAT to the CARP address, won't the return path be wrong?