OpenZiti as replacement for client VPN

First, I want to say thanks for this project and all the amazing resources found here.
I spent the last couple of days reading docs, watching videos, and trying to lab my solution without success.

My current setup relies on a VPN server inside the office. Users connect to that VPN server using a VPN client and then can reach internal and external resources that can only be accessed from inside the office.

I want to implement OpenZiti to replace this and have a more granular control of what each device and user can access.
Here is a diagram of how the solution should look (in blue you can see the data flow)


  • Is this possible?

  • How does the configuration should look like?

  • How does the IP addressing and routing work?

If I think of it as VPN, the client will get an IP, and send traffic to the Ziti router or Ziti Tunneler running in the office, then this device will route traffic to the internet where the firewall will NAT that traffic.
On the Firewall, I have a route pointing to the Ziti Tunneler for the subnet of the Ziti Client.

Unfortunately running OpenZiti in the Webserver is not an option.


Howdy @Foles_90, welcome to the community. We're thankful for the kind words about the project and I'm sorry you didn't have any luck so far but let's see if we can help.

Your diagram is exactly correct and makes perfect sense to me. Technically the traffic flow will flow like this:

  • client to public edge router (top left)
  • public edge router to 'private tunneler' (edge router or ziti-edge-tunnel, Ziti Desktop Edge for Windows, etc)
  • 'private tunneler' to web server which uses the outbound IP from "Office" in its web ACL, thus allowing the traffic.

If I think of it as VPN, the client will get an IP, and send traffic to the Ziti router or Ziti Tunneler running in the office, then this device will route traffic to the internet where the firewall will NAT that traffic.

On the Firewall, I have a route pointing to the Ziti Tunneler for the subnet of the Ziti Client.

This is "close enough". Your client doesn't get an IP really, more the client knows how to intercept an IP you assign or better yet a DNS entry you assign. When you're on 'the client' and you navigate to something like "my.totally.private.server", the OpenZiti tunneler knows how to capture this request and put it onto the OpenZiti overlay. The overlay then routes the traffic to the "final destination" (in your example, that tunneler in "the office") where in your example it would be offloaded from the overlay network and sent towards the actual server.

How can we help?

So yes, this is absolutely possible. Now that you know it's doable, what sort of issue are you having? Where did you get stuck? How can we help out?

Thanks for the quick reply!

The webserver URL can be resolved by a public DNS, so no need to use a specific DNS entry.
My idea was to intercept traffic going to that URL.

Here is where I have my main question. What is the source IP that the packet will have once it leaves the tunneler in “the office”.
Will the tunneler in the office do NAT? If not I need some sort of route back, but to what IP?

With OpenZiti -- you can totally do that. That is actually useful because the certificate delivered from that server is probably able to be validated by clients. That will allow https to work properly.

The traffic originates from "the office". So it's the exit IP of "the office" network. That's how the web ACL will allow that traffic to the target server.

This will all 'just work'. The OpenZiti client deployed in "the office" will handle this, you won't need a route back to any IP.


So I understood correctly, the client with whatever IP it has (does not matter to Ziti) will send traffic to the Ziti tunneler in the office. The tunneler will decapsulate that packet, inspect it and when it leaves to the webserver it will do NAT and use its own IP ( as the source IP. Then the Firewall will do NAT again using the outside address

You got it. Locally on that tunneler client, with OpenZiti running, will intercept the request for “your.private.server” and it’ll get assigned to a 100.x.x.x IP (for example, That IP is then captured by the OpenZiti client. So your browser ends up sending traffic from to (via the DNS lookup to “your.private.server”).

That’s when the OpenZiti client maps the packets landing at “” to “your.private.server” and wraps the packets in OpenZiti routing protocol and puts those OpenZiti packets onto the overlay.

The overlay knows that the offload point for “your.private.server” is “the office” and routes the traffic to “the office”. Once there the tunneler in “the office” knows that it’s traffic for “your.private.server” and it knows that it needs to make a TCP connection to the configured offload target (the actual remote server). That traffic then exits “the office” from, allowing the traffic to land at the private server and make it through the IP-based ACL.

Since the tunneler initiated the outbound connection, there’s no NAT to worry about (the underlay handles all that as it would normally).

Hope that all makes sense! :slight_smile:

Simply amazing! Thank you very much.

I am testing now everything in my lab and will come back later either with a success story or with more questions :sweat_smile:

1 Like

Could not make it work :frowning:

This what I am doing:

Create 2 identities

ziti edge create identity user androidphone -o androidphone.jwt -a client
ziti edge create identity device linux -a server -o linux.jwt

create the configs:

ziti edge create config host.v1 '{"protocol":"tcp", "address":"","port":'9000'}'
ziti edge create config webserver.intercept intercept.v1 '{"protocols":["tcp"],"addresses":[""], "portRanges":[{"low":'9000', "high":'9000'}]}'

create service:

ziti edge create service webservice.service --configs ",webservice.intercept"

create service polices:

ziti edge create service-policy webservice.bind Bind --service-roles '@webservice.service' --identity-roles '#server'

ziti edge create service-policy webservice.dial Dial --service-roles '@webservice.service' --identity-roles '#client'

Then from my phone I try to go to but it is not working

I was using chrome as my browser (Android device), changed to firefox and it worked!
Any ideas why chrome may be failing?

First of all - nice job! All your commands were spot on and you got it working.

As for chrome/firefox, if I had to guess, I’d guess the polling interval for the client to discover new services was slower than you. I think it’s set to a one minute poll before the services are available to try to reduce bandwidth and battery use. Maybe when you moved to firefox, the one minute elapsed? If you try it again, does chrome still not work properly?

I use brave which is chrome-based and I’ve not seen that particular problem. If chrome still gives you problems, I’ll try ping another dev and see if they have any ideas. It might just work now though?