VPN was designed in 1996 for a world where corporate networks had a defined perimeter. Zero Trust Network Access (ZTNA) was designed for a world where the perimeter doesn’t exist — where users work from anywhere, applications live in multiple clouds, and “inside the network” is no longer a meaningful security concept.

This guide explains the architectural difference, the identity verification model behind ZTNA, and how to migrate from legacy VPN to a modern ZTNA deployment.

The Core Problem: VPN’s Trust Model

Traditional VPN authenticates once and grants network-level access. A user connects, and the client receives a private IP address on your corporate subnet. From that point, the VPN treats all traffic as trusted — the user can reach file servers, databases, internal APIs, and management interfaces beyond what their job requires.

This “authenticate once, trust everything” model is why ransomware spreads so effectively through corporate networks. The attacker compromises one endpoint, establishes a VPN session, and uses that implicit trust to move laterally.

ZTNA inverts this model: never trust, always verify, at the application layer.

ZTNA Architecture

How ZTNA Works

UserDevice[DevicePostureC[[[AhIDCcPedeocrcevneiknitsv]tcesaZ(iexZ(tTPttGTieNoyTrNnZAlrEaAATiVuvnypNCcesatCopAoyrtleoulniudnriCtEfVa/nclrnietDenaiogcrieceteliaionttinntfniowotPeii]eronl)ocdranakntp)evieioran]AIpdpP]

The ZTNA connector runs inside your network and maintains an outbound-only connection to the ZTNA cloud control plane. Your applications never need inbound firewall rules opened — the connector initiates the connection, not the user’s device.

Identity Verification Model

ZTNA integrates directly with your Identity Provider (IdP) using OIDC or SAML. Every application access request triggers a fresh authorization check:

SignalVPNZTNA
User authenticationOnce at tunnel startPer-app connection + CAE
Device trustIP address onlyMDM certificate + compliance check
Application scopeEntire subnetSpecific app:port
Context evaluationNoneTime, location, risk score
Session revocationManual disconnectReal-time (CAE)

Keycloak as ZTNA Identity Broker

Keycloak can serve as the OIDC identity broker for ZTNA solutions:

# Keycloak realm configuration for ZTNA
# Create a dedicated client for the ZTNA provider

POST /admin/realms/corporate/clients
{
  "clientId": "ztna-provider",
  "protocol": "openid-connect",
  "publicClient": false,
  "redirectUris": ["https://your-ztna-provider.com/callback"],
  "attributes": {
    "access.token.lifespan": "300",  // 5 min - ZTNA re-verifies frequently
    "use.refresh.tokens": "true",
    "pkce.code.challenge.method": "S256"
  }
}

Configure device posture claims using Keycloak’s Protocol Mapper to pass device compliance status in the access token:

{
  "name": "device-compliance",
  "protocol": "openid-connect",
  "protocolMapper": "oidc-hardcoded-claim-mapper",
  "config": {
    "claim.name": "device_compliant",
    "claim.value": "true",
    "id.token.claim": "false",
    "access.token.claim": "true"
  }
}

The ZTNA policy engine reads the device_compliant claim and blocks access from non-compliant devices at the network layer, before the application ever receives the request.

ZTNA vs VPN: Complete Comparison

Security Model

AspectVPNZTNA
Access scopeFull network subnetSingle application
Lateral movementPossible — user can reach any accessible IPImpossible — no network adjacency
Credential theft impactFull network compromiseOne app compromised
Session re-evaluationNever (until disconnect)Continuous (CAE)
Zero-day exploitsVPN appliance is a public attack surfaceControl plane is SaaS, connector is outbound-only
Unmanaged devicesUsually blocked by IP/certClientless ZTNA enables browser-based access

Performance Characteristics

VPN routes all traffic through a central gateway, causing backhauling — a user in Austin accessing Salesforce has traffic routed through the corporate data center in Dallas, then back to Salesforce’s cloud, doubling latency.

ZTNA uses split tunneling by default:

  • SaaS apps: Direct to cloud, no backhauling
  • Private apps: Routed via nearest ZTNA PoP → private connector
  • Internet traffic: Direct, unrouted through ZTNA

The result: most users see improved performance for SaaS applications immediately after switching.

Cost Comparison

ItemVPNZTNA
HardwareVPN concentrators ($5K-$50K)None (SaaS)
LicensingPer device or concurrent usersPer user/month
BandwidthAll traffic through corporate WANOnly private app traffic
OperationalVPN appliance patching, firmware updatesManaged by vendor
Incident responseBroad blast radius investigationsApp-scoped forensics

For enterprises with 500+ remote users, ZTNA typically reduces total cost by 30-60% when factoring in appliance hardware, bandwidth, and operational overhead.

Provider Comparison

Cloudflare Access

Cloudflare Access is the ZTNA component of Cloudflare Zero Trust. Strong for:

  • Web application access (HTTP/HTTPS)
  • SSH/RDP via browser-rendered clients
  • Tight integration with Cloudflare CDN (performance benefit)
  • Free tier for up to 50 users
# Cloudflare Access — configure application with Keycloak IdP
# Via Cloudflare dashboard or Terraform:

resource "cloudflare_access_application" "private_app" {
  zone_id          = var.zone_id
  name             = "Internal Dashboard"
  domain           = "dashboard.corp.example.com"
  session_duration = "24h"
}

resource "cloudflare_access_policy" "corp_users" {
  application_id = cloudflare_access_application.private_app.id
  name           = "Corp Users Only"
  decision       = "allow"
  include {
    email_domain = ["corp.example.com"]
  }
  require {
    # Device posture via Cloudflare WARP client
    device_posture = [cloudflare_device_posture_rule.compliant.id]
  }
}

Zscaler Private Access (ZPA)

ZPA leads in enterprise deployments. Strong for:

  • Large-scale deployments (100K+ users)
  • Complex segmentation policies
  • Integration with Okta and Entra ID
  • App Connectors behind firewall with no inbound rules

Palo Alto Prisma Access

Prisma ZTNA + SASE. Strong for:

  • Customers with existing Palo Alto Next-Gen Firewall
  • Unified security policy across ZTNA and web filtering
  • Advanced threat prevention inline with access

OAuth Token Flow in ZTNA

ZTNA relies on standard OAuth 2.0 flows for application access tokens. Understanding this flow helps diagnose access failures:

12345678........UZIZAICAsTdTcfoceNPNcncrAAeaneaspesrcucspcseltortqihntoitueetovooennrkenksttoedetiln:pnsicrnapeZomattlvTxoceeaaNincrsnlAeieceudtseuacospsetotrtexenhetsrcdtrdohroaaouvpnglgirrrgahaieeeipvdtsnlcCaiusaootrrctnnneenoentcsdZeiateTscnpsaNitupufAgoottonruohrpasooltIralsoAdicicPzcccacaeyope(tsnpsKisnlseoeiyntccEcotavlckotaooeriladnouketna/(otOPikKcotCrnaEe)a(rtCeeAqEue)iprheedm)eraltunnel

If you see invalid_grant errors in ZTNA access logs, the root cause is usually an expired or replayed authorization code — see OAuth invalid_grant Error: Complete Troubleshooting Guide.

For the token verification mechanics ZTNA uses, see How to Decode JWT Tokens in JavaScript and the JWT Decoder tool.

Implementing ZTNA with Entra ID

Microsoft Entra Private Access is Microsoft’s ZTNA solution, deeply integrated with Entra ID Conditional Access:

# Entra Private Access — configure Quick Access connector
# Install the Private Network Connector on a domain-joined server

# 1. Register connector via Entra admin center
# 2. Configure Quick Access application
# 3. Set Conditional Access policy requiring MFA + compliant device

$policy = @{
    DisplayName = "ZTNA - Require Compliant Device"
    State = "enabled"
    Conditions = @{
        Users = @{ IncludeGroups = @("All Corp Users") }
        Applications = @{ IncludeApplications = @("$EntraPrivateAccessAppId") }
    }
    GrantControls = @{
        Operator = "AND"
        BuiltInControls = @("mfa", "compliantDevice")
    }
}
New-MgIdentityConditionalAccessPolicy -BodyParameter $policy

For broader Entra ID configuration patterns for Zero Trust, see Zero Trust Architecture Implementation: A Practical Guide for IAM Engineers.

Migration Strategy: VPN → ZTNA

Phase 1: Application Discovery (Week 1-2)

Before migrating, inventory which applications are accessed via VPN and categorize by protocol:

# Analyze VPN firewall logs to find top private app destinations
# (adjust for your firewall log format)
awk '{print $7}' /var/log/vpn-access.log | \
  sort | uniq -c | sort -rn | head -20

Applications typically fall into three categories:

  • Web/HTTPS: Immediate ZTNA candidates (highest ROI)
  • SSH/RDP: ZTNA with clientless browser rendering
  • Custom protocols (UDP, raw TCP): Requires ZTNA TCP proxy or keep on VPN

Phase 2: Pilot Deployment (Week 3-6)

Start with your top 5 web applications and 20-50 pilot users:

  1. Deploy ZTNA connector in your DMZ or private subnet
  2. Onboard pilot applications to ZTNA portal
  3. Configure IdP integration with Keycloak/Okta/Entra ID
  4. Enable device posture checking via MDM certificates
  5. Keep VPN active as fallback

Measure: user support tickets, latency (compare P95 before/after), authentication success rate.

Phase 3: Full Rollout (Month 2-4)

Onboard remaining web/SSH/RDP applications. For mTLS-secured services (internal microservices), ZTNA and mTLS complement each other — the ZTNA layer handles user identity while mTLS handles service-to-service authentication. For implementation details, see mTLS Certificate Authentication for Microservices in Kubernetes.

Phase 4: VPN Decommission

Retire client VPN for remote users. Keep site-to-site VPN/SD-WAN for office-to-office connectivity where network-level access is required.

Security Hardening

Block Lateral Movement at the Source

With ZTNA, lateral movement is structurally impossible — users never get a network-level IP address inside your private subnet. However, if an application itself is compromised, you still need application-layer controls.

For APIs behind ZTNA, implement OAuth scopes to enforce least privilege:

# FastAPI + Keycloak OIDC token validation behind ZTNA
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPBearer

security = HTTPBearer()

async def verify_ztna_token(credentials = Depends(security)):
    token = credentials.credentials
    # ZTNA already verified user identity, but validate token claims
    payload = decode_and_validate_jwt(token, KEYCLOAK_PUBLIC_KEY)
    
    # Enforce application-level scope
    if "app:admin" not in payload.get("scope", "").split():
        raise HTTPException(status_code=403, detail="Insufficient scope")
    
    # Check device compliance claim (set by Keycloak mapper)
    if not payload.get("device_compliant", False):
        raise HTTPException(status_code=403, detail="Device not compliant")
    
    return payload

For Non-Human Identity (NHI) access behind ZTNA — service accounts, CI/CD pipelines, AI agents — use short-lived credentials rather than static API keys. See NHI Secrets Sprawl: Fixing the Non-Human Identity Credential Crisis.

FAQ

Does ZTNA work for IoT and OT devices?

Standard ZTNA clients require a software agent, which IoT/OT devices cannot run. For these environments, consider:

  • Network-based ZTNA (policy enforced at the switch/firewall level via device certificates)
  • Microsegmentation instead of ZTNA for device-to-device communication
  • ZTNA only for human access to OT management interfaces

What happens if the ZTNA control plane goes down?

Most enterprise ZTNA providers offer 99.99% uptime SLAs. If the control plane is unreachable, connectors fail-closed (no access) rather than fail-open (all access). This is the opposite of VPN, where a failed concentrator causes a complete outage. Design for this with VPN fallback for critical systems during migration.

How does ZTNA handle service-to-service calls?

ZTNA handles human-to-application access. For service-to-service calls between microservices, use mTLS with workload identity (SPIFFE/SPIRE) or the OAuth 2.0 Client Credentials flow. ZTNA and workload identity are complementary layers in a full Zero Trust architecture.