Skip to content

6. NAT

Configure on the primary only (NAT syncs). Outbound source-NAT for corporate/venue egress, a fixed address for payments, 1:1 NAT for whole-IP services, and reflection handling for published services.

Settle <PUBLIC_BLOCK> first

A /29 gives ~6 usable addresses. The carve below (routing VIP + PAT + payments + a few 1:1) fits a /28 comfortably but is tight on a /29 — 1:1 NAT is what runs out first. If you're on a /29, prefer a reverse proxy (HAProxy) over 1:1 for published web services. Decide before building this page.

6.1 Outbound source-NAT (PAT)

Firewall ▸ NAT ▸ Outbound. Set mode to Hybrid (keeps auto rules, lets you add manual ones on top).

Two manual rules, in this order (order matters — first match wins):

# Source Destination Translation Notes
1 POS/payment nets payment-provider prefixes <PAYMENTS_ADDR> (single, fixed) Static-port on, never pooled
2 corporate + venue nets any <PAT_ADDR> general egress

Payments must never rotate off their whitelisted IP

Card processors whitelist a specific, stable source IP. Rule 1 sits above the general rule and pins payments to <PAYMENTS_ADDR>. Do not pool it. (This also dovetails with the Starlink payment-failover design.)

NAT egress uses the routed block, never a WAN /31

Translate to an address from <PUBLIC_BLOCK> (a CARP VIP), never a WAN /31 transit address — those aren't in your announced space (§5b). One PAT address ≈ 64k concurrent sessions, which covers the whole corporate + venue estate. A round-robin pool across several <PUBLIC_BLOCK> addresses (§5c) is a later upgrade if session scale ever demands it — not needed now.

6.2 1:1 NAT — servers needing a whole public IP

Firewall ▸ NAT ▸ One-to-One. For each DMZ/public host that needs a full public address:

External (CARP VIP on PUBVIP) Internal Notes
a <PUBLIC_BLOCK> address 10.128.40.x (DMZ) target the CARP VIP so it fails over

Keep this list minimal — every 1:1 is public attack surface, and on a /29 they're scarce. Prefer routed public IPs behind the firewall (not NAT) for the DMZ block where the design allocates real public addressing (§5b).

6.3 Published services & the hairpin problem

Most published services are reached by internal clients too, which creates the NAT-reflection hairpin. Handle it in this priority (§13):

  1. Split-horizon DNS (preferred). On the internal resolvers (Tier-1 10.128.32.3/.4), publish service.pubinvest.co.uk → 10.128.x (real internal target) while public DNS keeps the public IP. Internal clients never touch the public IP, client source IP is preserved, no reflection load. This is the default for every service whose clients use internal DNS — i.e. all corporate/venue clients. Configure the records in Services / Unbound.
  2. NAT reflection (fallback) for hardcoded-public-IP devices: Firewall ▸ Settings ▸ Advanced ▸ NAT:
    • Reflection for port forwards = Pure NAT
    • Automatic outbound NAT for reflection = on (the essential half — SNATs the reflected traffic so replies return through OPNsense)
    • Reflection for 1:1 = on (if used) Reflection loses client-IP visibility on those flows — which is exactly why split-DNS is preferred.

6.4 A forward into a venue LAN (the odd one)

If a published service targets a venue device (10.V.x) rather than the server network, the venue router's own zone firewall must also permit the inbound flow (CONFIG-GUIDE §3.2) — OPNsense is the server VLANs' gateway but a venue sits behind a second firewall across the metro. Keep these rare, single-host, single-port, and prefer relocating a genuinely internet-facing service to the DMZ (§13d).

Everything NATs to a CARP VIP, not a node IP

All outbound translations, 1:1 external addresses, and any forwards target CARP VIPs on PUBVIP so they follow the master; pfSync keeps existing sessions alive across failover (§13e).