When you use the web-based config.gateway.json configurator, there are options to create DNAT, SNAT, Firewall WAN_IN, and Hairpin DNAT rules.

After selecting all those options and setting the rules you want to map an external IP to an internal IP via NAT, you might get a configuration similar to the following:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
{
  "interfaces": {
    "ethernet": {
      "eth0": {
        "address": [
          "192.168.1.10/24",
          "192.168.2.20/24"
        ]
      }
    }
  },
  "service": {
    "nat": {
      "rule": {
        "1000": {
          "description": "DNAT for external to internal map",
          "destination": {
            "address": "192.168.1.10",
            "port": "1-65535"
          },
          "inbound-interface": "eth0",
          "inside-address": {
            "address": "192.168.99.10",
            "port": "1-65535"
          },
          "protocol": "all",
          "type": "destination"
        },
        "1500": {
          "description": "Hairpin for external to internal map",
          "destination": {
            "address": "192.168.1.10",
            "port": "1-65535"
          },
          "inbound-interface": "eth1",
          "inside-address": {
            "address": "192.168.99.10",
            "port": "1-65535"
          },
          "protocol": "all",
          "type": "destination"
        },
        "5000": {
          "description": "SNAT for external to internal map",
          "outbound-interface": "eth0",
          "outside-address": {
            "address": "192.168.1.10"
          },
          "protocol": "all",
          "source": {
            "address": "192.168.99.10"
          },
          "type": "source"
        }
      }
    }
  },
  "firewall": {
    "name": {
      "WAN_IN": {
        "rule": {
          "2000": {
            "action": "accept",
            "description": "Firewall for external to internal map",
            "destination": {
              "address": "192.168.99.10",
              "port": "1-65535"
            },
            "protocol": "all"
          },
          "2010": {
            "action": "accept",
            "description": "Hairpin FW for external to internal map",
            "destination": {
              "address": "192.168.99.10",
              "port": "1-65535"
            },
            "protocol": "all",
            "source": {
              "address": "192.168.1.10"
            }
          }
        }
      }
    }
  },
  "port-forward": {
    "hairpin-nat": "enable",
    "lan-interface": [
      "eth1"
    ],
    "wan-interface": "eth0",
    "rule": {
      "2000": {
        "description": "Hairpin Portfoward for external to internal map",
        "forward-to": {
          "address": "192.168.99.10",
          "port": "1-65535"
        },
        "protocol": "all",
        "original-port": "1-65535"
      }
    }
  }
}

In the configuration above, the external address 192.168.1.10 is mapped to the internal address 192.168.99.10 via NAT.

If you have the internal machine 192.168.99.10 on a VLAN (e.g. eth1.2), then you will need to modify the service.nat.rule and port-forward sections of the above configuration.

Specifically, service.nat.rule section (“rule”, nested under “nat”, nested under “service”), find the “Hairpin” rules. By default, the web configurator starts numbering those rules around 1500. Add all VLAN interfaces that you want the Hairpin NAT rules to apply to, to the inbound-interface line (you will need to make the value an array).

Sample modified service.nat.rule “Hairpin” rule:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
  "service": {
    "nat": {
      "rule": {
        "1500": {
          "description": "Hairpin for external to internal map",
          "destination": {
            "address": "192.168.1.10",
            "port": "1-65535"
          },
          "inbound-interface": ["eth1", "eth1.2"],
          "inside-address": {
            "address": "192.168.99.10",
            "port": "1-65535"
          },
          "protocol": "all",
          "type": "destination"
        }
      }
    }
  }

Next, in the lan-interface array (under the port-forward section), add the VLAN interface where 192.168.99.10 is located (at least). You can also add other VLAN interfaces here that you want the Hairpin NAT rule to apply to.

1
2
3
4
5
6
7
8
  "port-forward": {
    "hairpin-nat": "enable",
    "lan-interface": [
      "eth1",
      "eth1.2"
    ],
    ...
  }

If you have multiple rules (e.g. multiple public IPs mapped to different internal host IPs), where the hosts are all on different VLANs, add all the VLAN interfaces to the lan-interface array.

What the lan-interface array specifies, I think, is what interfaces you want the hairpin NAT rules to apply to. This means if you want two hosts, e.g. 192.168.99.10 and 192.168.99.20 to be able to communicate with each other using their external IPs, you will need to have the Hairpin rules applied to both the VLAN interfaces where those hosts are connected.

Furthermore, you will need to save the config.gateway.json file in the following location on your Unifi Controller (e.g. Cloud Key):

/srv/unifi/data/sites/default/config.gateway.json

NOTE: default could be different, depending on what the internal name of your Unifi Site is.

You can find the internal sites by running: ls /srv/unifi/data/sites/ on your Unifi Controller.

After saving config.gateway.json in the right location, navigate to the Unifi UI and Force Provision the USG.

Force Provisioning the USG will take the config.gateway.json present on the Controller and apply it to the USG.

Force Provision steps:

  1. Open “Network” Unifi app
  2. “Devices” view
  3. Click on the USG
  4. In the USG pane that shows up, click “Config” (gear icon)
  5. Open “Manage Device” drawer/section
  6. Press “Provision” button under the “Force Provision” section.

Your USG should now be forwarding packets from public IPs to local/VLAN IPs properly.