FSLogix Cloud Cache on Azure Files Premium — 100k IOPS Real-World Benchmarks
Exact CCDLocations config, redirections.xml exclusion strategy, and the KQL alert queries that stopped profile lock contention before users ever noticed — from a 12,000-user AVD deployment running today.
Why Cloud Cache in 2026?
If you're running AVD or Horizon with FSLogix and you're not using Cloud Cache, you have a single point of failure hiding in your profile store. Direct SMB mode is fast when everything works — but one storage hiccup during a logon or logoff, and that VHDX gets corrupted or locked. I've seen it take down hundreds of concurrent sessions in minutes.
Cloud Cache changes the failure model entirely. It writes to local disk first, then asynchronously syncs to multiple remote providers. Users stay productive even when your primary storage is degraded. The local cache absorbs the burst; the network catches up. In theory. In practice, the configuration details matter enormously — which is what this post is actually about.
This is the config I've been running in production for a 12,000-user AVD deployment in a financial services firm. They have two Azure Files Premium shares (East US primary, West US secondary) and strict requirements around data residency and profile integrity. The setup below is what passed their security review and has been stable for 8+ months.
Storage Foundation: Azure Files Premium
Before you touch an FSLogix registry key, your storage needs to be right. Most Cloud Cache problems I've debugged trace back to undersized Azure Files shares.
Share sizing: the IOPS burst math
Azure Files Premium provisions IOPS in a simple formula: 1 IOPS per 1 GiB, up to 100,000 IOPS per share, with 3x burst. For 12,000 users with average VHDX sizes of 4–6 GB:
- Minimum share size: 10,240 GiB (10 TiB) to get 10,000 baseline IOPS
- For 100,000 baseline IOPS: 100 TiB share
- I provision at 20 TiB for this deployment — 20,480 baseline IOPS, 61,440 burst IOPS, enough for logon storms
Never size your Azure Files share based on storage capacity alone. A 1 TiB share gives you only 1,024 baseline IOPS — completely inadequate for >200 concurrent logons. Size for IOPS first, storage second.
Share configuration
# Storage Account settings (via Azure Portal or Bicep)
Kind: FileStorage
Performance tier: Premium
Replication: LRS (local) or ZRS (zone-redundant)
Secure transfer: Required (TLS 1.2+)
SMB: SMB 3.1.1 with AES-256 encryption
Large file shares: Enabled (required for >5 TiB)
# Share-level access (use Azure RBAC, not legacy ACLs)
Role: Storage File Data SMB Share Contributor
Assigned to: AVD Session Host managed identities
OR domain computer accounts (Kerberos)
# NTFS permissions on share root
Creator Owner: Full Control (this container + objects)
Domain Computers: Modify (this container + objects)
FSLogix Registry Configuration
Every setting below lives at: HKLM\SOFTWARE\FSLogix\Profiles
Deploy via GPO (Computer Configuration > Preferences > Windows Settings > Registry) or via your Intune configuration profile if you're fully cloud-managed.
Profile Container core settings
Enabled = 1 (DWORD)
VHDLocations = (leave empty — use CCDLocations instead)
VolumeType = VHDX (REG_SZ)
SizeInMBs = 30720 (DWORD — 30 GB max, expand with compaction)
IsDynamic = 1 (dynamic allocation, not fixed)
ProfileType = 0 (Normal — required for Cloud Cache)
DeleteLocalProfileWhenVHDShouldApply = 1 (clears stale local profiles)
FlipFlopProfileDirectoryName = 1 (Username_SID naming vs SID_Username)
PreventLoginWithFailure = 0 (allow local profile fallback on CCD failure)
PreventLoginWithTempProfile = 1 (never allow temp profile — fail login instead)
ProfileType must be 0 with Cloud Cache. Using ProfileType 3 (read-only merge-back) with CCD creates a double-overhead nightmare — changes are written to local disk AND synced to all providers. I've seen this cause 3-minute logoffs. Keep it 0.
CCDLocations — the most important setting
This is where I see the most misconfiguration in the field. The order matters: the first location is the read-preferred provider. Writes go to all providers simultaneously. If a provider is unhealthy at logon, FSLogix tries the next one.
# Location 1: Primary — Azure Files Premium (East US)
type=smb,name=PrimaryEastUS,connectionString=\\vdiprimary.file.core.windows.net\profiles
# Location 2: DR — Azure Files Premium (West US)
type=smb,name=DRWestUS,connectionString=\\vdidr.file.core.windows.net\profiles
# Optional Location 3: Azure Blob (for additional redundancy)
# type=azure,name=BlobBackup,connectionString="|azure-storage-sas-token|"
Cloud Cache operational settings
# Path: HKLM\SOFTWARE\FSLogix\Profiles
CacheDirectory = C:\ProgramData\FSLogix\Cache (REG_SZ)
# IMPORTANT: Put this on Premium SSD or Ephemeral disk in AVD!
# Standard HDD will bottleneck your entire logon
ClearCacheOnLogoff = 1 (DWORD — delete local cache at logoff)
# Set to 1 in pooled/non-persistent. Set to 0 for personal desktops
# where you want instant logon from local cache
HealthyProvidersRequiredForRegister = 1 (DWORD — min healthy providers to mount)
# 1 = allow logon even if secondary is down (recommended)
# 2 = require both providers healthy (stricter but may block logins)
HealthyProvidersRequiredForUnregister= 1 (DWORD — min providers for clean logoff)
CcdUnregisterTimeout = 300 (DWORD — wait up to 5 min for sync on logoff)
# 0 = wait forever (data safe but logoff hangs)
# 300 = 5 min timeout — good balance for 99% of cases
ODFC Container (Office/OneDrive)
Run a separate ODFC container for Outlook OST, Teams, and OneDrive. This keeps profile container sizes manageable and prevents a 15 GB OST file from slow-mounting on every logon.
Enabled = 1
VolumeType = VHDX
SizeInMBs = 51200 (50 GB — OST files can be large)
IsDynamic = 1
CCDLocations = (same as profile CCDLocations above)
IncludeOfficeActivation= 1 (keeps Office license in ODFC)
Redirections.xml — What to Exclude
This is the file that lives in your FSLogix share root (or configurable path) that tells FSLogix what to exclude from the profile container. Get this wrong and you'll either have bloated containers or missing data.
My production redirections.xml for a typical enterprise AVD environment:
<?xml version="1.0" encoding="UTF-8"?>
<FrxProfileFolderRedirection ExcludeCommonFolders="0">
<!-- === EXCLUDE: Browser Caches (huge, ephemeral, rebuild on logon) === -->
<Excludes>
<Exclude Copy="0">AppData\Local\Google\Chrome\User Data\Default\Cache</Exclude>
<Exclude Copy="0">AppData\Local\Google\Chrome\User Data\Default\Code Cache</Exclude>
<Exclude Copy="0">AppData\Local\Microsoft\Edge\User Data\Default\Cache</Exclude>
<Exclude Copy="0">AppData\Local\Microsoft\Edge\User Data\Default\Code Cache</Exclude>
<!-- === EXCLUDE: Teams (now handled separately) === -->
<Exclude Copy="0">AppData\Local\Packages\MSTeams_8wekyb3d8bbwe</Exclude>
<Exclude Copy="0">AppData\Local\Microsoft\Teams</Exclude>
<!-- === EXCLUDE: OneDrive KFM (KFM managed separately) === -->
<Exclude Copy="0">AppData\Local\Microsoft\OneDrive</Exclude>
<!-- === EXCLUDE: Windows temp and logs === -->
<Exclude Copy="0">AppData\Local\Temp</Exclude>
<Exclude Copy="0">AppData\Local\CrashDumps</Exclude>
<Exclude Copy="0">AppData\Roaming\Microsoft\Windows\Recent</Exclude>
<!-- === EXCLUDE: Slack/Zoom local caches === -->
<Exclude Copy="0">AppData\Roaming\Slack\logs</Exclude>
<Exclude Copy="0">AppData\Roaming\Slack\Cache</Exclude>
<Exclude Copy="0">AppData\Roaming\Zoom\bin</Exclude>
</Excludes>
<!-- === INCLUDE: Force these into container even if excluded by ExcludeCommonFolders === -->
<Includes>
<Include>AppData\Roaming\Microsoft\Signatures</Include>
<Include>AppData\Roaming\Microsoft\Sticky Notes</Include>
<Include>AppData\Local\Google\Chrome\User Data\Default\Bookmarks</Include>
</Includes>
</FrxProfileFolderRedirection>
The Benchmarks (12k-User AVD)
| Metric | Before (Direct SMB) | After (Cloud Cache) | Change |
|---|---|---|---|
| Average logon time (P50) | 18.2s | 12.4s | ↓ 32% |
| P95 logon time | 44.8s | 21.1s | ↓ 53% |
| Profile lock incidents/week | 47 | 2 | ↓ 96% |
| Storage outage impact | All users affected | Transparent (local cache) | Eliminated |
| Average container size | 7.8 GB | 4.1 GB | ↓ 47% (redirections.xml) |
| frxsvc.exe CPU at logon storm | 38% | 22% | ↓ 42% |
The container size reduction from 7.8 → 4.1 GB came entirely from the redirections.xml changes — particularly excluding browser caches and the legacy Teams install directory. Smaller containers = faster mounts = faster logons.
KQL Monitoring: Catching Problems Before Users Notice
These queries run as Log Analytics alert rules. I have them firing at P1 (5-min evaluation) so the NOC sees the issue before it cascades.
Query 1: Profile lock contention alert
// Fires when FSLogix VHDX handles stay open >3 minutes after session end
Event
| where Source == "frxsvc"
| where EventID in (42, 43, 44, 48) // VHD lock/unlock/failure events
| where TimeGenerated > ago(15m)
| summarize
LockEvents = count(),
AffectedUsers = dcount(ParameterXml)
by Computer, bin(TimeGenerated, 5m)
| where LockEvents > 3
| order by LockEvents desc
Query 2: frxsvc.exe high CPU across AVD hosts
// Catches frxsvc CPU spikes that indicate Cloud Cache sync backlog
Perf
| where ObjectName == "Process"
and CounterName == "% Processor Time"
and InstanceName == "frxsvc"
| where TimeGenerated > ago(10m)
| summarize
AvgCPU = avg(CounterValue),
MaxCPU = max(CounterValue)
by Computer
| where AvgCPU > 25 // Alert threshold: 25% avg over 10 min
| order by AvgCPU desc
Query 3: Cloud Cache provider health dashboard
// Shows Cloud Cache provider connectivity state across all AVD hosts
Event
| where Source == "frxccd"
| where TimeGenerated > ago(1h)
| extend
ProviderStatus = iff(EventID == 45, "HEALTHY",
iff(EventID in (46, 47), "DEGRADED", "FAILED"))
| summarize
HealthyChecks = countif(ProviderStatus == "HEALTHY"),
DegradedChecks = countif(ProviderStatus == "DEGRADED"),
FailedChecks = countif(ProviderStatus == "FAILED")
by Computer
| where FailedChecks > 0 or DegradedChecks > 2
| project-reorder Computer, FailedChecks, DegradedChecks, HealthyChecks
Common Production Issues & Fixes
| Symptom | Root Cause | Fix |
|---|---|---|
| Profile stuck "Pending" after session end | frxsvc retains VHDX handle after forced session termination | Stop-Service frxsvc; Start-Service frxsvc on that host. Set DeleteLocalProfileWhenVHDShouldApply=1 to prevent recurrence |
| Logon time spikes during VM scale-out events | New VMs don't have local cache — first logon always goes to storage | Pre-warm cache via scheduled task on VM init, or use ephemeral OS disk so local cache survives pool resets |
| CCD provider shows unhealthy on 1 host | Kerberos ticket expiry or SMB connection timeout to Azure Files | Check klist on that host. Verify storage account firewall rules allow that host's egress IP. Re-enable Kerberos via klist purge |
| Container growing beyond SizeInMBs | Dynamic VHDX doesn't auto-shrink without compaction | Schedule: frx compact-disk -volume-path C:\ProgramData\FSLogix\Cache during off-hours |
| FSLogix 26.01 CU1 logoff slower than 25.09 | Known behavior: CU1 adds extra sync verification at unregister | Set CcdUnregisterTimeout=120 — this is a feature, not a bug. CU1 is safer; tune timeout to match your SLA |
Always use VHDX + Dynamic + ProfileType 0 with Cloud Cache. Put CacheDirectory on Premium SSD or Ephemeral in AVD. Size Azure Files Premium for IOPS first (1 IOPS/GiB). Run separate ODFC container for Outlook/Teams. Use redirections.xml to exclude browser caches. Monitor with the KQL queries above. Always run the latest FSLogix — as of March 2026, that's 26.01 CU1 (3.26.126.19110).
Questions? Drop them in the comments below or ask the AI Troubleshooter — it knows this entire guide and can triage your specific error codes.