Skip to main content
ariel-anieli
Visitor III
December 11, 2025
Solved

Is there a guide to make virtual IPs work on SSL VPN interfaces?

  • December 11, 2025
  • 13 replies
  • 1756 views
Hello,
 
We have two questions:
  1. Is there a guide to make virtual IPs work on SSL VPN interfaces?
  2. May hairpin NAT work using secondary IP interfaces?
We are trying to make hairpin NAT work on 3000F. Any help will be much appreciated.
 
We followed this KB on hairpin NAT (https://community.fortinet.com/t5/FortiGate/Technical-Tip-Configuring-Hairpin-NAT-VIP/ta-p/195448#M2442). Our use case is similar to Example 1.
 
Campus1 (VPN / LAN IPs) --- FW (WAN IPs) -- internalServer (VIP).
 
At the FortiGate side, the VPN tunnels land on an SSL interface (ssl.root); ssl.root is defined on a WAN interface (port35).
 
Two firewall policies are defined:
  1. a NAT ingress rule, from ssl.root to port35, allowing internal users (VPN admin pool) to reach the virtual IP (defined as destination address object) balancing on an internal server.
  2. an egress rule, from port35 to a DMZ, allowing the NAT'ed requests to reach the load balancer virtual IP.
This translates into these policies:
 
# show firewall policy 1  config firewall policy     edit 96         set uuid 40edc776-13ac-51f0-4ffe-76f817f3a8fb         set srcintf "ssl.root"         set dstintf "port35"         set action accept         set srcaddr "VPN admin pool"         set dstaddr "internal-vip"         set schedule "always"         set service "SVC_HTTP"         set logtraffic all         set nat enable         set ippool enable         set poolname "PRIMARY"     next end  # show firewall address internal-vip  config firewall address     edit "aichor-dataplane-vip"         set uuid b89f3c46-13ac-51f0-a148-d5b310b9adee         set associated-interface "port35"         set color 1         set subnet 5.23.18.38 255.255.255.255     next end

# show firewall policy 2  config firewall policy     edit 93         set uuid bde1c42c-9556-51f0-1920-baa5f4d44b9f         set srcintf "port35"         set dstintf "DMZ"         set action accept         set srcaddr "RFC1918" "WAN_primary_ip"         set dstaddr "external-vip-https"         set schedule "always"         set service "SVC_NGINX_EXT_HTTP_TCP_32443"         set inspection-mode proxy         set logtraffic all     next end

# show firewall vip external-vip-https  config firewall vip     edit "external-vip-https"         set uuid 5508607c-1f64-51f0-b372-bc95c2b4275d         set type server-load-balance         set server-type tcp         set extip 5.23.18.39         set extintf "any"         set monitor "nginx-internal-ingress-hc-tcp-32444" "ping"         set extport 443         config realservers             edit 1                 set ip 10.120.0.16                 set port 32444                 set max-connections 1000             next             edit 2                 set ip 10.120.0.151                 set port 32444                 set status standby                 set max-connections 1000             next             edit 3                 set ip 10.120.1.149                 set port 32444                 set status standby                 set max-connections 1000             next         end     next end
 
We were expecting policy ID 1 to match first, then policy ID 2. We observed policy ID 2 got directly matched.
 
# diagnose firewall iprope lookup 10.120.37.1 12345 5.23.18.39 443 tcp ssl.root  <src [10.120.37.1-12345] dst [85.233.198.42-443] proto tcp dev ssl.root> matches policy id: 0
# diagnose firewall iprope lookup 10.120.37.2 12355 5.23.18.39 443 tcp port35  <src [10.120.37.2-12355] dst [85.233.198.43-443] proto tcp dev port35> matches policy id: 2
 
To work around the mismatch, we added the internal IPs in srcaddr of policy ID 2 (the address object RFC1918), and see this message in diag debug flow
 
id=65308 trace_id=144 func=fw_pre_route_handler line=191 msg="VIP-10.120.0.123:32443, outdev-unknown"

 

A complete extract,

# diagnose debug flow trace start 5 # id=65308 trace_id=144 func=print_pkt_detail line=6005 msg="vd-root:0 received a packet(proto=6, 10.120.37.1:50042->5.23.18.39:443) tun_id=0.0.0.0 from ssl.root. flag [S], seq 856634200, ack 0, win 64240" id=65308 trace_id=144 func=init_ip_session_common line=6206 msg="allocate a new session-3dcd1101" id=65308 trace_id=144 func=__iprope_tree_check line=524 msg="gnum-100004, use int hash, slot=69, len=13" id=65308 trace_id=144 func=get_new_addr line=1280 msg="find SNAT: IP-5.23.18.36(from IPPOOL), port-50042" id=65308 trace_id=144 func=fw_pre_route_handler line=191 msg="VIP-10.120.0.123:32443, outdev-unknown" id=65308 trace_id=144 func=__ip_session_run_tuple line=3525 msg="DNAT 5.23.18.38:443->10.120.0.123:32443" id=65308 trace_id=144 func=__vf_ip_route_input_rcu line=1989 msg="find a route: flag=00000000 gw-0.0.0.0 via DATA" id=65308 trace_id=144 func=__iprope_tree_check line=524 msg="gnum-100004, use int hash, slot=36, len=9" id=65308 trace_id=144 func=fw_forward_handler line=1002 msg="Allowed by Policy-2:" id=65308 trace_id=144 func=ip_session_confirm_final line=3179 msg="npu_state=0x100, hook=4"

 

As mentioned above, the addresses used in the VIPs are secondary IPs of port35; we are also wondering if the problem might come from that.

 

Your feedback is much appreciated,

Ariel

Best answer by ariel-anieli

Hi @funkylicious,

 

Following a case with the Fortinet support, we could make the hairpin NAT work. We:

  • removed the VS address as secondary IP on the WAN interface (the problem came from that)
  • Disabled web mode on the SSL portal
  • Created a single policy from ssl.root to DMZ in proxy mode.
# show firewall policy 6  config firewall policy     edit 6         set uuid 2f8405aa-dce1-51f0-d5f3-7b76a72f83cc         set srcintf "ssl.root"         set dstintf "Z-DATA"         set action accept         set srcaddr "VPN admin pool"         set dstaddr "test-vip"         set schedule "always"         set service "ALL"         set inspection-mode proxy         set logtraffic all         set groups "SSLVPN_GRP"     next end  # show full-configuration | grep -f full-access config vpn ssl web portal     edit "full-access" <---         set tunnel-mode enable         set web-mode disable <----     next end  # diagnose debug flow trace start 5  # id=65308 trace_id=279 func=print_pkt_detail line=6005 msg="vd-root:0 received a packet(proto=6, 10.120.37.1:50750->5.23.18.42:443) tun_id=0.0.0.0 from ssl.root. flag [S], seq 3052092823, ack 0, win 64240" id=65308 trace_id=279 func=init_ip_session_common line=6206 msg="allocate a new session-47f3629a" id=65308 trace_id=279 func=fw_pre_route_handler line=191 msg="VIP-10.120.0.123:32443, outdev-unknown" id=65308 trace_id=279 func=__ip_session_run_tuple line=3525 msg="DNAT 5.23.18.42:443->10.120.0.123:32443" id=65308 trace_id=279 func=__vf_ip_route_input_rcu line=1989 msg="find a route: flag=00000000 gw-0.0.0.0 via DATA" id=65308 trace_id=279 func=__iprope_tree_check line=524 msg="gnum-100004, use int hash, slot=84, len=15" id=65308 trace_id=279 func=fw_forward_handler line=1002 msg="Allowed by Policy-6:" id=65308 trace_id=279 func=ip_session_confirm_final line=3179 msg="npu_state=0x100, hook=4" id=65308 trace_id=280 func=print_pkt_detail line=6005 msg="vd-root:0 received a packet(proto=6, 10.120.37.1:50750->5.23.18.42:443) tun_id=0.0.0.0 from ssl.root. flag [.], seq 3052092824, ack 702653330, win 502"

 

13 replies

funkylicious
SuperUser
SuperUser
December 11, 2025

hi,

from my experience, hairpin nat works great with VIPs but i dont think they work with VS ( virtual server / load-balance ) and the extintf i usually leave it to any so I can use the VIP as destination ( even for hairpin NAT ) on the real destination interface where the traffic needs to go, in your case DMZ/DATA.

 

also, i personally prefer not to assign more than 1 IP on the WAN, if I have/want to use SNAT ( oubound to internet ) for certain traffic to use a specific IP other the one on WAN i just create a ip pool for that IP.

 

what should be the benefit of policy 96 ? it just uses the WAN IP as a destination for service SVC_HTTP ( port 80? ) but it shouldnt really do anything, except if you want to access the GUI for mgmt if it listens on port 80 for the secondary ip .

 

for WAN to DMZ access you would need a policy like , port35 > DATA ( servers 10.120.* are there i assume ? ) , src all , dst external-vip-https, svc https ( no NAT )

for SSLVPN to DMZ i would do something similar, ssl.root > DATA , src vpn subnet + usergroup , dst VS ( external-vip-https ) , service https ( no NAT assuming there's a default route back to FGT )

"jack of all trades, master of none"
ariel-anieli
Visitor III
December 12, 2025

Hi @funkylicious,

 

Thanks for having answered, it's much appreciated.

 

From your answer I understood that you would:

  1. use `any` as extintf, so the VIP may be used as dstaddr.
  2. assign one IP to the WAN, and use an IP pool if SNAT is needed.
  3. define a policy from WAN to DMZ, with external-vip-https as dstaddr for all IPs  and no NAT (which should work, thank to Point 1).
  4. define a policy from SSLVPN to DMZ, with internal IPs as srcaddr and VS as dstaddr, without NAT (and assuming there is a default route back to FGT).

This implies that, for doing an hairpin NAT with internal IP ingressing from ssl.root (over WAN interface), it should be enough to set one policy and a VIP (with extintf as any).

 

Is that correct?

 

We will have a try on this, and let you know about our findings.

 

For your question on policy  1 (there is a typo; instead of IDs 96 and 93, it is 1 and 2), we followed the hairpin KB as said before. Our use case matching Example 1, we defined two policies.

ariel-anieli
Visitor III
December 12, 2025

Hi again, @funkylicious;

 

This follow from my previous response. We have tried Point 1 to 4; and there is no hairpin NAT happening between SSLVPN and DMZ.

 

We have defined policy ID 3,

# show firewall policy 3  config firewall policy     edit 3         set uuid c8bce004-d519-51f0-ecb5-41b49aee0b1a         set srcintf "ssl.root"         set dstintf "DMZ"         set action accept         set srcaddr "VPN admin pool"         set dstaddr "test-vip"         set schedule "always"         set service "SVC_HTTP"         set logtraffic all         set nat enable         set ippool enable         set poolname "PRIMARY"     next end  PA10-R104U34-F3501F-FW1 # show firewall vip test-vip config firewall vip     edit "eqx-test-vip"         set uuid 7234d330-d515-51f0-c42e-f2549a316cbc         set comment "For ID 3"         set type server-load-balance         set server-type tcp         set extip 5.23.18.42         set extintf "any"         set monitor "nginx-external-ingress-hc-tcp-32443"         set extport 443         config realservers             edit 1                 set ip 10.120.0.123                 set port 32443             next         end     next end  # diagnose firewall iprope lookup 10.120.37.1 12345 5.23.18.42 443 tcp ssl.root  <src [10.120.37.1-12345] dst [5.23.18.42-443] proto tcp dev ssl.root> matches policy id: 0

 

The IPs used in the VS comes are secondary IPs of port35. May hairpin NAT work with secondary IPs?

 

That is our second question. Thanks for your hints on the first question.

 

Below a debug flow and the interface configuration,

# diagnose debug flow filter         vf: any         proto: any         Host addr: any         host saddr: 10.120.37.1-10.120.37.1         host daddr: 5.23.18.42-5.23.18.42         port: any         sport: any         dport: any  # diagnose debug flow trace start 5   # id=65308 trace_id=198 func=print_pkt_detail line=6005 msg="vd-root:0 received a packet(proto=6, 10.120.37.1:36974->5.23.18.42:443) tun_id=0.0.0.0 from ssl.root. flag [S], seq 2996138729, ack 0, win 64240" id=65308 trace_id=198 func=init_ip_session_common line=6206 msg="allocate a new session-3f429e7f" id=65308 trace_id=198 func=__iprope_tree_check line=524 msg="gnum-100004, use int hash, slot=69, len=13" id=65308 trace_id=198 func=get_new_addr line=1280 msg="find SNAT: IP-5.23.18.36(from IPPOOL), port-36974" id=65308 trace_id=198 func=fw_pre_route_handler line=191 msg="VIP-10.120.0.123:32443, outdev-unknown" id=65308 trace_id=198 func=__ip_session_run_tuple line=3525 msg="DNAT 5.23.18.42:443->10.120.0.123:32443" id=65308 trace_id=198 func=__vf_ip_route_input_rcu line=1989 msg="find a route: flag=00000000 gw-0.0.0.0 via DATA" id=65308 trace_id=198 func=__iprope_tree_check line=524 msg="gnum-100004, use int hash, slot=36, len=9" id=65308 trace_id=198 func=fw_forward_handler line=839 msg="Denied by forward policy check (policy 0)"

 

# show sys interface port35  config system interface     edit "port35"         set vdom "root"         set ip 5.23.18.36 255.255.255.248         set allowaccess ping https ssh http         set type physical         set mediatype sr4         set alias "WAN"         set lldp-reception enable         set estimated-upstream-bandwidth 25000000         set estimated-downstream-bandwidth 25000000         set monitor-bandwidth enable         set role wan         set snmp-index 35         set secondary-IP enable         set forward-error-correction disable         set speed 100Gfull         config secondaryip             edit 1                 set ip 5.23.18.37 255.255.255.248                 set allowaccess ping             next             edit 2                 set ip 5.23.18.38 255.255.255.248                 set allowaccess ping             next             edit 3                 set ip 5.23.18.41 255.255.255.248                 set allowaccess ping             next             edit 4                 set ip 5.23.18.42 255.255.255.248                 set allowaccess ping             next             edit 5                 set ip 85.233.198.43 255.255.255.248                 set allowaccess ping             next         end     next end

 Have a good day,

Ariel

ariel-anieli
ariel-anieliAuthorAnswer
Visitor III
December 24, 2025

Hi @funkylicious,

 

Following a case with the Fortinet support, we could make the hairpin NAT work. We:

  • removed the VS address as secondary IP on the WAN interface (the problem came from that)
  • Disabled web mode on the SSL portal
  • Created a single policy from ssl.root to DMZ in proxy mode.
# show firewall policy 6  config firewall policy     edit 6         set uuid 2f8405aa-dce1-51f0-d5f3-7b76a72f83cc         set srcintf "ssl.root"         set dstintf "Z-DATA"         set action accept         set srcaddr "VPN admin pool"         set dstaddr "test-vip"         set schedule "always"         set service "ALL"         set inspection-mode proxy         set logtraffic all         set groups "SSLVPN_GRP"     next end  # show full-configuration | grep -f full-access config vpn ssl web portal     edit "full-access" <---         set tunnel-mode enable         set web-mode disable <----     next end  # diagnose debug flow trace start 5  # id=65308 trace_id=279 func=print_pkt_detail line=6005 msg="vd-root:0 received a packet(proto=6, 10.120.37.1:50750->5.23.18.42:443) tun_id=0.0.0.0 from ssl.root. flag [S], seq 3052092823, ack 0, win 64240" id=65308 trace_id=279 func=init_ip_session_common line=6206 msg="allocate a new session-47f3629a" id=65308 trace_id=279 func=fw_pre_route_handler line=191 msg="VIP-10.120.0.123:32443, outdev-unknown" id=65308 trace_id=279 func=__ip_session_run_tuple line=3525 msg="DNAT 5.23.18.42:443->10.120.0.123:32443" id=65308 trace_id=279 func=__vf_ip_route_input_rcu line=1989 msg="find a route: flag=00000000 gw-0.0.0.0 via DATA" id=65308 trace_id=279 func=__iprope_tree_check line=524 msg="gnum-100004, use int hash, slot=84, len=15" id=65308 trace_id=279 func=fw_forward_handler line=1002 msg="Allowed by Policy-6:" id=65308 trace_id=279 func=ip_session_confirm_final line=3179 msg="npu_state=0x100, hook=4" id=65308 trace_id=280 func=print_pkt_detail line=6005 msg="vd-root:0 received a packet(proto=6, 10.120.37.1:50750->5.23.18.42:443) tun_id=0.0.0.0 from ssl.root. flag [.], seq 3052092824, ack 702653330, win 502"

 

mohamedali94
Staff
Staff
December 24, 2025

Hello Ariel,

 

Thanks for updating the forum and for the detailed discussion.

 

Just a small clarification from our side: in the final working setup, NAT hairpin is not being used. The solution relies on DNAT only (via the VIP) together with a firewall policy from the SSL VPN interface to the DMZ/internal interface. No SNAT is involved in this case.

 

Thanks again for the insights and collaboration.

 

Wishing you a great holiday!

 

BR,

Mohamed Ali

Thought Leadership. Security Summit. Thursday, November 12th, PGA National Resort, Palm Beach Gardens, FL.
Thought Leadership. Security Summit. Thursday, October 8th. Disney's Grand Californian Hotel & SPA, Anaheim, CA.
Security Operations Summit: Modernize SecOps. Operate AI-Native.AMER: November 4 | 9:00 AM PST. India and SAARC: November 5 | 10:00 AM IST. EMEA: November 5 | 10:30 AM CET. APAC: November 5 | 11:00 AM SGT