1. Why this matters — policy is where Zero Trust actually happens
After Module 10 you have App Connectors humming — outbound TLS to ZPA Cloud, no inbound holes punched, the architectural promise of ZPA delivered. But right now, that humming tunnel is doing nothing useful. It's just capable of carrying traffic. Whether a specific user gets a specific app on a specific device at a specific time is decided entirely in policy.
This is the part where most VPN-to-ZPA migrations quietly fail. A team racks up the connectors, lights up the tunnel, sees "ZPA works", and then back-ports their old IPsec mindset on top: one big app segment for the whole DC, one Access Policy that says "if employee then allow". Six months later, an L1 contractor with a stolen laptop is reading the payroll database from a Goa beach Wi-Fi and security is wondering what changed. Nothing changed. The team built a fancier VPN, not Zero Trust.
Real Zero Trust at the ZPA layer is the intersection of four questions, evaluated on every single session:
- WHO — which user, which IdP group, which IdP attribute (role, department, employee-type=full-time-only)?
- WHAT — which App Segment is being requested? (Not "the network" — the specific app.)
- WHERE — what device is the user on (managed/unmanaged, OS version, disk encrypted, AV running), and what country / source-IP-class?
- WHEN — is the session still inside its timeout window? Has the user re-authenticated to the IdP recently enough for this app's sensitivity tier?
Skip any one of those four and you've rebuilt a VPN with extra steps. Get all four right and you have something a legacy VPN literally cannot do: a payroll database that disallows itself when the laptop's disk encryption is turned off, regardless of who the user is.
2. The ZPA policy stack — top to bottom
ZPA has six policy objects you'll touch in production. They sit in a stack — some are inventory ("this app exists, here's how to reach it"), some are decision ("can this user reach it right now?"). Senior engineers carry the stack in their head; if you can recite it cold, half your troubleshooting is already done.
| Layer | What it is | What it decides | Lives at |
|---|---|---|---|
| Application Segment | The addressable thing — FQDN, IP, port, protocol of one internal app (or one tight family of apps) | "Is this DNS lookup / SYN even claimed by ZPA?" | Configuration → Application Segments |
| Segment Group | A bag of App Segments grouped for policy purposes | "Which container do my Access Policies act on?" | Configuration → Segment Groups |
| Server Group | The binding between App Segments and the Connector Groups that can serve them | "Which connectors are eligible to deliver this app?" | Configuration → Server Groups |
| Access Policy | WHO can reach WHAT under WHICH conditions, ordered top-down | Allow / Block / Require-Approval per session | Policy → Access Policy |
| Timeout Policy | How long a session stays valid before forced re-auth | "Does the user need to re-prove identity now?" | Policy → Timeout Policy |
| App Protection Policy | Inline WAF for ZPA-published apps (mostly Browser Access) | "Does this HTTP request contain SQLi / XSS / file-upload to block?" | Policy → App Protection |
Posture Profiles aren't a separate policy — they're a condition consumed by Access Policy. You define them once under Administration → Posture Profiles, then reference them inside Access Policy rules.
Read the stack from top to bottom on every new session: ZPA Cloud takes the user's identity, walks the Access Policy list to find the first matching rule, resolves which App Segment was hit, looks up its Server Group, picks a healthy connector from the bound Connector Group, and stitches the byte stream. Six bands. Every one of them gets a vote.
3. Application Segment — the addressable thing
An Application Segment is the smallest unit ZPA cares about. It answers the question "what FQDN / IP / port combinations should ZPA intercept on the user's behalf?" — nothing more. It does not say who can access it (that's Access Policy), and it does not say where it lives (that's Server Group).
Field-by-field anatomy
- Name —
app-jira-prod. Keep it human; the field shows up in every diagnostic and report. - Domain (FQDN / IP / CIDR entries) — can be a single FQDN (
jira.acme.internal), a wildcard (*.dev.acme.internal), an IP (10.40.12.5), or a CIDR (10.40.12.0/24). Wildcards are powerful and dangerous — see the war story below. - Port / Protocol — TCP and/or UDP, single port or range (
443,22-23,1433). UDP support matters for things like DNS-over-private-IP and some custom apps; most ZPA traffic is TCP. - Bypass Type —
NEVER(always go through ZPA),ON_ACCESS(only intercept when user actually clicks an app from the portal),ALWAYS(bypass ZPA, send direct).NEVERis the senior-engineer default for production apps. - Health Check Type —
DEFAULT(TCP/SYN),NONE(skip health checks, e.g. for stateful UDP apps), or per-app custom. Bad health-check choice = connectors marking the app unhealthy and sending users to the failover Server Group when the app is fine. - ICMP Access — enable if users need to ping the internal IP. Off by default.
- Domain Type —
FQDN,IP,CIDR— mostly auto-detected; manual override exists for edge cases. - Segment Group — the parent bag (every App Segment must belong to exactly one Segment Group).
- Server Groups — one or more (multi-select). This is where you bind the addressable thing to the connectors that can deliver it.
name: app-jira-prod description: Atlassian Jira — engineering primary issue tracker domains: - jira.acme.internal - jira-api.acme.internal tcp_ports: 443 udp_ports: (none) bypass_type: NEVER health_check: DEFAULT icmp: disabled domain_type: FQDN segment_group: engineering-tools server_groups: - srv-grp-mumbai-dc # primary - srv-grp-azure-india # warm failover
A team migrated 60 internal apps from VPN to ZPA over a weekend. Monday morning, 12 of them refused to load. Z-App showed the apps as "available" but every click returned a browser-side connection-reset. The team chased connectors, posture, IdP claims, even firewall on the DC side. Four hours in, someone noticed: the App Segment for Confluence had cofluence.acme.internal (typo) instead of confluence.acme.internal. The Z-App's DNS interceptor never claimed the real FQDN, so the browser fell through to public DNS, got NXDOMAIN (because the FQDN is internal-only), and returned the reset. Lesson: after creating any App Segment, paste the FQDN into the App Connector CLI and run a dig from the connector's vantage point — if the connector can't resolve it, neither will the user via ZPA.
4. Segment Groups & Server Groups — why they're separate
The most common L1 confusion in ZPA training is "Why do I need both? Just give me one bucket." The answer is: they're answering different questions, and conflating them is exactly what makes large ZPA deployments rot.
Segment Group — the policy container
Access Policy rules act on Segment Groups, not individual App Segments. So Segment Group is your unit of authorization scope. You define a Segment Group like engineering-tools and put Jira, Confluence, GitLab, Jenkins inside it. Then one Access Policy rule says "engineering IdP group → engineering-tools Segment Group: ALLOW" and all four apps are covered. Tomorrow you add Grafana to engineering-tools and the policy follows automatically.
Server Group — the delivery binding
Server Group binds App Segments to Connector Groups — it answers "which connector cluster can actually reach this app on the wire?" Jira in your Mumbai DC and the company SharePoint in Azure Central India have the same authorization audience (everyone) but need different connectors. So they sit in different Server Groups even if they share a Segment Group.
The 4-way mapping
| Object | Answers | Used by | Typical cardinality |
|---|---|---|---|
| App Segment | "What's the address?" | Segment Group + Server Group | 1 per app FQDN family |
| Segment Group | "What's the policy bag?" | Access Policy | 1 per business function (HR, ENG, FIN) |
| Server Group | "Which connectors can deliver?" | App Segment | 1 per network location (DC, cloud region) |
| Connector Group | "Which actual VMs are the connectors?" | Server Group | 1 per HA pair, per location |
One App Segment can belong to multiple Server Groups (failover, multi-region active-active). Multiple App Segments share one Segment Group. And one Connector Group can back multiple Server Groups (a Mumbai connector pair can serve both srv-grp-mumbai-dc and srv-grp-mumbai-shared-services).
5. Access Policy — the rule list that decides everything
Access Policy is the most-touched object in ZPA. It's a top-down ordered list, evaluated on every new session, first-match-wins. The structure of one rule:
- Rule order — integer position; lower number = evaluated first.
- Name —
eng-allow-jira. Use a verb-first convention. - Action —
ALLOW,BLOCK, orREQUIRE_APPROVAL(the last one routes the request to an admin queue — useful for break-glass paths to PROD). - Application criteria — one or more Segment Groups (you can also pin to specific App Segments, but Segment Group is the senior-engineer default).
- Conditions — ALL of the following can be combined with AND logic:
- User — specific accounts (avoid).
- Group — IdP group claim (the workhorse).
- IdP Attribute — arbitrary SAML/SCIM claim (
employee_type=full-time,cost_center=ENG-001). - Posture Profile — device-side checks (see Section 8).
- Network / Country — source-IP geo. India + UAE only? Set it here.
- Device Type — managed Windows / macOS / iOS / Android, or unmanaged.
- Browser Access vs Z-App — this rule applies only when the user is on browser-access (clientless), or only when Z-App is installed.
- Time-of-day — business-hours-only access for finance systems, for example.
ZPA Access Policy is default-deny. If no Allow rule matches, the session is denied. This has been the documented behavior since ZPA went GA — no exceptions, no "legacy contexts". Still recommended to author an explicit BLOCK * rule at the bottom of your policy for audit-trail clarity (the deny shows up in logs with a named rule). New segment? Doesn't match a specific allow rule above the deny? It gets blocked. Discovery happens via the diagnostic log, with a named rule, not a silent default-deny.
Policy → Access Policy → + Add Rule
Name: eng-allow-jira
Order: 50
Action: ALLOW
Application: Segment Group = engineering-tools
Conditions:
Group: engineers-acme (Okta group)
Posture: posture-managed-laptop
Device Type: Windows, macOS
Country: India, United Arab Emirates
Save
Eight steps, evaluated in order on every new session. Steps 3, 4, and 5 are the three places Zero Trust earns its name — identity, device, and time. Any one returning false collapses the session and writes a row to the diagnostic log naming the exact rule that vetoed.
6. Posture Profiles — what you can prove about the device
A Posture Profile is a named bundle of device-side assertions. ZPA evaluates it via the Z-App agent (and at re-evaluation cadence, not just at login). A profile is an AND of all the checks inside it — one failing check fails the whole profile.
Common checks you'll see in an enterprise Posture Profile:
- OS & version — "Windows 10 build ≥ 19041", "macOS ≥ 13.0".
- Disk encryption — BitLocker enabled on Windows, FileVault on macOS.
- Anti-virus — specific product running (CrowdStrike, SentinelOne, Defender) and last-scan recency.
- Firewall — OS firewall enabled.
- MDM enrolment — device is registered to Intune / Jamf / Workspace ONE.
- Domain join — computer is joined to AD / AAD.
- Certificate present — specific machine cert in the certificate store (the strongest "is this a corporate device?" check).
- Process / file exists — e.g. EDR agent process running.
- Jailbreak / root detection — on mobile.
- Geo & network — country of source IP, trusted network ranges.
Tier your profiles. Don't write one giant posture-everything profile and attach it to every Access Policy — that's how mass denials happen. Build three tiers and reference them per app sensitivity:
posture-baseline— OS supported + AV running. Used for low-risk apps.posture-managed-laptop— baseline + disk-enc + MDM + machine-cert. Used for normal business apps.posture-crown-jewel— managed-laptop + recent MFA + specific country + EDR running & healthy. Used for payroll, source code, prod-DB.
Posture Profile checked for a process named SymantecEndpointProtection.exe. Vendor pushed an update that renamed the binary to SES_Agent.exe. The check started returning false. Access Policies that relied on the profile started denying. By 9:30 AM, helpdesk had 600 open tickets and the CIO was on a war-room call. Lesson: never pin posture to a specific binary name. Pin to (a) registry key set by the vendor's MSI, (b) WMI service object, or (c) the EDR's own Posture API where available. Vendor renames are silent and frequent.
7. Timeout Policy — how long a session is trusted
Timeout Policy answers "when does ZPA force the user back through identity?" — either by re-evaluating SAML/SCIM with the IdP, or by triggering an MFA prompt depending on the IdP's own session config.
- Re-authentication timeout — absolute. After N hours, regardless of activity, the user re-auths. Common values: 24h (everyday apps), 12h (HR / finance), 4h (admin / prod-DB).
- Idle timeout — relative. After N minutes of no traffic to this app, the session is torn down. Common values: 60min for normal apps, 15min for sensitive ones.
- Scope — global default + per-Segment-Group override (your
crown-jewel-appssegment group should override to a tighter timeout).
Coordinate Timeout Policy with the IdP's own session lifetime. If your Okta session is 24h but your ZPA re-auth timeout is 8h, the user gets re-prompted at 8h but Okta silently re-authenticates them without a visible prompt — which is what you usually want. If Okta's session is shorter than your ZPA timeout, the user gets bounced back to the Okta login mid-meeting. Either align them, or make the IdP the more permissive one.
8. App Protection Policy — ZPA as a WAF for published apps
App Protection only works for Browser Access (BAF) apps — apps where ZPA terminates TLS and reverse-proxies (clientless flow). For native Z-App tunneled apps, ZPA never sees plaintext HTTP and cannot apply WAF rules. If you need WAF on a native-tunnel app, layer ZIA inspection on top.
App Protection is a less-talked-about ZPA feature: ZPA can act as an inline WAF for apps it publishes — particularly those published via Browser Access (clientless), where ZPA terminates TLS and proxies HTTP requests. Common rule families:
- SQL Injection — signature matches for common SQLi patterns in request URL / body.
- Cross-Site Scripting — reflected and stored XSS detection in form fields.
- File upload — block file uploads entirely, or restrict to specific MIME types.
- Command injection — shell-metacharacter patterns.
- HTTP method restriction — PROD app exposes only GET/POST? Block PUT/DELETE/PATCH at the WAF.
Always stage App Protection in detection-mode first for at least a week before flipping to block. Legitimate API patterns — especially the JSON bodies of internal automation tools — trip generic SQLi signatures more often than you'd think. The detection-mode logs become your tuning queue.
Browser Access (BAF) and Privileged Remote Access (PRA)
Browser Access (BAF) — clientless ZPA. User points their browser at mywebapp.private.zscaler.com (or a vanity FQDN), authenticates via IdP, and reaches the internal app — no Z-App needed. ZPA terminates TLS and reverse-proxies. Separately licensed. Use case: contractor laptops without Z-App, vendor portals, BYOD.
Privileged Remote Access (PRA) — browser-based SSH/RDP/VNC. Same clientless pattern, but for privileged protocols. ZPA renders the remote session in-browser (HTML5 canvas). Adds session recording, command filtering for SSH (block rm -rf, log every command), credential vault integration. Replaces CyberArk/BeyondTrust PSM for many use cases.
BAF vs PRA: BAF = web apps. PRA = privileged interactive sessions (SSH/RDP/VNC). Both are clientless. Both are interview staples post-2024.
IdP attribute & SAML claim format
SAML claim format: ZPA expects attributes in the standard SAML 2.0 namespace (e.g. http://schemas.xmlsoap.org/ws/2005/05/identity/claims/groups) or urn:oid: form. Multi-value attributes (user in multiple AD groups) are matched against the rule's group list using OR semantics by default. Standard misconfig: IdP sends groups as a single comma-separated string instead of multi-value — ZPA can't split it.
SCIM provisioning latency
SCIM sync latency: Default ~40 min from IdP (Okta/Entra) to ZPA. When a user is added to a group and reports "I still can't access" — first check is SCIM last-sync time under Administration → IdP Configuration → Last SCIM Sync. Force sync if urgent.
REQUIRE_APPROVAL action — default expiry
4-hour default expiry — if the admin doesn't approve in the approval queue within 4 hours, the user's request expires and the session is denied. Configurable per rule.
Posture refresh cadence
Posture refresh by Z-App: every 60-90 seconds. Broker decision is taken at session-start; mid-session posture changes don't tear down an existing session (only affect new sessions). This trades off "instant lockout when posture decays" against "no spurious mid-meeting drops if AV daemon hiccups for 30 seconds".
- Diagnostics → Trace User — pick the user, click a recent denied event. The trace shows: matched App Segment, matched Access Policy rule (or "no match → default deny"), Posture eval result per check, Timeout state, Server Group resolution, Connector pick. This is the single most useful diagnostic in ZPA — learn to read it in your sleep.
- Logs → User Status — live agent state per user: connected? to which Service Edge? posture last-eval pass/fail? Lists denies with timestamps.
- Configuration → Server Groups → (group) → App Connector status — per-app health from each connector's vantage point. Green = TCP/SYN succeeded; red = connector can't reach the app on the wire.
- Z-App tray icon → Diagnostics → Network Diagnostics — user-side: shows which Service Edge, posture state, list of advertised apps, latency to broker.
9. Common Mistakes
- Wildcard App Segment that swallows the AD domain. An App Segment of
*.acme.internalmatches every internal service — including HR file shares, the AD print server, dev databases users shouldn't know exist. Use specific FQDN entries; reserve wildcards for tight subdomains like*.dev-tools.acme.internalwith the matching Access Policy scoped narrowly. - No explicit deny-all at the bottom of Access Policy. Without it, a new App Segment that doesn't match any existing rule can fall through to default-allow in some configurations. Always pin
BLOCK *at the lowest priority and let discovery happen via deny logs. - Posture pinned to a vendor product name string. See the SymantecEndpointProtection war story above. Pin to registry keys, MSI product GUIDs, or the EDR's own API — not the binary filename.
- Aggressive Timeout Policy with no idle exemption. 1-hour re-auth + 5-minute idle timeout sounds tight and secure. It also forces users to re-MFA in the middle of customer calls. Calibrate per app sensitivity, not "tight everywhere".
- Server Group bound to the wrong Connector Group. The connectors exist, they're healthy, the app is up — but the Server Group points at the dev-region Connector Group instead of prod. Users get told the app is "unreachable". This bug hides for weeks because everything looks right in the inventory.
- App Protection turned on in block-mode on day one. Legitimate JSON API requests with embedded quoted strings trip SQLi signatures. Staging in detection-mode for at least a week + tuning the false positives is non-negotiable.
- Treating "user is in the right IdP group" as sufficient. Group alone is half the answer. Without Posture and Country and Device-Type conditions, you've just rebuilt the VPN with a Zscaler logo.
10. Pro Tips
- Name like a database schema, not like a Slack channel.
app-jira-prod,seg-grp-engineering-tools,srv-grp-mumbai-dc,posture-managed-laptop. Six months from now, when you're triaging a 2 AM page, the object name should tell you what it is, where it lives, and what tier it serves — without opening it. - Build a "discovery" Access Policy rule. One low-priority ALLOW rule scoped to a single IT-admin IdP group that matches every Segment Group. When a new app comes online and you're not sure which group should own it, IT-admin can hit it, the logs reveal who is actually using it, and you migrate the access into the proper rule. It beats guessing.
- Stage every Posture Profile change behind a "shadow" rule for 48h first. Clone the rule, change the action to "log-only" or scope it to a test IdP group of 5 people, watch the diagnostics for 2 days, then promote. Posture changes blast-radius the entire user base — treat them like a kernel patch.
11. Real-world scenario — the Acme × Beta M&A integration
Acme has just acquired Beta. Beta has 80 internal apps spread across an HR cluster, an engineering cluster, and a finance cluster — all behind the legacy Beta VPN. Acme's CIO wants Beta apps reachable via ZPA inside 30 days so the VPN concentrator can be turned off.
The mistake path (what a tired engineer actually does on Friday at 6 PM)
A junior engineer takes the brief and ships fast:
- Creates one App Segment
beta-allwith domain entry*.beta-corp.internal, all ports allowed. - Creates one Segment Group
beta-appscontaining onlybeta-all. - Creates one Server Group
srv-grp-beta-dcbound to the new Beta connector pair. - Creates one Access Policy rule: "IF user in IdP group
acquired-beta-employeesTHEN ALLOWbeta-apps".
Deploys. Posts in #it-announcements. Goes home for the weekend. On Monday at 11 AM, an internal audit catches the problem: every Beta employee can now reach Beta's payroll database, Beta's source code repos, Beta's customer billing system — including a marketing intern who shouldn't see any of it. Because the IdP group acquired-beta-employees is the entire workforce, and the Access Policy doesn't differentiate between roles or apps. The wildcard *.beta-corp.internal swallows every internal FQDN.
The correct path (Tuesday's rebuild)
- Inventory. 80 internal apps → 80 distinct App Segments. Each named
beta-app-<name>. No wildcards. Each has the right port/protocol restricted to actual app usage (not "all ports"). - Group by business function. Three Segment Groups:
beta-hr-apps(12 segments),beta-eng-apps(48 segments),beta-fin-apps(20 segments). - Bind to delivery. Two Server Groups:
srv-grp-beta-mumbai-dc(primary, all 80 apps) andsrv-grp-beta-azure-warm(warm failover for the 25 highest-tier apps). Each Server Group bound to its respective Connector Group (Mumbai DC vs Azure Central India). - Access Policy keyed to IdP roles. Three rules, not one:
beta-hr-access: IdP grouphr-acmeORhr-beta+ Postureposture-managed-laptop→ ALLOWbeta-hr-appsbeta-eng-access: IdP groupengineers-acmeORengineers-beta+ Postureposture-managed-laptop+ Country IN (India, UAE) → ALLOWbeta-eng-appsbeta-fin-access: IdP groupfinance-acmeORfinance-beta+ Postureposture-crown-jewel+ Device-Type managed-only + business-hours → ALLOWbeta-fin-apps
- Bottom of policy: explicit
BLOCK *default-deny. - Timeout: 12h re-auth + 60min idle for HR/ENG; 4h re-auth + 15min idle for FIN.
- Verify with Diagnostics → Trace User for one user per role group before announcing. Confirm: marketing intern denied on payroll (rule
BLOCK *fires), finance manager allowed on payroll between 9 AM and 7 PM only (rulebeta-fin-accessfires).
Lessons
- One wildcard App Segment beats six months of careful Access Policy work. Inventory the apps; never wildcard the domain.
- "Acquired employees" is not a meaningful authorization unit. Authorization runs on role groups, not employment status.
- Segment Group is your authorization scope. If two apps need different policies, they belong in different Segment Groups — even if they live on the same connector.
- Test with Diagnostics → Trace User before announcing. The trace will catch wildcard over-permission before the audit does.
12. Quick reference card
ZPA Policies — cheat-sheet
- 4-tier hierarchy: App Segment (address) → Segment Group (policy bag) → Server Group (delivery binding) → Connector Group (the VMs).
- App Segment fields that bite: domain entries (avoid wildcards), Bypass Type =
NEVERfor prod, correct health check, right Server Groups bound. - Segment Group vs Server Group: SegGrp = authorization scope. SrvGrp = network delivery. Conflating them rots large deployments.
- Access Policy: top-down, first-match. Combine Group + Posture + Device-Type + Country + Time. Always end with explicit
BLOCK *. - Posture Profile: AND of checks. Tier them (baseline / managed-laptop / crown-jewel). Pin to registry / MSI / EDR API, not binary filenames.
- Timeout Policy: re-auth (absolute) + idle (relative). Override per Segment Group for sensitive apps. Coordinate with IdP session lifetime.
- App Protection: WAF for ZPA-published apps (especially Browser Access). Stage in detection-mode at least a week before block.
- Primary diagnostic: Diagnostics → Trace User — shows matched segment, fired rule, posture result, server-group resolution, connector pick.
- Skip the four (WHO/WHAT/WHERE/WHEN) and you've rebuilt a VPN.
- Naming convention:
app-<name>-<tier>,seg-grp-<function>,srv-grp-<location>,posture-<tier>. Future-you will thank present-you.
Build a posture-aware Access Policy:
- In ZPA Admin → Posture Profiles: create a profile requiring disk encryption + AV running + OS >= Windows 11.
- Create an App Segment for your test app (say
jira.internal). - Create an Access Policy rule: Allow + Group=Engineering + Posture=above-profile. Save.
- Add explicit BLOCK * as the last rule for audit clarity.
- Test from a compliant laptop → access works. Disable BitLocker → reconnect → confirm denial in ZPA logs.
📝 Check your understanding
10 scenario questions — same depth you'll see in interviews + practice exams. Pick one answer per question. You need 70% (7 of 10) to mark this lesson complete on your profile.
What's next?
Module 12 covers two tools that work alongside ZPA — Cloud Browser Isolation for risky web access on unmanaged devices, and Source IP Anchoring (SIPA) for SaaS allow-lists that demand a stable corporate IP.