Alright folks, been down in the home lab for the last few weekends and I think I finally have something worth sharing. Built out a full zero-trust network access (ZTNA) config on my FortiGate 60F, replacing the old "VPN-in-then-roam-anywhere" model. The goal was simple: no one gets to see my home network just because they have a VPN password. They only get to talk to the specific app they need.
The core of it is using FortiGate's application-level tunnels and tags. I set up separate access proxies for my Home Assistant, NAS management interface, and a little NodeRed dashboard. Each one has its own explicit policy, user group (pulled from FortiAuthenticator, but you could use local), and health check. The beautiful part? The clients never hit my internal RFC1918 space. Here's a snippet of the meat of one proxy policy:
```
config firewall access-proxy
edit "ZTNA-HomeAssistant"
set vip "ztna-ha.mydomain.com"
set client-cert enable
set auth-portal enable
set auth-virtual-host enable
config api-gateway
edit 1
set service "HTTP"
set url-map "/"
config realservers
edit 1
set address 192.168.1.50
set port 8123
next
end
next
end
next
end
```
I'm using client certificates *and* user auth. The FortiClient EMS is managing the posture checks (OS, firewall on, etc.) before it even lets the tunnel establish. Had a real "back to the future" moment debugging thisβfelt like the time I accidentally blackholed the entire company VPN with a mis-typed BGP community back in '08. Good times.
I'd love a second pair of eyes, especially on the logging side. I'm catching the denies, but making the flows more intuitive in the SIEM is taking some work. Anyone else gone down this rabbit hole? Would you segment further, maybe by putting the apps in a totally separate DMZ? Or is the proxy isolation enough?
-- Dad
it worked on my machine
That's a solid implementation of the core ZTNA principle, moving from network-centric to application-centric access. Your point about clients never hitting RFC1918 space is key, it fundamentally shrinks the attack surface compared to a traditional VPN tunnel.
I'm curious about your operational experience so far, specifically around session persistence and the user agent's behavior. With application-level proxies, if a user's connection to, say, Home Assistant drops, does the FortiClient automatically try to re-establish just that specific application tunnel, or does it require manual re-authentication? In a support context, that's a major usability factor we weigh against the security gain.
Also, have you tested the health check failover under load? I've seen scenarios where an app is marked 'unhealthy' but the client cache doesn't update immediately, leading to a brief access denial that feels like an outage to the end-user.
Support is a product, not a department.
Switching to application-specific access is the right move, but I'm curious about your operational overhead for managing these discrete proxies. In a support environment, each of those access proxies becomes its own service endpoint we'd need to monitor and maintain. How are you handling certificate lifecycle for those client-cert enabled proxies? Let's say you have ten team members needing access to those three apps, that's thirty individual certificate relationships to track versus one VPN credential set.
The FortiAuthenticator integration is smart for user groups, but have you hit any latency or single point of failure concerns with the external auth source? If that goes down during an after-hours support scenario, you're locked out of all your management interfaces simultaneously. A traditional VPN with a local fallback account might still have a place in the architecture for break-glass access.
Support is a product, not a department.
The certificate overhead is real. You either script the lifecycle with your CA's API or it becomes unmanageable. I use a short-lived cert model, automated. Thirty relationships is trivial at ten users, but it's a linear scaling problem.
>single point of failure concerns with the external auth source
You need a local auth fallback. Period. A dedicated break-glass SSO instance, or local accounts on the FortiGate for a specific, heavily monitored user group. Relying solely on external auth for all access is an availability risk, not just a security choice.
Trust, but verify
You're right to focus on the operational side. In my experience, that's where ZTNA either sinks or swims.
Regarding your question on session persistence, the FortiClient behavior can be tuned per application tunnel policy. You can set it to attempt automatic reconnection without user interaction for a set number of tries, which usually handles brief drops gracefully. But if the session token expires, it will require a manual re-auth. That's a key distinction from a traditional VPN where the tunnel itself might stay up.
On the health check point, that latency in cache updates is real. I've mitigated it by setting a more aggressive TTL for the health check status and using a combination of passive and active monitoring. The 'unhealthy' flag should propagate faster than the standard default, but it's a trade-off with system load.
βAnita
You're spot on about needing that local fallback. It's easy to get tunnel vision on the zero-trust "never trust" principle and accidentally design a system that's too brittle for real-world ops.
The break-glass group is essential, but the monitoring piece you mentioned is critical. If you have a local admin group for emergencies, you need those login attempts to scream at you. Silent local access defeats the whole purpose.
How do you handle alerting for that group? We route those auth logs to a separate, high-severity channel.
Raise the signal, lower the noise.
That's a clean setup, and isolating access to specific apps is exactly the right architecture. Your policy snippet cuts off, but I'm particularly interested in the realservers section and how you've configured the health check. The performance and failover behavior of those health checks is a measurable operational detail.
Have you run any latency benchmarking between the old VPN model and the new ZTNA proxies for the same application? With HTTP-level proxying, you're adding an extra termination and inspection point. I'd be curious to see the delta in response times, especially for the NodeRed dashboard which might have more frequent, smaller requests. The overhead might be negligible, but it's good to quantify.
-- bb42
Nice work shifting to that app-centric model, it's a game changer for home lab security.
That snippet cuts off right at the realservers section, which is where I'd be most interested. Are you pointing it at a single internal IP, or do you have a load balancer in front of the services? I ask because that config block is where you bake in your failover logic. For my HA setup, I've got the health check set to ping the service's `/api/` endpoint, not just the server IP. It's a bit more forgiving if the app hiccups but the VM is still up.
Also, for that NodeRed dashboard, watch out for WebSocket connections through the proxy. Sometimes the default gateway settings need a tweak to keep those long-lived connections happy. Ran into that myself last month! 😅
Pipeline Pilot
That's a fantastic project. I've gone down a similar path migrating my own setup away from a flat VPN, and the moment you see the internal RFC1918 space drop from the client's routing table is incredibly satisfying.
You've really highlighted the core strength with "no one gets to see my home network." That principle translates directly to business scenarios, too - it's exactly what you want for contractors or third-party vendors.
I'm very curious to hear about your NodeRed proxy config. That was my trickiest one, specifically getting the long-polling HTTP requests and WebSocket connections to behave. I ended up adjusting the TCP buffer settings in the realservers block to keep those dashboards snappy. Did you run into anything similar?
Data is sacred.
Totally agree about the contractor use case, it's a perfect fit. On the NodeRed config, WebSockets were definitely the hurdle for me too. I set a custom `http-reuse` policy on the proxy and increased the idle timeout to keep those connections alive. Made a huge difference for the dashboard's responsiveness.
Have you found any issues with service restarts? Sometimes a NodeRed restart can make the client side hang until the app tunnel fully renegotiates, which is a minor usability quirk.
Raise the signal, lower the noise.