Cloudflare Zero Trust and SASE - Lab Guide (Legacy)

⬇ Course syllabus (PDF)

This guide is adapted for a single-site lab pod (1 Windows 11 + 1 Ubuntu Server). Steps requiring multi-site infrastructure are omitted.

The AcmeCorp Scenario

If you came from the Application Security course, you already secured everything the public touches at AcmeCorp: the website and the orders API, sitting behind WAF, bot management, rate limiting, and API Shield, with the origin sealed so nobody can walk around it. This course is the other half of the same company. AcmeCorp still runs like an old flat network on the inside: employees connect over a VPN, and once they're on it they're trusted by default, internal apps answer to anyone who can reach them by IP, unmanaged devices join without anyone checking their health, and nothing inspects what leaves the building. You've just been hired to run AcmeCorp's Zero Trust rollout, and your job is to rebuild the inside of the company around one rule: trust nothing by default, verify every user and every device, every time, and control every byte in and out.

Over the next 18 labs you'll give every employee a verifiable identity, put the Cloudflare One Client on every device, check each device's health before it's allowed near anything, publish internal apps without opening a single inbound port, put an access decision in front of every one of them, filter traffic in both directions, then prove the old direct paths are gone and extend the same controls to servers. Reading the lab titles top to bottom is the whole build. Every lab opens with a "This lab" box, highlighted in yellow, that says which piece you're building and why, and roughly how long it takes.

Lab Environment Overview

The lab pod has two endpoints on separate Azure subnets. They cannot reach each other without a Cloudflare Tunnel.

Ubuntu origin at 10.91.0.X (called "Ubuntu Server" throughout this guide):

Win11 client at 10.90.0.X (called "Windows 11 desktop" throughout this guide):

Credentials

The password for every account in this lab is #Savetheinternet. It does not change.

VM console logins (Windows and Ubuntu)

Use these to sign in to any virtual machine console in the lab portal.

MachineUsernamePassword
Win11 client (10.90.0.X)cloudflare#Savetheinternet
Ubuntu origin (10.91.0.X)cloudflare#Savetheinternet

SAML identity provider users

Use these when Cloudflare prompts for SAML authentication during Cloudflare One Client enrollment, Access app login, or App Launcher access.

UsernamePasswordPurpose
itadmin@acmecorp.com#SavetheinternetAdmin user; SAML test (Step 1.4) and initial Cloudflare One Client enrollment (Step 3.3)
usera@acmecorp.com#SavetheinternetPrimary ZTNA test user (Labs 1, 2, 3)
userb@acmecorp.com#SavetheinternetUnauthorized user; used to demonstrate a Cloudflare Access block (Step 8.7)

Local Ubuntu accounts created during the lab

Step 9b.1 walks you through creating two local Linux user accounts on the Ubuntu Server. These accounts are not pre-provisioned on the lab pod; you create them yourself. They are the OS accounts you log in to through the browser-rendered SSH app in Steps 9b.2-9b.3.

UsernamePasswordCreated on
usera#SavetheinternetUbuntu Server (Step 9b.1)
itadmin#SavetheinternetUbuntu Server (Step 9b.1)

General Tips

In browser-based VM consoles, paste using right-click, not Ctrl+V. Ctrl+V will not work.

When copying multi-line commands, paste them one line at a time. Concatenated paste often breaks the installer.

Throughout the guide, <team-name> auto-substitutes once you fill it into the orange Fill in here form in Step 1.1.

Throughout the guide, <your-domain> auto-substitutes to <your team name>.sxplab.com once you fill in your team name in the orange Fill in here form in Step 1.1.

Lab 1 - Identity & Account Baseline

Before Zero Trust can bind a policy to anyone, AcmeCorp has to agree on who vouches for identities and turn its inspection engine on. You connect Cloudflare to the shared lab SAML identity provider so every login checks against one trusted source, then flip on the account-wide baseline the rest of the course depends on: the proxy, TLS decryption, protocol detection, host selectors, and malware scanning. It's the least glamorous lab in the course, and every lab after it assumes it's already done. You'll also confirm you can read the eight log datasets under Insights & Logs > Logs, because when something breaks later, that's where you'll be looking. Estimated time: 20-30 minutes.
Prerequisites:
  • Lab portal access with your assigned pod
  • Browser access to the Windows 11 desktop console
  • Your team name (you will find it in Step 1.1)

1.1 Identify your team name

Why: Almost every URL, MSI parameter, and SAML configuration in this lab embeds your team name. Capture it now so you do not stop later to look it up.

Open the Cloudflare dashboard at https://dash.cloudflare.com and sign in.

Select your assigned lab account from the Accounts list (e.g., Implement Cloudflare One lab - <team-name>). This takes you to the account home page.

In the account sidebar, scroll down to the Protect & Connect section and click Zero Trust. You arrive at the Cloudflare One Overview page.

In the Zero Trust sidebar (left side), click Settings.

On the Settings page, the default filter is All. The first card shown is Team name and domain.

Copy the Team name value and paste it into the field below.

Fill in here: your team name

Enter your team name once. From this point on, every <team-name> and <your-domain> placeholder across the entire guide is replaced with your real values, including inside copy blocks, so when you click Copy you get the real command. You only type your team name; your pod's assigned domain is filled in automatically as <your-domain> (always <your team name>.sxplab.com). Values save to your browser only; nothing crosses to other students.

Verify: The Team name and domain card in Settings shows the team name you entered, and every <team-name> and <your-domain> placeholder in this guide now shows your real values. Your web apps live under <team-name>.sxplab.com, which is the value the your-domain placeholder resolves to throughout this guide. For example, the AcmeCorp website will be published at web.<team-name>.sxplab.com later in Lab 8. The separate address <team-name>.cloudflareaccess.com is your Cloudflare Access team domain (the web App Launcher), not your pod's web domain. The App Launcher is not turned on yet, so visiting that address now shows a "Welcome! Please contact your administrator to enable the Access App Launcher" message; you enable it later in the course.

1.2 Create a SAML identity provider in the shared lab IdP

Why: The lab IdP needs to know about your specific Zero Trust org before it can issue assertions to you. Registering your team name at the shared lab IdP creates a SAML provider tied to your team.

Open a new browser tab and go to https://labs.cloudflare.com/saml/register

In the registration form, paste your team name in the format <team-name>.cloudflareaccess.com.

Click the register button to create the SAML provider.

On the confirmation page, click the Download link for the SAML metadata file. Save it somewhere you can find it (Downloads is fine).

Verify: The downloaded file is an XML document containing <EntityDescriptor> and <IDPSSODescriptor> elements.

1.3 Add the SAML provider to your Zero Trust account

Why: Downloading the metadata file is half the integration. Importing it into Zero Trust completes the trust relationship in both directions so your users can authenticate through the lab IdP.

Return to the Zero Trust dashboard tab.

In the sidebar, click Integrations to expand it.

Click Identity providers.

Click Add an identity provider.

From the list of identity provider types, click SAML.

In the SAML configuration form, drag and drop the SAML metadata XML file you downloaded in Step 1.2, or click the import area and select the file.

Cloudflare automatically populates the SAML configuration fields from the metadata. Leave them as imported.

Click Save.

Verify: The SAML provider appears in the Identity provider integrations list with the Test button next to it.

1.4 Test SAML authentication

Why: Catch IdP setup mistakes now. Every subsequent lab assumes this works.

Still on the Integrations > Identity providers page, find the SAML provider you just created in the list.

Click Test in the row.

A new browser tab opens to the shared lab IdP sign-in page.

Sign in with:

FieldValue
Usernameitadmin@acmecorp.com
Password#Savetheinternet

The browser returns to Cloudflare showing Your connection works! or equivalent success message.

Verify: You see a success page after authentication. If you see an error, the metadata file may have been corrupted or the team name may be misspelled. Repeat Steps 1.2 and 1.3.

1.5 Enable the account-wide baseline settings

Why: Many later labs assume Cloudflare Gateway is proxying traffic, TLS is being decrypted, protocol detection is on, and egress host selectors are available. Turn these on now so you do not chase mysterious failures later.

The settings live on two filter views of the Traffic settings page: Proxy and inspection holds proxy/TLS/protocol toggles, and Policy settings holds host selectors and malware scanning.

In the Zero Trust sidebar, click Traffic controls to expand it.

Click Traffic settings.

At the top of the page, click the Proxy and inspection filter button. Configure these toggles:

SettingValueNotes
Allow Secure Web Gateway to proxy trafficOnConfirm TCP, UDP, and ICMP checkboxes are all checked under "Select protocols to forward"
Inspect HTTPS requests with TLS decryptionOn
Allow protocol detectionOn
Manage HTTP inspection by portDefaultLeave as "Inspect on 80 and 443 only"

Click the Policy settings filter button. Configure these toggles:

SettingValueNotes
Allow egress policy host selectorsOn
Scan files for malwareOnAlso enable Scan on file upload and Scan on file download under the toggle
Verify: Reload the page. Both filter views (Proxy and inspection and Policy settings) show the toggles still set to On.

1.6 Confirm log access via Cloudflare One Logs page

Why: Later labs verify policy hits, blocked traffic, and posture results by reading logs. The Zero Trust dashboard's Logs page gives filterable access to all 8 datasets. This step doubles as the reference the later labs point back to: it names the exact result column and filter each dataset uses, so when a lab says "confirm the block in the logs," you already know which viewer to open and what to look for.

In the Zero Trust sidebar, click Insights & Logs to expand it, then click Logs. The Logs landing page shows 8 dataset cards. Click a card to open that dataset's viewer.

New Logs experience: The dashboard now opens the redesigned Logs viewers by default, and every step in this guide is written for them. Each viewer has a Return to old logs button in the top-right if you ever need the legacy layout, but you shouldn't need it. Two things carry across all the Gateway and Access viewers:
  • The enforcement result is in the Action column (Gateway: DNS, Network, HTTP) or the Decision column (Access), shown as a colored pill.
  • The policy that matched is in the Policy name column. A request only shows a policy name when it matched a named policy you created; implicit/default allows show --, which is normal.

The eight datasets, and what each one is for:

Dataset cardProductWhat it shows / when you use itResult column (values)
Admin activity logsAuditConfiguration changes: who created, updated, or deleted settings.n/a (audit trail)
Access authentication logsAccessLogin attempts to Access-protected apps. Used in the Access labs (self-hosted apps, App Launcher, private-network ZTNA).Decision (Allowed / Blocked)
DNS query logsGatewayDNS queries inspected by Gateway and the DNS policy that matched. Used in the internal-DNS, DNS-location, and DNS/HTTP-policy labs.Action (Allow / Block / Override / Bypass)
Network logsGatewayL3/L4 traffic inspected by Gateway and the network policy that matched. Used in the private-network ZTNA and network-policy labs.Action (Allow / Block / Bypass / Override)
HTTP request logsGatewayHTTP requests inspected by Gateway, plus HTTP-policy, isolation, and DLP matches. Used in the DNS/HTTP-policy, order-of-enforcement, and SWG/DLP labs.Action (Allow / Block / Isolate / Bypass / Override)
SSH command logsAccessSSH commands a user ran on a target through Access for Infrastructure.n/a (command record)
SCIM provisioning logsAccessIdentity sync between Access and your identity provider (90-day retention).n/a (sync record)
Posture logsDevice clientDevice posture check results. Used in the posture lab. This one opens a separate "Device posture logs" viewer (no Action column, no summary chart).Conditions met (Success / Failure)
How filtering works (it matches exactly): In every Gateway and Access viewer, + Add filter builds a Field / Operator / Value condition, and the only operators are equals and does not equal (there is no "contains"). So later labs filter by exact values:
  • By result: Field Action (Gateway) or Decision (Access) equals the value you want, e.g. Action equals Block.
  • By policy: Field Policy equals, then pick the exact policy name from the Value list.
  • By user: Field Email equals the exact user email.
Verify: All 8 dataset cards are visible on the Logs landing page. Open DNS query logs: the table shows Action and Policy name columns, and the summary counts Allow / Block / Override / Bypass appear across the top. Click + Add filter and confirm it offers Field / Operator / Value selectors with the Operator limited to equals / does not equal (results may be empty until later labs generate traffic).

Lab 2 - Device Profile & Enrollment Setup

Now Zero Trust knows who to trust, but not which devices are allowed to enroll or how the client should behave once it does. Before you hand out the Cloudflare One Client in the next lab, you configure it on paper: the global client settings, plus a device profile (Lab-Profile) scoped to your three lab identities with production-shaped behavior (users can't leave the org, auto-updates off, auto-connect on, split tunnel adjusted for Microsoft 365), and the enrollment policy that says only a verified SAML login from your IdP may join. Nothing enrolls yet. You're writing the rules the client will follow the moment it's installed. Estimated time: 20-30 minutes.

2.1 Configure Cloudflare One Client global settings

Why: The global settings apply to every Cloudflare One Client across the org. The three settings below are recommended baselines for any production deployment.

Return to the Zero Trust dashboard.

In the sidebar, click Team & Resources to expand it.

Click Devices.

Click the Management tab at the top of the page.

Click the Global device client settings filter button. Locate and configure the following toggles:

SettingValueWhy
Install CA to system certificate storeOnInstalls available certificates to the certificate store of enrolled Windows, macOS, and Linux devices. Devices need the certificate to inspect HTTPS traffic, display custom block pages, and apply other advanced features.
Assign a unique IP address to each deviceOnOverrides the device's existing local interface IP with a unique IP address randomly assigned from the Carrier Grade NAT space (100.64/10). On by default for MASQUE devices and required for Cloudflare Mesh tunnels.

Now click the Mesh connectivity filter button. Configure:

SettingValueWhy
Allow all Cloudflare One traffic to reach enrolled devicesOnWhen on, all traffic sent from your organization to Cloudflare can reach your enrolled devices. Manage traffic security through Gateway network policies. Update your split tunnel configurations to ensure traffic flows as expected.
Verify: All three settings are on across both filter tabs. Reload the page to confirm.

2.2 Create a new device profile

Why: The default device profile is reasonable for a quick test, but the lab requires settings that the default does not have. Creating a new profile with a matching expression scopes it to your lab users and preserves the Default profile as a fallback for any device that does not match.

Still under Team & Resources > Devices, click the Device profiles tab at the top of the page.

You arrive at the "View all device profiles" page. Confirm you see the Default profile in the list with a Profile name column and Status toggle.

Click + Add profile.

Give the profile a name: Lab-Profile.

Build the matching expression

Under Build an expression, configure two conditions joined by AND. Add every value as its own entry: click Copy on a block below, paste it into the Value field, press Enter, then move to the next block.

Condition 1 — Selector User email, Operator in. Add each address on its own:

itadmin@acmecorp.com
usera@acmecorp.com
userb@acmecorp.com

AND — Condition 2 — Selector Operating system, Operator in. Add each OS on its own:

windows
mac
linux
ios
android
chromeos
Check: Condition 1 shows three separate email entries and condition 2 shows six separate OS entries. If a Value shows one long entry instead, delete it and re-add each value on its own.

Click Save.

Open the new profile by clicking its name.

Verify: Lab-Profile appears in the device profiles list at position #1 (above Default at #2). The matching expression shows both conditions.

2.3 Configure the device profile settings

Why: These settings define how the Cloudflare One Client behaves on every enrolled device matched by this profile: whether users can disconnect, whether the client auto-updates, and whether it auto-connects on boot.

With your new profile open, scroll down to the Configure settings section.

Find Allow device to leave organization and turn it Off. This prevents users from unenrolling.

Find Allow updates and turn it Off. Updates will be managed via MSI in production; auto-update is not desirable in a controlled lab.

Find Auto connect and turn it On. Set the Timeout to 30 minutes.

Why 30 minutes and not 1? Auto connect forces the client back on after the timeout expires. In a lab environment you need to be able to disconnect the client for testing (e.g., verifying behavior with the client off, switching modes in Step 16.4). Setting 1 minute means the client reconnects before you can finish a test. 30 minutes gives you enough time to run exercises with the client disconnected. In production, 1 minute is the tightest setting; for the lab, 30 minutes is practical.
Why not 0? Setting Auto connect to 0 does not mean "reconnect immediately." Per the Cloudflare docs, 0 means "allow the switch to stay in the off position indefinitely until the user turns it back on." That is the least aggressive setting; it never auto-reconnects. Values 1 through 1440 are the ones that actually auto-reconnect.

Do not save yet ΓÇö Steps 2.4 and 2.5 make more changes to this profile.

2.4 Adjust local domain fallback

Why: Local domain fallback tells Cloudflare One Client to send DNS queries for certain domains to the local resolver instead of through Cloudflare. The default list includes corp. Removing it ensures all DNS queries (including any .corp domains) route through Cloudflare Gateway, which is the expected behavior for a managed Zero Trust deployment.

Still on the profile page, scroll down to the Local Domain Fallback section (it appears above Split Tunnels) and click Manage. This opens a separate Local Domain Fallback page showing a list of 14 default domain entries.

Find the entry corp in the list. Click the three-dot menu (...) on the right side of the corp row and select Delete.

Verify: The Local Domain Fallback entries list shows 13 items (down from 14). Corp is gone.

Click Back to Profile at the top left to return to the device profile.

2.5 Configure split tunnel for the device profile

Why: Cloudflare One Client uses split tunnel in Exclude mode by default - traffic destined to listed IP ranges bypasses the Cloudflare tunnel and goes directly through the local network adapter. The default list excludes RFC1918 and CGNAT ranges. Adding Microsoft 365 domains to the exclude list ensures O365 traffic bypasses Cloudflare for performance reasons (a common production pattern).

Back on the profile page, scroll down to the Split Tunnels section (below Local Domain Fallback). You see four radio button options: Include IPs and domains, Exclude IPs and domains (should already be selected), Include IPs, and Exclude IPs. Confirm Exclude IPs and domains is selected.

Below the Split Tunnels section, find the Directly route Microsoft 365 traffic toggle. Turn it On. This automatically excludes Microsoft 365 IPs and domains from going through Cloudflare Zero Trust. The entries are appended every hour and will not be visible in the split tunnel list UI.

Click Save profile. This saves everything from Steps 2.3 and 2.5.

Verify: Reopen the profile. Allow device to leave organization is Off, Allow updates is Off, Auto connect is On with Timeout 30 minutes, Split Tunnels is set to Exclude IPs and domains, and Directly route Microsoft 365 traffic is On.

2.6 Configure Device Enrollment Permissions

Why: Enrollment Permissions decide which users are allowed to install and authenticate the Cloudflare One Client against your org. Without an enrollment policy, no one can enroll.

In the Zero Trust sidebar, click Team & Resources > Devices.

Click the Management tab at the top of the page.

Click the Device enrollment filter button.

In the Device enrollment permissions section, click the Manage button. This opens the Device enrollment permissions page.

You land on the All tab showing four filter tabs: All, Policies, Policy tester, Login methods. In the Device enrollment policies section, click Create new policy (or click Builder to switch to the policy builder view).

In the Policy details panel on the right side, configure:

FieldValue
Policy NameLab Enrollment
ActionAllow
Policy session durationSame as application session duration (default)

In the Policy rules panel on the left side, under Include:

FieldValue
SelectorLogin Methods
ValueSelect the SAML provider you created in Step 1.3

Click Save policy.

Scroll down to the Authentication section (marked NEW). Configure:

SettingValueEffect
Accept all available identity providersOffIn the Choose available identity providers for this application dropdown, select only your SAML provider. This forces SAML and prevents One-time PIN (OTP) from appearing as an enrollment option.
Apply instant authenticationOnAllows users to skip identity provider selection when only one login method is available, giving a smoother enrollment experience.

Click Save at the bottom of the page.

Verify: The Lab Enrollment policy appears in the Device enrollment policies list with action Allow. The Authentication section shows only your SAML provider selected and Apply instant authentication is On.

Lab 3 - Deploy the Cloudflare One Client

The rules are written, so now you actually deploy the client. Real companies don't ask thousands of employees to install it by hand or type the team name on each machine, so you push it the way SCCM, Group Policy, or Intune would: a silent MSI install with the ORGANIZATION parameter baked into the command. You enroll the Windows desktop with a SAML login as itadmin, confirm Gateway is now processing the device's internet traffic, and seed mdm.xml with the pre_login key you'll switch on in Lab 9a. From here the device is enrolled, and every request it makes is attributable. Estimated time: 45-60 minutes.

3.1 Sign in to the Windows 11 desktop console

Why: Subsequent steps install and configure the Cloudflare One Client directly on the Windows client. You need a logged-in session for that.

From the Lab Portal, click the monitor icon on the Win11 client card to open the Windows desktop console in your browser.

If the desktop background is black, the VM is still booting. Wait one or two minutes until the background turns orange.

The VM auto-logs in using the built-in cloudflare account from the Credentials table at the top of this guide. No manual sign-in is needed.

Wait for the desktop to load.

Verify: You are on the Windows desktop with the taskbar and start menu visible. There is a Cloudflare Cloudflare One Client icon in the system tray.

3.1b Uninstall the pre-installed Cloudflare One Client

Why: The pod ships with a pre-installed client that was registered against a different org. We want to demonstrate the production deployment pattern (MSI with org parameter) so we uninstall first.

Click the Windows Start menu.

Click Settings (the gear icon).

In Settings, click Apps.

Click Installed apps.

In the search bar, type Cloudflare WARP.

Find Cloudflare WARP in the results. Click the three-dot menu next to it.

Click Uninstall.

Confirm the uninstall when prompted.

Wait for the uninstall to complete.

Verify: Search the installed apps list for Cloudflare. No Cloudflare WARP entry should appear. The system tray no longer shows the Cloudflare icon.

3.2 Install Cloudflare One Client via MSI with the organization parameter

Why: Production deployments use the silent MSI flag with the ORGANIZATION parameter so the client knows which org to enroll against without prompting the user. This is the pattern an MDM (SCCM, Group Policy, or Intune) would use to push the client to thousands of devices.

On the Windows desktop, open a web browser.

Download the Cloudflare One Client MSI installer from https://downloads.cloudflareclient.com/v1/download/windows/ga. Save to Downloads.

Right-click the Start menu and select Terminal (Admin) or Windows PowerShell (Admin).

Approve the UAC prompt.

In the admin terminal, navigate to Downloads:

cd C:\Users\cloudflare\Downloads

Check your Downloads folder for the actual MSI filename (the Cloudflare One Client version number changes with each release). Fill it in here so the install command below picks it up:

Fill in here

Run the MSI install with the ORGANIZATION parameter:

msiexec /i "<warp_msi_filename>" /qn ORGANIZATION="<team-name>" SUPPORT_URL="http://support.example.com"

For example, if the downloaded file is Cloudflare_WARP_2026.4.1390.0.msi and your team name is popular-oracle:

msiexec /i "Cloudflare_WARP_2026.4.1390.0.msi" /qn ORGANIZATION="popular-oracle" SUPPORT_URL="http://support.example.com"

The /qn flag makes the install silent. The ORGANIZATION parameter pre-configures the client with your team name so the user is not prompted for it. Wait for the install to complete. The Cloudflare One Client window appears within 30-60 seconds showing "What would you like to use WARP for?" with two options: Private browsing (the consumer 1.1.1.1 product) and Cloudflare One Client (the Zero Trust product).

Select Cloudflare One Client by clicking Continue on the right side.

The next screen says "Enroll your device with Cloudflare One Client" and asks "What is your team name?". The field may already be pre-filled with your team name from the ORGANIZATION parameter in the MSI command. If not, type your team name (e.g., popular-oracle). Click Continue.

Verify: The Cloudflare One Client icon appears in the system tray. A browser tab opens for SAML authentication.

3.3 Authenticate Cloudflare One Client via SAML

Why: The MSI install and team name entry configured which org the client enrolls against. Now the user authenticates via SAML to prove their identity.

After clicking Continue in Step 3.2, a browser tab opens to the Trusted SAML Server login page. The page title shows "Login to continue to Trusted SAML Server | <team-name>."

Sign in with:

Email or Username: itadmin@acmecorp.com

Click Log in.

Password: #Savetheinternet

The browser shows a "Successfully logged in!" notification and redirects to a Cloudflare Access success page with confetti and a Success! heading. You may see a prompt to "Open Cloudflare WARP" ΓÇö click it if it appears.

Switch back to the Cloudflare One Client window. It now shows Disconnected with "Your Internet is not protected." and a Connect button. The left sidebar shows Home, Connectivity, Settings, Profile, and About.

Click the Connect button.

The client connects and shows Connected with "Your Internet is protected." and an orange ring around the Cloudflare cloud icon with a shield checkmark.

Verify: The Cloudflare One Client shows Connected / "Your Internet is protected." Click Settings in the left sidebar to confirm the team name matches yours.

3.4 Confirm the device appears in the dashboard

Why: A connected client must also appear in the Zero Trust dashboard before it can match posture policies or receive Gateway rules.

Return to the Zero Trust dashboard in your browser.

In the sidebar, click Team & Resources.

Click Devices.

Click the Your devices tab.

Find the Windows device in the list. It is identified by the Windows hostname (something like win-desktop).

Verify: The device row shows status Connected, the assigned user is itadmin@acmecorp.com, and the OS is Windows 11.

3.5 Confirm Gateway is processing internet traffic

Why: The whole point of the Cloudflare One Client is to route the device's traffic through Cloudflare Gateway. A simple "what is my IP" check confirms the routing works before any policy gets in the way.

On the Windows desktop, open the browser.

Navigate to https://www.whatismyip.com

The page displays your public IP address.

Verify: The IP address shown belongs to Cloudflare (the ASN reported on the page is Cloudflare or AS13335). If it shows your Azure pod IP instead, Cloudflare One Client is not tunneling correctly. Recheck Step 3.3.

3.6 Override the MDM configuration file

Why: Some Cloudflare One Client features are not yet exposed in the dashboard's device profile UI. The mdm.xml file overrides client behavior locally. The lab uses it to enable one thing the GUI cannot yet: pre_login (a service-token-authenticated device session before any human user logs in to Windows). The pre_login placeholders are filled in during Lab 9a (Step 9a.5) after browser-rendered RDP is set up. The pre-login flow is tested in Lab 9a Steps 9a.3-9a.6.

On the Windows desktop, open PowerShell in admin mode and navigate to:

cd C:\ProgramData\Cloudflare

If a file mdm.xml exists, back it up:

copy mdm.xml mdm.xml.bak

Open Notepad as administrator. Right-click the Start menu, click Run, type notepad, then Ctrl+Shift+Enter to elevate.

In Notepad, open C:\ProgramData\Cloudflare\mdm.xml.

Replace the entire file contents with the following XML. Replace <team-name> (in two places) with your actual team name.

<dict>
  <key>pre_login</key>
  <dict>
    <key>organization</key>
    <string><team-name></string>
    <key>auth_client_id</key>
    <string>placeholder-will-fill-in-step-3.15</string>
    <key>auth_client_secret</key>
    <string>placeholder-will-fill-in-step-3.15</string>
  </dict>
  <key>configs</key>
  <array>
    <dict>
      <key>organization</key>
      <string><team-name></string>
      <key>display_name</key>
      <string>Default</string>
      <key>support_url</key>
      <string>http://support.example.com</string>
      <key>onboarding</key>
      <false/>
      <key>allow_updates</key>
      <false/>
    </dict>
  </array>
</dict>

Save the file: click File > Save As, navigate to C:\ProgramData\Cloudflare, change Save as type to All files, set the file name to mdm.xml, set Encoding to UTF-8, and click Save. If Notepad defaults to "Text Documents (*.txt)", it will save as mdm.xml.txt which the client will not read. Close Notepad.

The Cloudflare One Client detects the new mdm.xml automatically and restarts itself. No manual restart is needed.

Verify: The Cloudflare One Client returns to Connected state within a minute. (No visible feature change yet ΓÇö the pre-login behavior is tested in Lab 9a Steps 9a.3-9a.6, after browser-rendered RDP is set up.)

Lab 4 - Posture, DEX & warp-diag

A device that's connected isn't the same as a device that's healthy. You set up the ways to measure and troubleshoot it: a Firewall posture check that flips to Fail the moment the firewall goes off (you'll weaponize that in Lab 13), four DEX synthetic tests that tell you whether a slow app is the device, the network, the Cloudflare edge, or the destination, three DEX notifications covering fleet connectivity and application latency and availability, and warp-diag collected both ways, remotely from the dashboard and locally on the box for when a user calls support. Estimated time: 30-40 minutes.

4.1 Create device posture checks

Why: Posture checks measure attributes of the enrolled device (OS version, firewall state, disk encryption, domain membership, etc.) and feed those results into Access and Gateway policies. The lab uses one check: Firewall to detect a disabled Windows Firewall.

In the Zero Trust sidebar, click Reusable components to expand it.

Click Posture checks.

Confirm you are on the Cloudflare One Client checks tab (the default).

Click Add a check.

On the Add device posture attribute page, click the Firewall tile.

The "Add Firewall posture check" page opens with three fields:

Name (Required): enter Firewall Check

Operating system: select Windows

ENABLE FIREWALL CHECK: toggle this On

Click Save.

Verify: The Firewall Check appears in the Posture checks list. Navigate to Insights & Logs > Logs > Posture logs. The Firewall Check result shows Pass (the Windows Firewall is on by default).
Polling frequency: By default, the Cloudflare One Client polls posture checks every 5 minutes. Gateway adds its own 5-minute cache on top, so at the default schedule, Gateway policies may take up to 10 minutes to reflect a posture change. The polling frequency can be adjusted via the API using the schedule parameter on the Update device posture rule endpoint (minimum 1m); there is no dashboard UI for this. In production deployments, shorter polling intervals mean faster enforcement but more device overhead.

4.2 Create DEX synthetic tests

Why: DEX (Digital Experience Monitoring) runs synthetic probes from enrolled devices to specified endpoints. The probes generate latency, success rate, and traceroute data that helps you identify whether problems are on the device, the network, the Cloudflare edge, or the destination.

In the Zero Trust sidebar, click Insights & Logs to expand it.

Click Digital experience.

Click the Tests tab at the top of the page.

Click Create a test. The "Add a Test" page opens. Five sections are shown: Basic information, Resource configuration, HTTP request configuration (optional), Advanced settings, and DEX rules. For this lab, only Basic information and Resource configuration need values; leave the rest at defaults.

Create all four tests below. For each one, fill in the Name, Description (optional), Test type, Test frequency, Resource (External target), and Target fields. Test type defaults to HTTP Get and cannot be changed once the test is created. Test frequency defaults to 30 minutes; change it to 5 for every test. When done, click Add test at the bottom right, then click Create a test for the next.

#NameTest typeFrequencyResourceTarget
1Traceroute - InternetTraceroute5 minExternal target1.1.1.1
2Traceroute - Ubuntu ServerTraceroute5 minExternal target10.91.0.X
3HTTP - InternetHTTP Get5 minExternal targethttps://www.cloudflare.com
4HTTP - Ubuntu ServerHTTP Get5 minExternal targethttp://10.91.0.X

Tests 2 and 4 target the Ubuntu Server private IP. They will show errors until Lab 2 builds the tunnel ΓÇö this is expected.

Verify: Four DEX tests appear in the Tests list. The two internet-facing tests begin reporting data. The two Ubuntu Server tests show errors until Lab 2 completes; this is expected.

4.3 Configure DEX notifications

Why: Synthetic tests are useful in isolation but more useful when they alert. DEX notifications fire when a test crosses a threshold (high latency, low availability, connectivity anomaly). DEX notifications are configured at the account level (outside Cloudflare One).

If the Zero Trust sidebar has a Back to <your account> link at the top, click it to return to the account-level dashboard. If the account-level sections already appear above Zero Trust in the same sidebar, stay there.

In the account sidebar, expand Manage account (near the bottom) and click Alerts. If your account instead shows Observability (above Build), expand Observability > Alerts and click All alerts; on the account-level Alerts page, this link may read Overview.

On the Manage account > Alerts page, click Add, filter Product to Digital Experience, and click Select next to the alert type. In the Observability > Alerts layout, click Create an alert, search for DEX tests or Device connectivity, and select the alert type.

Create all three alerts below. For each one, enter the Alert name, leave the alert threshold or configuration at its default, click + Add email recipient under Notification email to add itadmin@acmecorp.com, then click Save. Return to the alerts list and click Add (or Create an alert) for the next one.

#Alert typeAlert name
1DEX tests availability (beta)DEX - Application availability
2DEX tests latency (beta)DEX - Application latency
3Device connectivity (beta)DEX - Fleet connectivity

Some older alert tables label these types Application availability, Application latency, and Fleet connectivity, respectively.

Verify: Three DEX alerts appear in the account-level Alerts or Notifications list, each tied to a different Digital Experience alert type.

When done, navigate back to Cloudflare One via Protect & Connect > Zero Trust in the account sidebar.

4.4 Collect warp-diag from the dashboard (remote collection)

Why: warp-diag is the equivalent of a "show tech-support" for the Cloudflare One Client. It bundles client logs, configuration, network state, and DNS state into a zip. Remote collection means you trigger it from the dashboard without needing the user to do anything.

If you are still on the account-level Alerts or Notifications page from Step 4.3, navigate back to the Zero Trust dashboard: in the account sidebar, click Protect & Connect > Zero Trust.

In the Zero Trust sidebar, click Insights & Logs > Digital experience.

Click the Diagnostics tab at the top of the page.

Click Run diagnostics. The Run diagnostics page opens.

In the Select devices list, check your Windows device. The list shows Device name, User name, Status, User Email, Client version, and Last seen. Note that devices on client versions older than 2024.12.492.0 won't appear here.

Under Capture configuration, Device speed test and Device diagnostic logs are checked by default. Leave both checked; Device diagnostic logs is the one that initiates the warp-diag command remotely (file size up to 100 MB).

Optionally also check Test all routes for broader diagnostics, or Packet captures (PCAP) if you want a pcap as well.

Click Run diagnostics at the bottom right.

Wait for the collection to complete. The diagnostic appears in the Diagnostics list with status changing from In progress to Completed.

Click the completed diagnostic to download the zip file.

Verify: The downloaded zip contains warp-diag output files (warp-cli output, network state, DNS state).

4.5 Collect warp-diag locally

Why: When a device is offline or you cannot reach the dashboard, the same diagnostic is collectible from the device itself. This is the path support most often uses.

On the Windows desktop, open PowerShell in admin mode.

Run:

warp-diag

Wait for the command to finish. It writes a zip file to the current directory and prints the path.

Take note of the file path. You can leave it on the desktop or copy it elsewhere.

Verify: The terminal output reports the zip file location. The zip exists on disk and is non-empty.

4.6 Analyze warp-diag with online tools

Why: The raw zip is several text files. The community-maintained log viewer parses them into something readable.

On your local machine (not the lab VM), open a browser to https://warp-diag-log-viewer.pages.dev/. The visible heading is Warp Diag Log Explorer (the browser tab says Warp Diag Viewer).

Click the Choose File button and select the warp-diag zip you collected in Step 4.4 from your local Downloads folder.

Review the parsed sections: device info, network state, recent log entries, DNS queries, gateway IPs.

Verify: The viewer renders the warp-diag contents in a navigable UI without errors.

Lab 5 - Internal DNS & Profile Lockdown

Internal hostnames like web.cf1pslabs.int don't exist in public DNS, so an enrolled device still can't find AcmeCorp's private destinations. You build a private namespace out of three primitives: an internal DNS zone (cf1pslabs.int) holding web and ssh records for the Ubuntu Server, a view that only enrolled users can see, and a Gateway resolver policy that routes those queries to that view. Then you lock the profile so a user can't disconnect their own client, because every lab after this one assumes it stays on. Estimated time: 25-35 minutes.

5.1 Create an internal DNS zone

Why: Internal DNS (also called private DNS) lets you publish DNS records that resolve only for clients on your overlay network. This is how web.cf1pslabs.int resolves to 10.91.0.X for a Cloudflare One Client user but stays unresolvable from the public internet.

Internal DNS is an account-level feature (not inside Cloudflare One). The dashboard's own info card on the Internal DNS page says it best: three parts work together: zones hold records, views group zones for delivery to specific users, and resolver policies in Gateway send queries to the right view. Two optional pieces show up later for production deployments: a reference zone on a zone (a fallback the resolver tries when a record isn't found, up to five hops deep), and a Fallback through public DNS toggle on the resolver policy (re-runs the query through 1.1.1.1 if the internal lookup returns REFUSED, NXDOMAIN, or a CNAME). The lab doesn't use either, but you'll see both options in the UI.

Return to the account-level dashboard. Click Back to... at the top of the Zero Trust sidebar.

In the account sidebar, expand Networking and click Internal DNS.

The Internal DNS page has two tabs: Internal zones and Internal DNS views. You start on the Internal zones tab.

Create the zone

Click + Create an internal zone.

A dialog appears: "Name your internal DNS zone." Enter cf1pslabs.int and click Save.

The zone detail page opens showing the zone name, Zone ID, and three tabs: DNS Records, Linked views, Reference Zone.

Add the DNS records

Click + Add a record. A side panel opens with a list of record types, alphabetical from the top. A is the first card. Click Create under A.

The form has two sections: Record Configuration (Name, IPv4 address, TTL) and Record Attributes (Comment, Tags; reference only, doesn't affect resolution). Create both records:

#NameIPv4 addressTTL
1web10.91.0.X (your Ubuntu Server IP)Auto
2ssh10.91.0.X (same IP)Auto

For each record: fill in Name and IPv4 address, leave TTL as Auto, and click Save record. Then click + Add a record again for the second one.

Verify: The DNS Records tab shows two A records: web and ssh, both pointing to the Ubuntu Server IP.

5.2 Create a DNS view linked to Cloudflare One Client users

Why: A view is the bridge between a zone and the users who should resolve it. Without a view, the zone exists but no Gateway resolver policy can target it.

Navigate back to the Internal DNS page by clicking Internal DNS in the breadcrumb at the top.

Click the Internal DNS views tab.

Click + Create a view. Enter the name Cloudflare One Client Users and click Save.

The view detail page opens showing the View ID, a Linked zones section (currently empty), and a Resolver policies referencing this view section (currently N/A).

Click Manage linked zones. The "Link zones to Cloudflare One Client Users" page opens showing your available zones.

Check the box next to cf1pslabs.int and click Save.

Verify: Back on the view detail page, the Linked zones section now shows cf1pslabs.int with its Zone ID and "Cloudflare One Client Users" in the Linked views column.

5.3 Create a Gateway resolver policy for the internal DNS zone

Why: The resolver policy tells Cloudflare Gateway which view to consult for queries to cf1pslabs.int. Without it, Gateway falls back to public DNS and the queries fail.

Navigate back to the Zero Trust dashboard: in the account sidebar, click Protect & Connect > Zero Trust.

In the Zero Trust sidebar, expand Traffic controls and click Resolver policies (New).

Click Add a resolver policy. The "Create Resolver policy" page opens with two sections: Scope (left: IF Conditions, right: THEN Resolver) and Details (bottom).

Scope ΓÇö IF Conditions

Under "And traffic matches...":

Selector: Domain, Operator: is, Value: cf1pslabs.int

Scope ΓÇö THEN Resolver

Select Use Internal DNS (Beta).

In the view selector that appears, choose Cloudflare One Client Users.

Leave Fallback through public DNS toggled Off. (On would re-run the query through 1.1.1.1 if cf1pslabs.int comes back REFUSED, NXDOMAIN, or a CNAME. We want a clean miss to surface in the lab.)

Details

Policy name: Internal DNS - cf1pslabs.int

Set policy as active: On

Click Create policy.

Verify: The policy appears in the Resolver policies list, enabled and active.

5.4 Test the lock-down behaviors in the device profile

Why: Production deployments often need to prevent users from disconnecting or switching modes. The profile has two locks. Toggling them and then trying the prohibited action proves the lockdown works.

In the Zero Trust dashboard, navigate to Team & Resources > Devices. Click the Device profiles tab. Under General profiles, click Lab-Profile to open it.

Scroll down to Configure settings and make the following change:

SettingSet toEffect
Lock device client switchOnPrevents users from disconnecting the client
Mode switchConfirm OffPrevents users from switching between Traffic and DNS mode and DNS only mode

Click Save profile.

On the Windows desktop, open the Cloudflare One Client. Try two things:

Attempt to click the Disconnect button.

Attempt to change the mode under Settings.

Verify: Both attempts fail or are visibly disabled. The Disconnect button is greyed out or missing. The mode cannot be changed. Document what you observed.

Unlock the client switch for lab use

Now that you have verified the lockdown works, turn Lock device client switch back to Off. Later lab exercises require you to disconnect and reconnect the client for testing. Leave Mode switch as Off for now (Step 16.4 will unlock it temporarily).

Click Save profile.

Verify: On the Windows desktop, the Cloudflare One Client Disconnect button is now clickable again. Do not disconnect yet; just confirm the button is available.

>Labs 1-5 Wrap-up

By the end of Lab 1 you should have:

Milestone
☐A working SAML IdP integrated with your Zero Trust org
☐Account-wide baseline settings on (Proxy, TLS decryption, protocol detection, egress host selectors, malware scan)
☐Log access confirmed via the Cloudflare One Logs page (8 dataset cards)
☐A device profile configured for production (no leave-org, no auto-updates, auto-connect, split tunnel adjusted)
☐Cloudflare One Client deployed via MSI to the Windows 11 desktop
☐mdm.xml enabling pre-login (placeholders filled in and tested in Lab 3 after browser-rendered RDP is set up)
☐Posture check for Firewall (passing)
☐Four DEX synthetic tests configured (two reporting now, two pending Lab 2)
☐Three DEX notifications configured
☐warp-diag collected both remotely and locally
☐Internal DNS zone cf1pslabs.int with web and ssh records, view linked to Cloudflare One Client users, and a resolver policy

>Labs 1-5 Troubleshooting

SymptomLikely causeFix
SAML test in Step 1.4 fails with "invalid issuer"Metadata file was downloaded for a different team nameRepeat Steps 1.2 and 1.3 with the correct team name
MSI install in Step 3.2 hangsEarlier uninstall did not fully completeReboot the Windows VM. Re-run msiexec.
Cloudflare Cloudflare One Client icon shows Disconnected after sign-inDefault browser had a stale session or the MSI did not pick up ORGANIZATIONRight-click the Cloudflare One Client icon > Re-authenticate. If that fails, uninstall and re-run the MSI
DEX tests in Step 4.2 stay in "No data" foreverTest frequency lower bound varies by entitlementWait 15-20 minutes. If still no data, check that Cloudflare One Client is connected and reporting health metrics
Posture check Firewall Check does not appear in the device's resultsPosture check was scoped to the wrong OSOpen the posture check; verify OS is Windows and platform is All

>Labs 1-5 to Labs 6-7

Lab 2 builds a Cloudflare Tunnel to the Azure Ubuntu Server and sets up the routing that makes the internal DNS records you just created reachable.

Lab 6 - Cloudflare Tunnel & Routing

The private namespace now hands out addresses for destinations nobody can actually reach yet, because AcmeCorp's internal network has no inbound door and Zero Trust says keep it that way. You build the outbound-only path: a Cloudflare Tunnel (Azure-Tunnel) from the Ubuntu Server to Cloudflare's edge, route the lab's private range (10.91.0.0/24) through it, and watch the records from Lab 5 finally resolve to something reachable. You'll also add a tunnel health alert and turn on debug-level cloudflared logging. The origin never opens an inbound hole; the tunnel only opens from the inside. Estimated time: 45-60 minutes.

6.1 Plan the tunnel

Why: Cloudflare Tunnel creates an outbound-only encrypted connection from your infrastructure to Cloudflare's edge. On this pod you have one region (Azure) with one Ubuntu Server.
Tunnel namecloudflared runs onRoutes private CIDR
Azure-TunnelUbuntu origin (10.91.0.X)10.91.0.0/24

Confirm you have console access to the Ubuntu Server before proceeding.

6.2 Create the Azure-Tunnel in the dashboard

Why: Tunnel creation in the dashboard generates a tunnel token (a one-time secret) that cloudflared uses to authenticate against Cloudflare. Each cloudflared instance running this tunnel uses the same token.

In the Zero Trust sidebar, expand Networks and click Tunnels & Mesh.

Click Add a tunnel or the Create a tunnel link at the top of the page.

Click Select Cloudflared. The Name your tunnel step opens.

Tunnel name: Azure-Tunnel. Click Save tunnel.

On the connector install screen, leave the page open. The tunnel token is in the install commands shown on this page.

Click the Linux tab. Under Choose your environment, select Debian. Then under Choose your architecture, select 64-bit. The three install commands appear on screen.

You don't need to copy all three commands manually; Step 6.3 below has them ready to paste with one-click Copy buttons. Just grab the tunnel token from the third command (the long string after --token) using the form below.

Verify: The tunnel appears in the Tunnels list with status Inactive (it is created but no cloudflared instance has connected yet).
Fill in here: your tunnel token

The third install command on the connector install screen ends with --token <long-string>. Copy just that long string and paste it below. The debug-logging step (Step 6.9) will substitute it into a configuration file automatically.

6.3 Install cloudflared on the Azure Ubuntu Server

Why: Running cloudflared on the Ubuntu Server with the Azure-Tunnel token establishes the outbound-only tunnel from that machine to the Cloudflare edge. From here on, anything Cloudflare needs to reach inside the Azure region travels through this connection.

From the Lab Portal, open the Ubuntu Server console (Azure region).

If prompted, log in with cloudflare / #Savetheinternet.

Open a terminal. (Most lab consoles open one automatically.)

Run these three commands one at a time. Click Copy on each block, paste into the terminal, press Enter, and wait for each one to finish before moving to the next. Running all three pasted together will break the install; dpkg would run before the .deb finishes downloading.

Download the cloudflared package:

curl -L --output cloudflared.deb https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb

Install the package:

sudo dpkg -i cloudflared.deb

Register cloudflared as a system service with your tunnel token. The token below auto-fills from the form you completed in Step 6.2:

sudo cloudflared service install <your-token>
Why: Installing cloudflared as a system service (rather than running it in the foreground) registers it with systemd, so the tunnel starts automatically on boot and keeps running after you close the SSH session. This is how a connector runs in production.

Verify the service is running:

sudo systemctl status cloudflared

The output should show Active: active (running).

Before the ICMP ping test in Step 6.5, check the cloudflared binary's raw-socket capability on the Ubuntu Server. The command resolves any symlink first. For example, /usr/local/bin/cloudflared may point to the real binary at /usr/bin/cloudflared:

getcap "$(readlink -f "$(command -v cloudflared)")"

If the command prints nothing, grant cap_net_raw to the real binary and restart the service so the running process picks it up. Applying setcap to the symlink instead returns Invalid file:

sudo setcap cap_net_raw+ep "$(readlink -f "$(command -v cloudflared)")"
sudo systemctl restart cloudflared

Run the capability check again using the copy block below. Expect a line such as /usr/bin/cloudflared cap_net_raw=ep; the path may differ. If getcap or setcap is unavailable, install libcap2-bin first with sudo apt-get install -y libcap2-bin. Recheck after replacing or upgrading the binary because file capabilities can be lost. This host-side check complements the Gateway ICMP setting you verified in Lab 1.

getcap "$(readlink -f "$(command -v cloudflared)")"
Verify: Return to the dashboard tab. The Azure-Tunnel now shows status Active with one connector. Refresh the page if it still says Inactive.

6.4 Add the private network route

Why: Tunnel creation establishes the connection from cloudflared to Cloudflare. The network route tells Cloudflare which destination IP ranges to send through the tunnel.

In the Zero Trust dashboard, expand Networks and click Routes. On the CIDR tab, click Add CIDR route. Configure:

FieldValue
CIDR10.91.0.0/24
TunnelSelect Azure-Tunnel from the dropdown
DescriptionAzure region private network
Virtual networkExpand Additional settings and select default

Click Create.

Verify: The Routes list shows one entry mapping 10.91.0.0/24 to Azure-Tunnel.

6.5 Test connectivity from the Windows 11 client to the Ubuntu Server

Why: Verify that IP-level reachability works before adding complexity.

On the Windows 11 desktop (signed in as cloudflare). Confirm Cloudflare One Client status is Connected.

Open PowerShell (does not need to be Administrator).

Continuous ping to the Ubuntu Server. If it does not work immediately after adding the private route, allow up to 10 minutes for pings to start working before troubleshooting. Press Ctrl+C to stop when you have several successful replies:

ping -t 10.91.0.X

(Replace 10.91.0.X with the published Ubuntu Server IP from your lab portal if different.)

Verify: Pings succeed. If they fail, check:
  • Azure-Tunnel shows Healthy in the dashboard.
  • The route 10.91.0.0/24 -> Azure-Tunnel exists.
  • 10.91.0.0/24 is NOT in the split-tunnel Exclude list.
  • In Traffic controls > Traffic settings > Proxy and inspection settings, "Allow Secure Web Gateway to proxy traffic" has ICMP checked under Select protocols to forward. Without ICMP forwarding, ping can return "Destination host unreachable" from a Cloudflare IP even while HTTP and SSH work.
  • On the Ubuntu Server, getcap "$(readlink -f "$(command -v cloudflared)")" shows cap_net_raw=ep. If it does not, follow the capability and service-restart steps in Step 6.3.
  • On the Ubuntu Server, net.ipv4.ping_group_range includes the group of the user running cloudflared. If the checks above pass but ping still fails, test the lab workaround below.

If ping still fails after those checks, on the Ubuntu Server inspect the current allowed ICMP ping group range:

sysctl net.ipv4.ping_group_range

For this lab, temporarily allow all group IDs to create ICMP ping sockets, then restart cloudflared so the running service picks up the change:

sudo sysctl -w net.ipv4.ping_group_range="0 2147483647" && sudo systemctl restart cloudflared

Retry ping -t 10.91.0.X from the Windows 11 client, using your Ubuntu Server IP. Only if it now works, make the Ubuntu Server setting survive a reboot:

printf 'net.ipv4.ping_group_range = 0 2147483647\n' | sudo tee /etc/sysctl.d/99-cloudflared-icmp.conf
Production note: The range above allows all groups to open ping sockets for this lab. On a production connector, use the narrower range that includes the group ID of the user running cloudflared.

6.6 Use IPv4 on this lab's Windows client

Why: This pod uses an IPv4 private route to the Ubuntu Server. For this exercise, disabling IPv6 binding on the Windows 11 adapter keeps the internal DNS test on IPv4. Earlier lab testing saw nslookup errors or timeouts with IPv6 enabled, but that observation does not establish that the Cloudflare One Client generally fails to handle IPv6 DNS.
Lab versus production:
  • In this lab: Disable IPv6 on this VM's adapter as instructed below to keep the exercise on the tested IPv4 path. This is a lab workaround, not a production requirement.
  • In production: Keep IPv6 enabled where it is part of your network design. If private DNS fails, check the client mode, DNS resolver path, and Split Tunnels before changing adapter bindings.
  • Documented Windows caveats: In DNS only mode, nslookup may fail when it uses the client's IPv4-mapped IPv6 local DNS proxy address; try Resolve-DnsName or specify the IPv4 proxy address 127.0.2.2. In Traffic only mode, IPv6 DNS servers may need Split Tunnel exclusions. Neither caveat establishes a blanket IPv6 failure in the Traffic and DNS mode used here. See Cloudflare One Client known limitations.

On the Windows 11 desktop, open Control Panel > Network and Internet > Network and Sharing Center.

Click Change adapter settings in the left sidebar.

Right-click your active network adapter (typically Ethernet) and select Properties.

Uncheck Internet Protocol Version 6 (TCP/IPv6).

Click OK.

Verify: IPv6 is unchecked in the adapter properties. You do not need to restart the machine.

6.7 Test FQDN-based connectivity using internal DNS

Why: Step 6.5 verified IP-level connectivity. Now verify that internal DNS hostnames resolve correctly.

On the Windows 11 desktop (signed in as cloudflare). Confirm Cloudflare One Client is Connected.

Open PowerShell.

Resolve the internal DNS records:

nslookup ssh.cf1pslabs.int
nslookup web.cf1pslabs.int

Each should return the Ubuntu Server IP (10.91.0.X or as published). 4. Test reachability:

ping ssh.cf1pslabs.int
ping web.cf1pslabs.int
Verify: Both pings succeed. If nslookup fails, revisit Steps 5.1-5.3.

6.8 Configure a tunnel health alert

Why: When both connectors of a tunnel fail simultaneously (the tunnel goes fully down), you want to know immediately. The tunnel health alert fires an email notification when this happens. Notifications are configured at the account level (outside Cloudflare One).

Return to the account-level dashboard. Click Back to... at the top of the Zero Trust sidebar to leave the Zero Trust scope.

In the account sidebar, expand Manage account and click Alerts. The Alerts page has Alerts and Destinations tabs. Click Add.

Page 1 of 2: pick the alert type

The Create an Alert page lists alert types by product. Choose Tunnel in the Product dropdown. The table filters to two rows: Tunnel Creation or Deletion Event and Tunnel Health Alert.

Click Select on the Tunnel Health Alert row.

Page 2 of 2 (first half): name and recipient

Fill in:

FieldValue
Alert nameAzure-Tunnel Health Alert
Description (optional)Leave blank, or add a one-liner like Alerts when Azure-Tunnel goes down
Notification emailClick + Add email recipient and enter an address you can check

Click Next.

Page 2 of 2 (second half): scope and trigger

Under Add tunnel(s), type Azure-Tunnel in the search field and click Search. Tick the checkbox next to the matching row. The footer should read 1 tunnels selected.

Leave Include future tunnels off. (Toggle it on if you want the alert to also fire for any tunnel created in this account later; useful in production, not for the lab.)

Under Health status trigger, select Becomes down. The other four radio options (Becomes either healthy/degraded/down, Becomes either degraded or down, Becomes degraded, Becomes healthy) are for different operational pages; "Becomes down" matches the production scenario this lab simulates.

Click Create.

Verify: The alert appears in the Alerts list with name Azure-Tunnel Health Alert.

When done, navigate back to Cloudflare One via Protect & Connect > Zero Trust in the account sidebar.

6.9 Enable debug logging on cloudflared

Why: Default cloudflared logging is at the info level, which captures normal operation but not the protocol-level detail you sometimes need when debugging connection issues. Setting log level to debug writes every TLS handshake, every QUIC frame, and every routing decision to the cloudflared log.

From the Lab Portal, open the Ubuntu Server console (Azure region, where Azure-Tunnel runs).

Open a terminal as the cloudflare user.

The lab pod installs cloudflared as a systemd service. To enable debug logging, you need to edit the service unit file directly (not a config.yml). Run:

sudo systemctl edit cloudflared --full

This opens the full service unit file in nano. Make two changes:

Find the Type= line under [Service]. Change it to:

Type=simple
Why: The service install in Step 6.3 created the unit with Type=notify, which expects cloudflared to signal systemd when it is ready. Running it in the foreground with an explicit tunnel run line does not send that readiness signal, so systemd would time out and mark the service failed. Type=simple tells systemd to treat the process as running as soon as it starts.

Find the ExecStart= line. Add --loglevel debug between --no-autoupdate and tunnel run. The line should look like:

ExecStart=/usr/bin/cloudflared --no-autoupdate --loglevel debug tunnel run --token <your-token>

Save the file (Ctrl+O, Enter, Ctrl+X).

Reload the systemd daemon and restart cloudflared:

sudo systemctl daemon-reload
sudo systemctl restart cloudflared

Verify the new log level is active by following the logs in real time:

sudo journalctl -u cloudflared -f

You should see verbose DEBUG-level log entries. Press Ctrl+C to stop following.

Verify: The cloudflared logs contain DEBUG entries in addition to INFO. The tunnel in the dashboard still shows Healthy.

>Labs 6-7 Wrap-up

By the end of Lab 2 you should have:

Milestone
☐One Cloudflare Tunnel created and active: Azure-Tunnel (one connector on Ubuntu Server)
☐One private network route: 10.91.0.0/24 → Azure-Tunnel
☐Ubuntu Server reachable by IP from the Windows 11 client
☐FQDN resolution working for cf1pslabs.int (internal DNS)
☐Tunnel health alert notification configured for Azure-Tunnel
☐Debug-level cloudflared logging on the Azure tunnel
☐A DNS location sending the Ubuntu Server's queries through Gateway, ready for DNS blocking policies in Lab 14

Lab 7 - DNS Location for Server-Side Filtering

Not everything at AcmeCorp can run the client. Servers, printers, and IoT gear have no user to sign in, but you still want their DNS queries to reach Gateway. You give the Ubuntu Server its own DNS over HTTPS endpoint (a Gateway DNS location named Ubuntu Server), point a dnsproxy instance on the server at it, then confirm its queries appear in the DNS query log. DNS blocking policies come later in Lab 14. Estimated time: 20-30 minutes.

7.1 Create a DNS location for the Ubuntu Server

Why: DNS locations let you apply Gateway DNS policies to networks that don't run the Cloudflare One Client. By pointing the Ubuntu Server's DNS resolver at a Gateway DNS location endpoint, you can filter DNS queries from the server itself.

a. Create the DNS location in the dashboard

In the Zero Trust sidebar, expand Traffic controls and click Resolvers & Proxies.

Click the DNS locations tab.

Click Add a location. On the Add DNS endpoints step, configure:

FieldValue
Location nameUbuntu Server
DNS over HTTPS (DoH)On

Click Continue. On Protect your endpoints, leave DoH endpoint filtering & authentication Off and click Continue again. Cloudflare briefly shows Review setup details, then Confirmation. This creates the location; there is no separate Save button. If this is your first DNS location, it becomes the default automatically.

On the Confirmation screen, note the DNS over HTTPS (DoH) endpoint URL (looks like https://<unique-id>.cloudflare-gateway.com/dns-query). The <unique-id> piece is what you'll fill in below.

Fill in here: your DoH endpoint ID

From the URL Cloudflare just showed (https://<unique-id>.cloudflare-gateway.com/dns-query), paste only the leading slug below.

b. Point the Ubuntu Server at the DoH endpoint

Note: cloudflared proxy-dns was removed in cloudflared 2026.2.0. Use dnsproxy (a lightweight Go DNS forwarder) instead.

SSH into the Ubuntu Server (cloudflare / #Savetheinternet).

Download and install dnsproxy:

curl -sL https://github.com/AdguardTeam/dnsproxy/releases/download/v0.81.4/dnsproxy-linux-amd64-v0.81.4.tar.gz | sudo tar xzf - -C /usr/local/bin/ ./linux-amd64/dnsproxy --strip-components=2 && sudo chmod +x /usr/local/bin/dnsproxy

Start dnsproxy on port 5053, forwarding to your Gateway DoH endpoint:

sudo /usr/local/bin/dnsproxy -l 127.0.0.1 -p 5053 -u https://<unique-id>.cloudflare-gateway.com/dns-query &

(Replace the URL with your DoH endpoint from the dashboard. Port 5053 avoids conflicting with the Docker DNS container on port 53.)

c. Test a DNS query through the proxy

dig @127.0.0.1 -p 5053 malware.testcategory.com

You have not created Gateway DNS blocking policies yet. Those come in Step 14.1. This query may resolve normally, and that is expected; a block is not required in Lab 7. After you create and enable a DNS policy that blocks the Malware security category, you can repeat this query to test the block.

Verify: Check Insights & Logs > Logs > DNS query logs. Click + Add filter, select DNS location, and filter by Ubuntu Server. Find the server's query for malware.testcategory.com. Its presence shows that the DNS location sends queries to Gateway without the Cloudflare One Client. A blocked response is not required until you add a matching DNS policy in Lab 14.

>Labs 6-7 Troubleshooting

SymptomLikely causeFix
sudo cloudflared service install ... returns "invalid token"The token has expired (tokens are usually valid for ~30 days but lab tokens can expire faster)Return to Networks > Connectors, click the tunnel, click Configure, get a fresh token
ping 10.91.0.X fails after Azure-Tunnel is healthyRoute is wrong, split tunnel excludes the destination, Gateway is not forwarding ICMP, or the cloudflared host lacks raw-socket capability or an allowed ping groupCheck the route and split-tunnel Exclude list; confirm ICMP is checked in Traffic controls > Traffic settings > Proxy and inspection settings; on the Ubuntu Server, check getcap "$(readlink -f "$(command -v cloudflared)")" and apply the capability fix in Step 6.3 if missing. If ping still fails, test ping_group_range in Step 6.5.
nslookup web.cf1pslabs.int returns NXDOMAINInternal DNS zone, view, or resolver policy is misconfiguredRe-check Steps 5.1-5.3

>Labs 6-7 to Labs 8-13

Lab 3 publishes self-hosted applications (HTTPS and browser-rendered SSH) through Access apps, then layers on private-network ZTNA policies with a posture failure exercise. The Azure-Tunnel and internal DNS you just built are the routing fabric for everything Lab 3 publishes.

Lab 8 - Self-Hosted Web & SSH Apps

The tunnel can carry traffic, but there's still nothing deciding who's allowed down it. You put the first access decisions in place: a reusable Allow Lab Users policy (usera and itadmin), plus a Ubuntu Web app and a Ubuntu SSH app both published through Azure-Tunnel with SAML in front of each. Browser-rendered SSH means a user doesn't even need an SSH client, just a browser. You test all three identities end to end, including userb, who gets turned away because she's not in the policy. Estimated time: 45-60 minutes.

8.1 Create a baseline Access policy for lab users

Why: All the Access apps in this lab share the same policy. Creating a reusable policy now lets you attach it to each app without re-defining the logic. The policy must include both usera@acmecorp.com (the primary test user) and itadmin@acmecorp.com (the account you enrolled the Cloudflare One Client with in Step 3.3). Without itadmin, Cloudflare One Client-enrolled traffic to private-network Access apps will be denied.

In the Zero Trust sidebar, click Access controls.

Click Access policies (or Policies depending on dashboard build).

Click + Add a policy.

Configure:

FieldValue
Policy nameAllow Lab Users
ActionAllow
Session duration24 hours (default)
Include rule: SelectorEmails
Include rule: Valueadd each address below as its own entry

Include rule — Value. Add each address as its own entry: click Copy, paste into the Value field, press Enter, then add the next.

usera@acmecorp.com
itadmin@acmecorp.com

Save the policy.

Verify: The policy appears in the Access policies list with the name Allow Lab Users, action Allow, and includes both email addresses.

8.2 Configure an Access HTTPS app for the Ubuntu Server web service

Why: This is the most common Access pattern: take an internal HTTP application and publish it to the internet behind Cloudflare authentication. Users get a public hostname they can reach from anywhere; Cloudflare authenticates them before passing traffic to the origin.

Navigate to Access controls > Applications > + Add an application > Self-hosted > Select.

Configure the application using the values below:

FieldValue
Application nameUbuntu Web
Session duration24 hours
Application domainSubdomain: web, Zone: your assigned zone. Full FQDN: web.<your-domain>
Identity providersSAML only (uncheck all others)

Click Next. On the Policies page, click Select existing policies, check Allow Lab Users (from Step 8.1), and click Confirm.

Click Next. On Additional settings, leave defaults. Click Next.

On the Connect a server step (some dashboard builds show this; others skip directly to save), leave as-is; the tunnel side gets configured in Step 8.3.

Click Add application or Save.

Verify: Ubuntu Web appears in the Applications list with the FQDN web.<your-domain> and policy Allow Lab Users attached.

8.3 Publish the Ubuntu Server web service through Azure-Tunnel

Why: Creating the Access app published the hostname but did not connect it to the origin. The tunnel side needs a published application route that maps web.<your-domain> to http://10.91.0.X:80 (or whichever port the Ubuntu Server's web service is on).

In the Zero Trust dashboard, expand Networks and click Tunnels & Mesh.

Click Azure-Tunnel to open its detail page.

Click the Published application routes tab.

Click + Add a published application route. Configure:

FieldValue
Subdomainweb
DomainSelect <your-domain> from the dropdown
Path(leave blank)
Service typeHTTP
URL10.91.0.X:80 (check with curl http://10.91.0.X from the Ubuntu Server console to find the listening port)

Click Save hostname.

Verify: The published route appears in Azure-Tunnel's route list mapping web.<your-domain> to http://10.91.0.X:80

8.4 Test the HTTPS Access app

Why: Confirm that the entire end-to-end path works: browser to web.<your-domain> → Cloudflare → Access prompts for SAML → SAML authenticates → Cloudflare proxies to 10.91.0.X:80 through the Azure-Tunnel.

On the Windows 11 desktop (or any browser, since this is testing the non-Cloudflare One Client user experience), open an InPrivate or incognito browser window.

Navigate to https://web.<your-domain>

Cloudflare's Access challenge page loads. Click your SAML provider.

The shared lab IdP sign-in page loads. Authenticate with:

Username: usera@acmecorp.com

Password: #Savetheinternet

After successful auth, the browser is redirected back to web.<your-domain> and the Ubuntu Server's web page renders.

Verify: You see the AcmeCorp website (or whichever web content the Ubuntu Server is serving). The browser address bar shows your domain with a green/secure lock icon. Repeat the test with itadmin@acmecorp.com: open another InPrivate window, navigate to the same URL, authenticate as itadmin. It should also succeed (itadmin is in the Allow Lab Users policy). Then try userb@acmecorp.com; this user should be blocked with a Cloudflare Access denial page.

8.5 Configure an Access browser-rendered SSH app

Why: Browser-rendered SSH lets users SSH into a server without needing an SSH client installed. Cloudflare renders the SSH session in a Browser Isolation tab. The user authenticates once via SAML; the SSH session opens immediately after.
Same wizard as Step 8.2: Access controls > Applications > + Add an application > Self-hosted.
FieldValue
Application nameUbuntu SSH
Session duration24 hours
Application domainSubdomain: ssh. Full FQDN: ssh.<your-domain>
Browser renderingSSH (switches the app from a normal HTTPS proxy to a browser-rendered terminal session)
Identity providersSAML only
PolicyAllow Lab Users (from Step 8.1)

Click Next through each wizard page, then Add application.

Verify: Ubuntu SSH appears in the Applications list with FQDN ssh.<your-domain> and browser rendering set to SSH.

8.6 Publish SSH through Azure-Tunnel

Why: Browser-rendered SSH lets users SSH into a server without an SSH client. This step publishes the SSH service through the same Azure-Tunnel that serves the web app.

In the Zero Trust dashboard, click Networks > Connectors and open Azure-Tunnel.

Click the Published application routes tab.

Click + Add a published application route. Configure:

FieldValue
Subdomainssh
DomainYour assigned zone
Path(leave blank)
Service typeSSH
URL10.91.0.X:22

Save the hostname.

Verify: Azure-Tunnel now lists ssh.<your-domain> in its published routes mapped to ssh://10.91.0.X:22.

8.7 Test browser-rendered SSH

Why: Confirm the full flow: browser to ssh.<your-domain> → SAML auth → browser-rendered terminal opens → local credentials authenticate against the Ubuntu Server's SSH daemon.

On the Windows 11 desktop, open an InPrivate browser window.

Navigate to https://ssh.<your-domain>

Authenticate via SAML as usera@acmecorp.com / #Savetheinternet.

After SAML auth, a browser-rendered terminal page loads. The terminal prompts for the SSH username.

Enter SSH credentials:

FieldValue
Usernamecloudflare
Password#Savetheinternet

The remote shell prompt appears (cloudflare@ubuntu-server:~$ or similar).

Run a few commands to confirm the session is live: whoami, hostname, cat /etc/os-release.

Verify: Commands execute and return output in the browser-rendered terminal. The session is fully interactive (you can type more commands).

8.8 Confirm the Access decisions in the logs

Why: Testing from the browser shows you the user's experience, but the authoritative record of who was allowed and who was turned away lives in the Access authentication logs. Access decisions are recorded in the Decision column (Allowed / Blocked), which is separate from the Gateway Action column you used in the DNS, Network, and HTTP labs. Reading the log now builds the habit of confirming every policy decision against the record instead of trusting the browser alone.

In the Zero Trust dashboard, click Insights & Logs > Logs > Access authentication logs.

Click + Add filter, set Field Application, Operator equals, and pick Ubuntu Web from the Value list. (The Operator only offers equals / does not equal, so there is no substring match.)

Read the Decision column and the User email column for the login attempts you made in Step 8.4.

Verify: The Access authentication logs show Decision = Allowed for usera@acmecorp.com and itadmin@acmecorp.com, and Decision = Blocked for userb@acmecorp.com, all against the Ubuntu Web application.

Lab 9a - Browser-Rendered RDP & Pre-Login

Lab 9 comes in two halves. This first half stays on the Windows desktop and covers two access patterns for managed Windows machines. First, browser-rendered RDP: users reach the Windows desktop through a browser with no RDP client installed. Second, pre-login: a service-token machine identity, dropped into the mdm.xml you seeded in Lab 1, that lets the device authenticate at the lock screen before any human signs in. You publish the RDP Workstation app over its own tunnel, then create the service token, allow it in the enrollment policy, and wire it into mdm.xml. Lab 9b then moves to the Ubuntu Server for the multi-user SSH exercise. Estimated time: 45-60 minutes.

9a.1 Set up Browser-Rendered RDP via the Get Started wizard

Why: Browser-rendered RDP lets users connect to a Windows desktop via their browser without an RDP client. The Get Started wizard is the only working dashboard path for browser-rendered RDP; the manual "Add an application" flow creates the wrong app type. This step installs cloudflared on the Windows client to create an rdp-tunnel, then publishes the Windows desktop as an RDP app.

9a.1a. Capture the Windows client LAN IP

On the Windows 11 desktop, open PowerShell and run:

ipconfig

Note the IPv4 Address of the active Ethernet adapter (e.g., 10.90.0.51) and paste it below.

Fill in here: Windows desktop LAN IP

9a.1b. Open the wizard

In the Zero Trust sidebar, click Overview.

Click the Get started tab at the top.

On the Set up secure access to private apps from any browser card, click Get started (or View details if the wizard was previously started).

Click Continue next to Enable in-browser remote desktop sessions to Windows hosts.

9a.1c. Define your application

FieldValue
Name your applicationRDP Workstation
Local IP address of your RDP target<your-lan-ip>
RDP port3389

Click Continue.

9a.1d. Select a public domain

FieldValue
Select domain<your-domain>
Enter a subdomainrdp

Click Continue.

9a.1e. Add your first Access policy

The wizard creates a one-time-pin (OTP) policy by default. Instead of using OTP, attach the existing Allow Lab Users policy from Step 8.1. If the wizard does not allow selecting an existing policy, type usera@acmecorp.com and itadmin@acmecorp.com in the approved emails field (the two members of the Allow Lab Users policy).

Click Continue.

9a.1f. Assign a Tunnel

Click the Choose or create a Tunnel dropdown and type rdp-tunnel.

Click the Create "rdp-tunnel" option that appears.

Click Continue.

9a.1g. Deploy the Tunnel on the Windows client

Select Windows and 64-bit.

On the Windows 11 client, click the download the cloudflared service link. Depending on the download offered, you may get cloudflared-windows-amd64.msi or cloudflared-windows-amd64.exe. Follow the matching option:

In the dashboard tab, click the copy icon on the Your Tunnel command block.

Back in the elevated PowerShell window on the Windows 11 client, paste the command you just copied from the dashboard's Your Tunnel command block (the cloudflared.exe service install ... line that contains the tunnel token) and press Enter. For the .exe option, use the same PowerShell window where you ran the setup command above.

Switch back to the dashboard. Wait for rdp-tunnel has been deployed (green checkmark).

Click Continue, then close the success page.

Verify:
WhereWhat you should see
Access controls > ApplicationsRDP Workstation, type RDP, URL rdp.<your-domain>
Networks > Connectorsrdp-tunnel, Status HEALTHY
Networks > Routes > CIDR<your-lan-ip>/32 on rdp-tunnel

9a.2 Test the browser-rendered RDP session

Why: Confirm the full flow: browser → SAML auth → RDP session renders inside the browser → Windows desktop is usable.

Open an InPrivate browser window.

Open your https://<team-name>.cloudflareaccess.com App Launcher. If it is not enabled yet, use the enable and policy instructions in Step 12.1 now, allowing usera@acmecorp.com, then return here. Select the RDP Workstation tile for the Windows 11 client target at the LAN IP you recorded in Step 9a.1, not the bare rdp.<your-domain> application address.

The target tile opens a URL that includes /rdp/<vnet-id>/<target-ip>/3389. If you see Unable to find your RDP target!, go back to the App Launcher and click the Windows target tile. The bare application address does not specify which Windows machine to connect to.

Authenticate via SAML as usera@acmecorp.com / #Savetheinternet.

At the Windows credential prompt, enter: cloudflare / #Savetheinternet.

The Windows desktop renders inside the browser.

Verify: You can see the Windows desktop, move the mouse, and open applications inside the browser-rendered RDP session.

9a.3 Create a service token for pre-login

Why: Pre-login authentication happens before a Windows user logs in. A service token is a machine identity (client ID + secret) that authenticates the device so Cloudflare One Client can establish a connected session before any human user signs in.

In the Zero Trust sidebar, click Access controls.

Click Service credentials.

Confirm you are on the Service Tokens tab.

Click Add a service token.

FieldValue
Service token nameCloudflare One Client Pre-Login
Service Token DurationNon-expiring (or the longest available)

Click Generate token.

Cloudflare displays the Client ID and Client Secret exactly once. Copy both to a safe place immediately. You will not see the secret again.

Verify: The service token appears in the Service Tokens list. Both Client ID and Client Secret are captured outside the dashboard.

9a.4 Add enrollment policy for the service token

Why: The default enrollment policy only allows SAML logins (Step 2.6). For pre-login to work, enrollment must also accept the service token.

Navigate to Team & Resources > Devices.

Click the Management tab.

Click the Device enrollment filter button, then click Manage.

Click Add a rule.

FieldValue
NamePre-Login Service Auth
ActionService Auth
Rule typeInclude
SelectorService Token
ValueCloudflare One Client Pre-Login (pick the service token you created in Step 9a.3 from the dropdown)

Save the rule.

Verify: The enrollment permissions list now has two rules: the SAML rule from Step 2.6 and the new Service Auth rule.

9a.5 Update mdm.xml with service token credentials

Why: The placeholder values in mdm.xml (Step 3.6) need real values. The Cloudflare One Client reads the pre-login section at Windows boot and uses the service token to authenticate the device.

On the Windows desktop (signed in as cloudflare), open Notepad as administrator.

Open C:\ProgramData\Cloudflare\mdm.xml.

Locate the <key>auth_client_id</key> line. Replace the placeholder string with your service token's Client ID.

Locate the <key>auth_client_secret</key> line. Replace the placeholder string with your service token's Client Secret.

Save the file. Close Notepad.

Verify: Re-open the file. Both auth_client_id and auth_client_secret contain real values (not the placeholder text).

9a.6 Understand the pre-login flow

Why: With the service token credentials in mdm.xml and the enrollment policy accepting Service Auth, the Cloudflare One Client can authenticate the device at Windows boot before any human user logs in. In production, this enables the machine to reach domain controllers and other resources over Zero Trust before the user signs in.
Lab environment limitation: Forcing a pre-login cycle requires signing out all Windows users and waiting at the lock screen. In the lab VM console, this can break the browser session. Do not force a pre-login cycle in this lab. This step explains how pre-login works and where to verify it in the dashboard.

In a production deployment, pre-login works as follows:

All Windows users sign out. The device reaches the lock screen.

The Cloudflare One Client reads the service token from mdm.xml and authenticates automatically.

In the dashboard at Team & Resources > Devices > Your devices, the device appears with the user non_identity@<team-name>.cloudflareaccess.com: the pre-login machine identity.

When a human user signs in to Windows, the client re-enrolls under that user's identity (e.g., usera@acmecorp.com).

The dashboard then shows both the pre-login entry and the human user entry for the same device.

Verify: You understand that pre-login creates a non_identity@ device entry visible at Team & Resources > Devices > Your devices, and that the mdm.xml file now contains real service token credentials.

Lab 9b - Multi-User SSH on the Ubuntu Server

The second half of Lab 9 moves off the Windows desktop and onto the Ubuntu Server. A shared server in the real world serves more than one person, and Zero Trust has to attribute every session to the identity that opened it. You create two local Linux accounts on the Ubuntu Server, then reach the single browser-rendered SSH app you published in Lab 8 twice, once as each identity, and confirm both sessions land on the one server as separate users. Finally you read the Access authentication logs and see both identities as distinct Allowed sessions against the same resource. Estimated time: 20-30 minutes.

9b.1 Create local users on the Ubuntu Server

Why: A shared resource in the real world serves more than one person. To show per-user identity on one machine, you create two local Linux accounts on the Ubuntu Server, usera and itadmin, matching the usera@acmecorp.com and itadmin@acmecorp.com identities in the Allow Lab Users policy. In the next steps each identity logs in through the single browser-rendered SSH app you published in Lab 8, and Cloudflare attributes each session to the identity that authenticated.
Legacy lab note: These accounts are not pre-provisioned on the lab pod. You create them yourself on the Ubuntu Server. The Windows desktop is not used for this exercise.

Step 9b.1a: Open a shell on the Ubuntu Server. From the Lab Portal, open the Ubuntu Server console (Azure region) and log in as cloudflare / #Savetheinternet.

Step 9b.1b: Create both users with the standard lab password. Paste the following block. It creates usera and itadmin, each with a home directory and the standard password #Savetheinternet, then prints both accounts so you can confirm they exist:

sudo useradd -m -s /bin/bash usera; echo 'usera:#Savetheinternet' | sudo chpasswd; sudo useradd -m -s /bin/bash itadmin; echo 'itadmin:#Savetheinternet' | sudo chpasswd; getent passwd usera itadmin

You should see one line each for usera and itadmin in the final command's output.

Verify: The final getent command prints one line each for usera and itadmin, showing their home directories (/home/usera and /home/itadmin).

9b.2 Log in as each user through the SSH app

Why: One published SSH resource can serve many users. You reach the same browser-rendered SSH app from Lab 8 twice, as two different identities, and land in two different Linux accounts on the one Ubuntu Server. This is the multi-user pattern: shared infrastructure, separate identities, each one attributable.

Session 1 - usera. Open an InPrivate browser window and navigate to https://ssh.<your-domain>. Authenticate via SAML as usera@acmecorp.com / #Savetheinternet. At the browser-rendered SSH login prompt, sign in as the local user usera / #Savetheinternet. Run whoami; it returns usera.

Session 2 - itadmin. Open a second InPrivate browser window and navigate to the same https://ssh.<your-domain>. Authenticate via SAML as itadmin@acmecorp.com / #Savetheinternet. At the SSH login prompt, sign in as the local user itadmin / #Savetheinternet. Run whoami; it returns itadmin.

Note: The SAML identity (usera@acmecorp.com or itadmin@acmecorp.com) is who Cloudflare authenticates and records in the logs. The local Linux user you then type at the SSH prompt is the account you become on the server. Both identities are in the Allow Lab Users policy, so both reach the shell; userb@acmecorp.com is not, which is the block test you ran in Step 8.7. Access only decides whether you reach the terminal here; it does not bind a SAML identity to a specific Linux account, so an authorized user could type any local username at the prompt. Enforced identity-to-principal mapping with short-lived SSH certificates is covered in Step 13.10 (Access for Infrastructure).
Verify: Two independent SSH sessions to the same Ubuntu Server, one running as usera and one as itadmin, each reached through the single browser-rendered SSH app after authenticating as a different identity.

9b.3 See multiple users in the logs

Why: Every session is tied to the identity that opened it. This is how an operator answers "who reached this server, and when" for a shared resource.

In the Zero Trust dashboard, navigate to Insights & Logs > Logs > Access authentication logs.

Click + Add filter, set Field Application, Operator equals, and pick Ubuntu SSH from the Value list.

Read the Decision and User email columns for the two logins you made in Step 9b.2. You see one row for usera@acmecorp.com and one for itadmin@acmecorp.com, each with Decision = Allowed and its own timestamp.

Verify: The Access authentication logs show both usera@acmecorp.com and itadmin@acmecorp.com as separate Allowed sessions against the Ubuntu SSH application. Multiple users, one shared resource, each one attributed.

Lab 10 - Intranet App Through Azure-Tunnel

Companies never have just one internal app: there's a website, an intranet, an HR portal, a wiki. The production pattern is one tunnel serving many. You add the intranet as a second service on the same Azure-Tunnel from Lab 6, pointing the intranet hostname at port 8000, gate it behind the same Allow Lab Users policy, and confirm it renders as a visibly different site from the public website, because nginx is serving two separate sites off one server. Estimated time: 25-35 minutes.

10.1 Publish the intranet service through Azure-Tunnel

Why: The Ubuntu Server runs two web services in Docker: website on port 80 (which you just published as Ubuntu Web) and intranet on port 8000 (mapped to container port 80). Publishing the intranet as a second service through the same tunnel demonstrates a common production pattern: one tunnel, multiple services on different ports.

In the Zero Trust dashboard, click Networks > Connectors and open Azure-Tunnel.

Click the Published application routes tab.

Click + Add a published application route. Configure:

FieldValue
Subdomainintranet
DomainSelect <your-domain> from the dropdown
Path(leave blank)
Service typeHTTP
URL10.91.0.X:8000 (the intranet Docker container maps host port 8000 to container port 80)

Click Save hostname.

Verify: Azure-Tunnel now lists two published routes: web.<your-domain> on port 80 and intranet.<your-domain> on port 8000.

10.2 Create an Access app for the intranet

Why: The intranet is a separate application from the public website. Giving it its own Access app means you can apply different policies to it (e.g., restrict to internal employees only while the public website is open to partners).
Same wizard as Steps 8.2 and 8.5: Access controls > Applications > + Add an application > Self-hosted.
FieldValue
Application nameIntranet
Session duration24 hours
Application domainSubdomain: intranet. Full FQDN: intranet.<your-domain>
Identity providersSAML only
PolicyAllow Lab Users (from Step 8.1)

Click Next through each wizard page, then Add application.

Verify: Intranet appears in the Applications list with FQDN intranet.<your-domain>.

10.3 Test the intranet app

Why: Confirm the full end-to-end flow for the second service: browser → Access auth → tunnel → intranet container on port 8000. The intranet page should look different from the public website, confirming you are hitting the right Docker container.

On the Windows 11 desktop, open an InPrivate browser window.

Navigate to https://intranet.<your-domain>

Cloudflare's Access challenge page loads. Click your SAML provider.

Authenticate as usera@acmecorp.com / #Savetheinternet.

After auth, the intranet page renders. It should look different from the AcmeCorp website you saw in Step 8.4 (the intranet is a separate Docker container serving different content).

Verify: The intranet page loads successfully. The content is distinct from web.<your-domain>. Both Access apps (Ubuntu Web and Intranet) are now accessible through the same Azure-Tunnel but serve different content on different ports.

10.4 Confirm the Intranet access decision in the logs

Why: Same habit as Step 8.8: every Access app you publish should be confirmed against the Access authentication logs, not just the browser. This proves the Intranet app is authenticating through the same Allow Lab Users policy and recording an Allowed decision for the user who reached it.

In the Zero Trust dashboard, click Insights & Logs > Logs > Access authentication logs.

Click + Add filter, set Field Application, Operator equals, and pick Intranet from the Value list.

Read the Decision and User email columns for the login you made in Step 10.3.

Verify: The Access authentication logs show Decision = Allowed for usera@acmecorp.com against the Intranet application.

Lab 11 - Public-IP Lockdown: Proving the Tunnel Is the Only Path

On the bring-your-own-AWS version of this lab, this is where you'd close the last direct path by hand: delete the inbound firewall rules that let anyone reach the Ubuntu Server directly by public IP, leaving the tunnel as the only way in. On this managed pod that lockdown is already done for you at the network level, so there's nothing to remove here. The idea is the same either way: once the direct public path is gone, every policy you built has teeth, because a user can't route around it by talking to the origin directly.
Managed pod: skip Lab 11 and go to Lab 12. The public-IP lockdown exercise applies to the BYO-AWS lab, where students remove inbound rules from the Ubuntu Security Group to prove that the Cloudflare Tunnel is the only path to the origin. In this managed pod, that lockdown is already configured at the pod-network level; the Ubuntu Server's public IP is not exposed for direct access. There are no AWS firewall rules for you to change.

Lab 12 - App Launcher, JWT Inspection & Policy Tester

With a handful of apps published, employees need one place that shows only the apps they're entitled to, and operators need to see what the access decision actually trusted. You turn on the App Launcher (the one URL a lot of customers point their helpdesk at) with a tile for each published app, decode a CF_Authorization JWT on jwt.io to read exactly which claims Cloudflare trusted about a session, and run Policy Tester against Ubuntu Web to predict Allow or Block for usera, itadmin, and userb without making anyone re-authenticate. Estimated time: 30-45 minutes.

12.1 Enable the App Launcher

Why: The App Launcher gives every authenticated user a single dashboard, accessible at https://<team-name>.cloudflareaccess.com, listing every Access app they have permission to reach. Enabling it means users have one URL to remember instead of three separate FQDNs.

In the Zero Trust dashboard, click Settings.

Click Authentication.

Find App Launcher and click Manage or the toggle.

Enable the App Launcher.

Optionally customize the title and branding for the lab.

If you completed Step 9a.1 (browser-rendered RDP), also add usera@acmecorp.com and userb@acmecorp.com to the App Launcher policy so the RDP Workstation tile appears.

Click Save.

Verify: Open an InPrivate browser to https://<team-name>.cloudflareaccess.com. Authenticate as usera@acmecorp.com. The App Launcher page loads showing tiles for Ubuntu Web, Ubuntu SSH, Intranet, and (if Step 9a.1 was completed) RDP Workstation.

12.2 Inspect the CF_Authorization JWT

Why: Every Access-authenticated session sets a cookie named CF_Authorization that contains a signed JWT. The JWT carries the user's identity, the apps they're authorized for, and metadata. Inspecting this token is a useful debugging skill and reveals what Cloudflare actually trusts about the session.

With the Ubuntu Web app open in a browser (Step 8.4), press F12 to open browser developer tools.

Click the Application tab (Chrome/Edge) or Storage tab (Firefox).

Expand Cookies > web.<your-domain>.

Find the cookie named CF_Authorization. Click it.

Copy the cookie value.

Open https://www.jwt.io in a new tab.

Paste the cookie value into the Encoded text area.

The decoded JWT appears on the right side. Inspect the payload.

Verify: The decoded JWT payload contains:
  • email: usera@acmecorp.com
  • iss: https://<team-name>.cloudflareaccess.com
  • aud: an audience ID specific to the Ubuntu Web app
  • iat and exp: issued-at and expiration timestamps
  • identity_nonce: a session identifier
  • Possibly groups: empty for this lab since the SAML IdP didn't provide group claims

Document the aud value. Cloudflare uses this to identify which app the token grants access to. Different Access apps have different audience IDs.

12.3 Use the Policy Tester

Why: Policy Tester evaluates the Ubuntu Web app's policies against users who have already authenticated to Access. You can review who would be allowed or blocked without asking each user to try the app.

In the Zero Trust dashboard, click Access controls > Applications.

Click Ubuntu Web to open it.

Click Test Policy.

Click Test policies. This runs the test for all active users with a prior Access login; you do not need to enter an email address.

Expand the allowed and blocked results to see which users were tested. Look for usera@acmecorp.com, itadmin@acmecorp.com, and userb@acmecorp.com.

Verify: For users included in the results, usera and itadmin should be Allowed, while userb should be Blocked. A user with no prior Access login may not appear in the bulk test.

Lab 13 - Private-Network ZTNA & Posture-Based Blocking

This is the headline ZTNA exercise. So far Access has guarded public hostnames someone types into a browser; real ZTNA also gates private-IP destinations for users already on the tunnel, and it has to revoke access the instant a device turns up unfit. You add a Gateway Network policy (Allow Access App Eval) that lets Access evaluate private traffic, turn on WARP Authentication Identity so users aren't re-prompted, build and then tear down two private-network apps to prove the pattern, stack the three posture policies, then turn Windows Firewall off and watch access drop, turn it back on and watch it return. The posture check from Lab 4 just became an access decision. Estimated time: 60-75 minutes.

13.1 Configure a Gateway Network policy that lets Access evaluate private traffic

Why: Up to this point, the three Access apps work for non-Cloudflare One Client users (someone in an InPrivate browser tab without Cloudflare One Client). For Cloudflare One Client users (someone with Cloudflare One Client connected), private-IP destinations are normally just routed through the tunnel without Access evaluation. To make Access policies apply to Cloudflare One Client-user traffic against private IPs, you need a Gateway Network policy that explicitly hands traffic to Access when the destination matches a self-hosted Access app with a private address.

In the Zero Trust dashboard, expand Traffic controls.

Click Firewall policies.

Click the Network tab.

Click Add a policy. Configure:

FieldValue
Policy nameAllow Access App Eval
ActionAllow
TrafficSelf-hosted Access App with Private Address is Present

Save.

Drag this policy to the top of the Network policy list (it must evaluate before any deny-all rule).

Verify: The policy appears at the top of the Network firewall policies list. It is set to Allow when the destination matches a self-hosted Access app with a private address.

13.2 Enable WARP Authentication Identity for SSO

Why: Without this setting, a Cloudflare One Client-enrolled user who navigates to an Access app gets prompted to authenticate again. With it on, WARP's existing identity is passed through as the Access identity automatically - true SSO from device to app.

In the Zero Trust dashboard, click Settings.

Click WARP Authentication Identity (or Cloudflare One Client > Authentication identity).

Toggle WARP Authentication Identity to On.

Set scope: Apply to all applications.

Save.

Verify: The setting persists after page reload.

13.3 Create two private-network ZTNA Access apps

Why: This exercise demonstrates the Access pattern for private resources accessed by Cloudflare One Client users. Each app maps to a specific internal IP and port. Access policies on each app gate which users can reach that exact resource.

Create both apps via Access controls > Applications > + Add an application > Self-hosted and private. Use the values below:

App NameDestination TypeIPPortsPolicy
Web-internalPrivate network10.91.0.X80, 443Allow Lab Users
SSH-internalPrivate network10.91.0.X22Allow Lab Users

For each app: set Identity providers to SAML only. Click Next, select the Allow Lab Users policy, click Confirm > Next > Add application.

Verify: Two new entries appear in the Applications list: Web-internal and SSH-internal. Each shows a private-network destination.

13.4 Test private-network ZTNA access from the Cloudflare One Client device

Why: Confirm that Cloudflare One Client-user traffic to the private IPs/hostnames is intercepted by Access and gated by the policies.

On the Windows 11 desktop (signed in as cloudflare, Cloudflare One Client enrolled as itadmin@acmecorp.com), confirm the client is Connected.

Open a browser and navigate to http://10.91.0.X (the Ubuntu Server web).

What is the result? Document what happens.

Open PowerShell. SSH to the Ubuntu Server:

ssh cloudflare@10.91.0.X

What is the result? Document.

Expected result: Both access attempts should succeed because itadmin@acmecorp.com is in the Allow Lab Users policy attached to both private-network Access apps. If prompted for re-authentication, authenticate as itadmin@acmecorp.com / #Savetheinternet.
Verify: Web loads, SSH connects.

13.5 Tear down the private ZTNA Access apps before the next exercise

Why: The next exercise demonstrates the alternative approach to ZTNA: use Gateway Network policies instead of Access apps. Before that, remove the Access apps so the Network policies are the only thing controlling access.

Access controls > Applications.

For each of Web-internal and SSH-internal, click the three-dot menu and select Delete.

Confirm each deletion.

Verify: Only Ubuntu Web and Ubuntu SSH remain in the Applications list. The two private-network apps are gone.

13.6 Deploy the three-policy posture stack as Gateway Network policies

Why: This is the production pattern for ZTNA via Gateway. Three policies evaluate top-to-bottom; the first match wins.

Navigate to Traffic controls > Firewall policies. Click Add a policy. The policy editor opens with a Scope section containing IF Conditions (left) and THEN Actions and settings (right), and a Details section (bottom).

Policy 1: Posture-Fail-NET-Restricted-Access

SectionFieldValue
If traffic type is...DropdownNetwork
Then... (Required)Action dropdownBlock
And traffic matches...Click + Add conditionSelector: Destination IP, Operator: in (not "is"; CIDR ranges require in), Value: 10.0.0.0/8
And posture matches...SelectorPassed Device Posture Checks
Operatoris not
Value (Select... dropdown)Firewall Check
Details > Policy namePosture-Fail-NET-Restricted-Access
Key detail: The posture condition uses Passed Device Posture Checks with operator is not and value Firewall Check. This means "the Firewall Check has NOT passed"; i.e., the firewall is off. If the Firewall Check dropdown shows "No valid options," confirm your device has reported at least one posture result (check Insights & Logs > Logs > Posture logs). The posture check from Step 4.1 must have run at least once.

Click Create policy.

Policy 2: Cleanup-Policy-AllInternal

SectionFieldValue
If traffic type is...DropdownNetwork
Then... (Required)Action dropdownAllow
And traffic matches...Click + Add conditionSelector: Destination IP, Operator: in (not "is"), Value: 10.0.0.0/8
Details > Policy nameCleanup-Policy-AllInternal

No posture condition on this one. Click Create policy.

Policy 3: All-NET-InternalNetwork-ImplicitDeny

SectionFieldValue
If traffic type is...DropdownNetwork
Then... (Required)Action dropdownBlock
And traffic matches...Click + Add conditionSelector: Destination IP, Operator: in (not "is"), Value: 10.0.0.0/8
Details > Policy nameAll-NET-InternalNetwork-ImplicitDeny

No posture condition. Click Create policy.

Order the policies

After creating all three, drag them into this order (first match wins):

Posture-Fail-NET-Restricted-Access (Block when firewall is off)

Cleanup-Policy-AllInternal (Allow everyone who passed posture)

All-NET-InternalNetwork-ImplicitDeny (Block everything else as a safety net)

The Allow Access App Eval policy from Step 13.1 should remain above all three.

Verify: The Network policy list shows all four policies in order: Allow Access App Eval at the top, then the three posture-stack policies. Each policy is enabled.

13.7 Test access with posture check passing

Why: Baseline test. With Windows Firewall on and the machine joined to the domain (from Lab 2), both posture checks should report Pass and traffic should flow.

On the Windows 11 desktop (signed in as cloudflare, client enrolled as itadmin@acmecorp.com). Confirm:

Cloudflare One Client: Connected

Windows Firewall: On (Control Panel > System and Security > Windows Defender Firewall)

Test each access path:

SSH: ssh cloudflare@10.91.0.X (or use the browser-rendered SSH app from Step 8.7)

Web: navigate to http://10.91.0.X in a browser

Both should succeed.

Verify: SSH session opens, web page loads.

13.8 Posture failure exercise - disable Windows Firewall and observe the block

Why: This is the headline intermediate-level exercise for Lab 3. Disabling the firewall causes the Firewall Check posture to flip from Pass to Fail. The Posture-Fail-NET-Restricted-Access policy then blocks all access to 10.0.0.0/8. Demonstrating this proves the posture-based access control works.

On the Windows 11 desktop, open Control Panel > System and Security > Windows Defender Firewall > Customize Settings.

Turn off Windows Defender Firewall for both Domain and Private profiles. Save.

Wait 10 minutes. Posture results refresh on a periodic cycle (default is around 10 minutes; some builds use shorter intervals).

Verify posture has flipped to Fail:

In the Zero Trust dashboard, Team & Resources > Devices > Device posture.

Find your device in the Firewall Check results. It now shows Fail.

Re-test access:

SSH: ssh cloudflare@10.91.0.X

Web: http://10.91.0.X

Both should fail because Posture-Fail-NET-Restricted-Access now matches and blocks.

Verify: Both access attempts time out or are explicitly denied. In the Zero Trust dashboard, Insights & Logs > Logs > Network logs, click + Add filter and set Field Destination IP, Operator equals, Value 10.91.0.X. The most recent entries show Action = Block and the Policy name column shows Posture-Fail-NET-Restricted-Access.

Restore the firewall:

Re-enable Windows Defender Firewall for Domain and Private profiles.

Wait 10 minutes for posture to refresh.

Re-test access. All three should succeed again.

13.9 Explore the block events in the Logs page

Why: The Gateway Network logs from Step 13.8 are useful for auditing posture failures.

In the Zero Trust sidebar, click Insights & Logs > Logs.

Click the Network logs card.

Click + Add filter, set Field Policy, Operator equals, and pick Posture-Fail-NET-Restricted-Access from the Value list. (The Operator only offers equals / does not equal, so match the exact policy name rather than a substring.)

Inspect the results.

Verify: Each row corresponds to an attempted access during Step 13.8. The Action column shows Block, the Policy name column shows Posture-Fail-NET-Restricted-Access, the Email matches your enrolled device identity (itadmin@acmecorp.com from Step 3.3), and the Destination IP is 10.91.0.X.

13.10 (Optional) Configure Infrastructure Access for SSH with command logging

Why: Infrastructure Access protects native SSH from a Cloudflare One Client device with an Access policy and short-lived SSH certificates. Command logging requires a separate SSH log encryption key; it is not a connection option in the application editor. You download and decrypt the recorded commands with your private key.

Do this step only if you have lab time remaining. There are four sub-tasks:

a. Create an SSH target

Navigate to Access controls > Targets. If a target already maps your Ubuntu Server's 10.91.0.X in the default virtual network, use that target in sub-step b. The same IP and virtual network cannot be assigned to a second target. Otherwise, click Add a target and fill in:

FieldValue
HostnameUbuntuServerSSH (no spaces)
IPv4Type your Ubuntu Server's full 10.91.0.X address, then select it with the default virtual network

If you filled in the new-target form, click Add target.

b. Create an Infrastructure Access application

Navigate to Access controls > Applications > Create new application > Infrastructure > Continue with Infrastructure. The editor is one form; it does not have separate Next pages.

FieldValue
Application nameInfrastructure SSH - Ubuntu Server
Target criteria > Include > Target hostnameSelect the existing or newly created Ubuntu Server target from sub-step a
Port22
ProtocolSSH
Access enforcement > Policy nameAllow Infrastructure SSH Users
Configure rules > Include > SelectorEmails: add usera@acmecorp.com and itadmin@acmecorp.com (the same users allowed by the policy from Step 8.1)
Connection context > SSH usercloudflare (the existing local Ubuntu account); leave the email-alias checkbox off

Click Save. This creates a new Infrastructure policy with the same allowed emails as Allow Lab Users; the existing policy is not selected in this form.

For the SSH CA, go to Access controls > Service credentials > SSH. Click Add a certificate, select SSH with Access for Infrastructure, then click Generate SSH CA. Open the new certificate and copy its CA public key. Do not choose the separate Application certificate type.

c. Configure the Ubuntu Server to trust the CA

SSH into the Ubuntu Server console (cloudflare / #Savetheinternet) and run:

sudo nano /etc/ssh/ca.pub

Paste the public key from sub-step b. Save the file.

sudo nano /etc/ssh/sshd_config

Add these lines above any Include directives:

PubkeyAuthentication yes
TrustedUserCAKeys /etc/ssh/ca.pub

Save, validate the configuration, and reload the SSH service:

sudo sshd -t
sudo systemctl reload ssh
Why: Adding Cloudflare's SSH CA public key to TrustedUserCAKeys tells the server's SSH daemon to trust short-lived certificates that Cloudflare signs for authenticated users. That replaces static SSH keys and passwords for this Infrastructure Access connection: instead of managing per-user credentials on the box, the server trusts one CA, and Cloudflare issues a fresh, minutes-long certificate each time an authorized user connects. Reloading sshd applies the validated configuration without dropping an existing SSH session.

d. Test native SSH and encrypted command logging

On the Windows 11 desktop, confirm the Cloudflare One Client is connected as itadmin@acmecorp.com. For command logging, download the Windows ZIP from the ssh-log-cli releases, extract it, and open PowerShell in the folder containing ssh-log-cli.exe. Generate an encryption key pair:

.\ssh-log-cli.exe generate-key-pair -o sshkey

Display the public key so you can copy it:

Get-Content -Raw -LiteralPath '.\sshkey.pub'

In the Zero Trust dashboard, go to Traffic controls > Traffic settings > Traffic logging > SSH log encryption public key, click Edit, paste the public key, and click Save. Keep the matching private-key file sshkey on your Windows 11 desktop. The dashboard cannot display decrypted command text without it.

In PowerShell on the Windows 11 desktop, connect to the Ubuntu Server through the Cloudflare One Client with your native SSH client. Replace 10.91.0.X with the Ubuntu Server IP for your pod, as shown in Step 6.5: ssh cloudflare@10.91.0.X

Run whoami, ls, pwd, and date. Then go to Insights & Logs > Logs > SSH command logs, select the session, and download its encrypted log. In PowerShell from the same folder, run .\ssh-log-cli.exe decrypt -i <downloaded-log-file> -k .\sshkey, replacing the file placeholder with your download path. Extract the resulting .decrypted.zip to inspect the text files. See the SSH log download and decryption instructions for the file format.

Verify: The SSH session opens as the local cloudflare user without a reusable SSH key. The downloaded encrypted log can be decrypted with your private key to inspect the commands you ran; their plaintext is not displayed in the dashboard.

>Labs 8-13 Wrap-up

By the end of Lab 3 you should have:

Milestone
☐Browser-rendered RDP app (RDP Workstation) published via rdp-tunnel
☐Local Ubuntu users (usera, itadmin) created and reached through the browser-rendered SSH app as two identities; both visible in the Access authentication logs
☐Service token created for pre-login
☐Pre-login flow configured (service token + enrollment policy + mdm.xml); concept understood (non_identity@ device entry in dashboard)
☐A reusable Allow Lab Users Access policy (usera + itadmin)
☐Three Access self-hosted apps published externally: Ubuntu Web (HTTPS on port 80), Ubuntu SSH (browser-rendered SSH), Intranet (HTTPS on port 8000), all via Azure-Tunnel
☐App Launcher enabled and showing all three apps
☐Inspected a CF_Authorization JWT and decoded its claims
☐Used Policy Tester to verify allow/block decisions
☐Gateway Network policy enabling Access evaluation for private traffic
☐WARP Authentication Identity on for silent SSO
☐Posture stack (posture-fail block → cleanup allow → implicit deny)
☐Demonstrated posture-based access control by disabling Windows Firewall and watching access fail
☐Inspected block events in Gateway Network logs
☐(Optional) Infrastructure Access SSH with short-lived certificates and encrypted command logs

>Labs 8-13 Troubleshooting

SymptomLikely causeFix
Access HTTPS app returns 502 Bad GatewayPublic hostname on the tunnel points to wrong port or origin is downCheck the Ubuntu Server is serving HTTP on the port you specified (Step 8.3 URL field). curl http://10.91.0.X from the Ubuntu Server console
Browser-rendered SSH terminal stuck on "Connecting..."SSH port (22) on the Ubuntu Server is not reachable through the tunnelConfirm Azure-Tunnel has 10.91.0.0/24 as a route (Step 6.4) and that the SSH published route uses ssh:// scheme
Posture failure block (Step 13.8) does not triggerPosture refresh interval is longer than expected; OR the posture-fail policy is below the cleanup-allow policy in orderWait up to 15 minutes. Verify policy order in Step 13.6. The posture-fail policies must be above cleanup-allow.
Cloudflare One Client user gets re-prompted for SAML auth on every Access app accessWARP Authentication Identity not enabled or scope is wrongRe-check Step 13.2. The setting must be On and scoped to all applications.
Policy Tester returns "Application not found"The Access app is not finished provisioningWait a minute after creating the app, then open it via Access controls > Applications > Ubuntu Web > Test Policy
RDP wizard "Continue" stays disabled on Step 9a.1gcloudflared has not connected back to CloudflareOn the Windows client, run Get-Service cloudflared; should be Running. Re-run the install command if missing

>Labs 8-13 to Labs 14-17

Lab 4 turns from identity-based access controls to content-based controls: Cloudflare Gateway DNS, HTTP, and Network policies for SWG functionality, plus Browser Isolation (RBI) and Data Loss Prevention (DLP). The Access apps you built in Lab 3 continue to work; Lab 4 adds policies that filter the content of what users browse.

Lab 14 - DNS & HTTP Policies with Browser Isolation

Deciding who gets in is only half the job. The other half is screening what everyone sends and receives, in both directions. You stand up Gateway's content controls: the recommended DNS policy stack (blocks at resolution time, the cheapest place to stop something), the recommended HTTP stack (inspects the decrypted request for paths and categories), and Browser Isolation, where a risky page runs in a Cloudflare-hosted browser so any zero-day stays off the user's device. Estimated time: 45-60 minutes.
Why: Cloudflare publishes a recommended baseline DNS policy stack that covers the most common threats: malware C2, phishing, content categories (Adult, Gambling, etc.), and country-based blocks. Deploying the baseline once gives you something to test against and a reference point for customizations.

The Cloudflare-recommended DNS policy stack typically includes:

In the Zero Trust dashboard, click Traffic controls > Firewall policies > DNS tab.

Click Add a policy to create each policy listed above. In the policy editor, set its conditions and action, enter its name under Details, and click Create policy.

Confirm all created policies are Enabled.

Verify: The DNS firewall policy list shows the recommended policies. Each is enabled.

14.2 Test the DNS policies

Why: Confirm that the DNS policies are actually intercepting and blocking queries before adding more complexity.

On the Windows 11 desktop with Cloudflare One Client connected, open a browser and test each site:

SiteExpectedLog Action
https://www.bbc.comLoads normallyAllow
http://malware.testcategory.com (Cloudflare synthetic malware domain)Blocked with Cloudflare block pageBlock: Security Categories
https://www.yandex.ruBlocked (or DNS resolution fails)Block: Country block

Inspect the logs: Insights & Logs > Logs > DNS query logs. Click + Add filter and set Field Email, Operator equals, Value your device email. Confirm blocked entries show Action = Block and the Policy name column shows the matching policy.

Verify: bbc.com succeeds. malware.testcategory.com shows a block page. Russian site is blocked. DNS logs show block entries with the correct matching policy in the Policy name column.

14.3 Disable DNS policies before testing HTTP policies

Why: Both DNS and HTTP policies can block the same destinations. To isolate the effect of HTTP policies in the next step, temporarily disable the DNS policies. (You'll re-enable everything in Step 16.1 for the order-of-enforcement exercise.)

In the Zero Trust dashboard, navigate to Traffic controls > Firewall policies > DNS tab.

For each policy in the list, click the status toggle to switch it to Off (disabled). Repeat for every DNS policy.

Confirm all DNS policies are showing as disabled (grey/off toggle).

Verify: All DNS firewall policies are toggled off. Re-test malware.testcategory.com in a browser - should now succeed (or fail differently than before, depending on what the browser does with the connection).
Why: HTTP policies operate after DNS resolution succeeds and can inspect the actual HTTP/HTTPS request. This is where TLS decryption matters - HTTP policies can match on URL paths, request headers, hostname, and content, none of which are visible without TLS decryption.

In the Zero Trust dashboard, navigate to Traffic controls > Firewall policies > HTTP tab.

Click Add a policy to create each policy listed below.

#Policy NameWhat it blocks
1Security categories blockMalware, C&C, Phishing, etc.
2Content categories blockAdult, Gambling, Weapons, etc.
3Country blockHigh-risk geographies
4File type controlsExecutable downloads, etc.
5Untrusted certificate blockSites with self-signed, expired, or invalid TLS certificates

In the policy editor, set each policy's conditions and action, enter its name under Details, and click Create policy.

Confirm all created policies are Enabled.

Verify: The HTTP firewall policy list shows all five recommended policies, each enabled.

14.5 Test the HTTP policies

Why: Confirm HTTP policies match where DNS policies did. Differences will show because HTTP can match URL paths and other fine-grained signals.

On the Windows 11 desktop, open a browser. Repeat the same test pattern from Step 14.2, now with only HTTP policies active:

SiteExpectedLog Action
https://www.bbc.comLoads normallyAllow
http://malware.testcategory.comBlocked at HTTP level (block page identifies matching policy)Block: HTTP security categories
https://www.yandex.ruBlockedBlock: HTTP country block

Inspect logs: Insights & Logs > Logs > HTTP request logs. Click + Add filter and set Field Email, Operator equals, Value your device email. The Policy name column shows the HTTP policy that matched (not the DNS policy from Step 14.2, which is now disabled).

Verify: Same observable behavior as Step 14.2 (sites blocked), but the matching policies in the logs are now HTTP-tier policies.

14.6 Create a Browser Isolation list and policy

Why: Remote Browser Isolation (RBI) renders pages in a Cloudflare-hosted browser instead of the user's local browser. The user sees a video stream of the page. This isolates the user from any malware or zero-days the page might exploit. Common use cases: marketing teams visiting risky competitor sites, security teams analyzing suspicious URLs, or as a general policy for "lightly risky" content categories.

The lab builds an isolation policy for dlptest.com (a public DLP test site) configured as a "view-only" experience.

Step a. Create the isolation list

Navigate to Reusable components > Lists.

Click Create manual list.

FieldValue
List nameDomain Isolation
List typeHostnames

In the Add entry field, type dlptest.com and click Add.

Click Save.

Step b. Create the HTTP isolation policy

Navigate to Traffic controls > Firewall policies > HTTP tab.

Click + Add a policy.

FieldValue
NameAll-HTTP-Domain-Isolate
ActionIsolate
TrafficDomain in list Domain Isolation

Configure Isolation settings:

SettingValue
Copy from remote to clientDo not allow
Paste from client to remoteDo not allow
PrintingDo not allow
File uploadsDo not allow
File downloadsAllow (needed for next test)

Click Save.

Verify: The new policy appears at or near the top of the HTTP policy list with action Isolate and the four restrictions configured.

14.7 Test the isolation policy

Why: Confirm isolation works and the restrictions block the expected actions.

On the Windows 11 desktop, open a browser and navigate to https://dlptest.com/sample-data/. The page loads inside Browser Isolation (look for a thin yellow border or Cloudflare toolbar).

ActionExpected
Download a sample file from the /sample-data/ pageSucceeds (downloads are allowed)
Navigate to dlptest.com/https-post/ and paste text from outside the sessionFails silently or shows "blocked by isolation"
Upload one of the downloaded sample filesFails (uploads blocked)

Inspect logs: Insights & Logs > Logs > HTTP request logs. Filter by hostname dlptest.com. Confirm entries show the Isolate action.

Verify: Page loads isolated; download works; paste fails; upload fails.

Lab 15 - TLS Bypass, Cert Handling & Network Policies

Inspection can't open every connection the same way. Some destinations pin certificates and break under decryption (banks are the classic case), some present invalid certificates you have to make a ruling on, and some traffic never reaches the HTTP tier at all. You tune the checkpoint: a do-not-inspect bypass list for the domains that break under decryption, an untrusted-certificate ruling (block versus pass through), the recommended Network policy stack, and a targeted rule that blocks outbound SMTP on port 25, because port 25 isn't HTTP and has to be stopped a layer down. Estimated time: 45-60 minutes.

15.1 Test the untrusted certificate policy

Why: The recommended policies you built in Step 14.4 include an Untrusted certificate block policy. This policy uses the Untrusted certificate action to control what happens when Gateway encounters a self-signed, expired, or otherwise untrusted TLS certificate. The three options are: Error (default: show Gateway error page), Block (show your block page), or Pass through (bypass the warning and connect to the upstream). This step tests the existing policy against a known bad-cert site.

Navigate to Traffic controls > Firewall policies > HTTP tab. Find the Untrusted certificate block policy from Step 14.4 and confirm it is enabled. Click it to open the editor.

In the policy editor, look at the right side under THEN > Actions and settings. The Untrusted certificate action dropdown appears below the Action dropdown (which should be set to Allow). The dropdown reads "Set how Gateway should handle insecure requests." Confirm it is set to Block.

Test: On the Windows 11 desktop, open a browser and navigate to https://expired.badssl.com/. The site presents an expired TLS certificate. Gateway should block it with a block page.

(Optional) To see the difference between Block and Pass through:

In the policy editor, change the Untrusted certificate action dropdown from Block to Pass through. Save.

Revisit https://expired.badssl.com/; the page now loads without a certificate warning because Gateway passes through to the untrusted origin.

Change it back to Block and save.

Verify: With Block, expired.badssl.com shows a Gateway block page or error. With Pass through, the page loads. Check Insights & Logs > Logs > HTTP request logs, filter by expired.badssl.com to confirm the policy matched.

15.2 Configure a TLS inspection bypass list

Why: Some sites use certificate pinning, custom TLS configurations, or other quirks that break under Cloudflare's TLS decryption. Banking and certain enterprise SaaS apps are common examples. The bypass list lets you exempt specific domains from decryption while keeping decryption on for everything else.

Step 1. Create the bypass list

Navigate to Reusable components > Lists. Click Create manual list.

FieldValue
List nameDomainInspectionBypass
List typeHostnames

In the Add entry field, type cnn.com and click Add.

Click Save.

Step 2. Create the bypass policy

Navigate to Traffic controls > Firewall policies > HTTP tab. Click Add a policy.

FieldValue
NameHTTP-DoNotInspect-Bypass
ActionDo Not Inspect
TrafficDomain in list DomainInspectionBypass

Click Save. Move this policy to the top of the HTTP policy list (Do Not Inspect must evaluate before any policy that would decrypt).

Verify: The policy is at the top of the HTTP list with action Do Not Inspect.

15.3 Confirm TLS bypass actually skips decryption

Why: The cleanest signal that decryption is being bypassed is the TLS certificate the browser sees. With decryption on, the certificate is issued by Cloudflare Managed G2 (your Gateway root CA). With decryption bypassed, the certificate is whatever the origin actually presented.

On the Windows 11 desktop, open a browser.

Navigate to https://www.cnn.com

Click the padlock icon in the browser address bar.

Click Connection is secure > Certificate is valid (or similar - exact wording varies by browser).

Look at the Issued by field.

Expected: the issuer is something like GlobalSign, DigiCert, or similar public CA - NOT Cloudflare Managed G2.

Compare: navigate to another site that is NOT on the bypass list, like https://www.bbc.com. Inspect its certificate. Issued by should be Cloudflare Managed G2 because that site is being decrypted.

Verify: cnn.com shows a public CA in Issued by. bbc.com shows Cloudflare Managed G2. The difference confirms the bypass works.

15.4 Disable HTTP policies before testing Network policies

Why: Same isolation reasoning as Step 14.3: keep Network policies as the only filter so you can see their effect cleanly. You will re-enable everything in Step 16.1.

Navigate to Traffic controls > Firewall policies > HTTP tab.

For each HTTP policy in the list, click the status toggle to switch it to Off (disabled). Repeat for every HTTP policy.

Verify: All HTTP policies show disabled.
Why: Network policies operate at the TCP/UDP layer - they match on destination IP, port, protocol, and certain selectors that work before any HTTP-level decryption. They're useful for blocking non-HTTP protocols and for catching traffic that bypasses HTTP filtering (anything not on ports 80/443).

Navigate to Traffic controls > Firewall policies > Network tab. Click Add a policy to create each policy manually:

Policy NameActionTraffic Selector
All-NET-Internet-BlocklistBlockSecurity Categories matches all (Malware, C&C, Phishing, etc.) + Destination IP in Domain/Host Blocklist
All-NET-NO-HTTP-HTTPS-Internet-DenyBlockDestination port NOT in {80, 443} AND Destination IP in 0.0.0.0/0. Blocks all non-web outbound traffic. Calibrate before production: breaks SSH, RDP, FTP, mail, etc.

Click Create policy for each. Both should be Enabled.

Verify: Both Network policies in the firewall policy list, enabled.

15.6 Test the Network policies

Why: Confirm Network policies catch what they should catch and that the non-web blocker behaves as expected.

Test security category block (Network tier):

From the Windows 11 desktop, navigate to http://malware.testcategory.com. Should be blocked.

Insights & Logs > Logs > Network logs. Filter by Action: Block. Confirm an entry matches the Network blocklist policy.

Test non-HTTP block:

On the Windows 11 desktop, open PowerShell.

Try an outbound SSH connection to a public SSH test server:

ssh -p 22 demo@test.rebex.net

The connection should be blocked by the All-NET-NO-HTTP-HTTPS-Internet-Deny policy because SSH is on port 22.

Check Insights & Logs > Logs > Network logs, filter by destination test.rebex.net or by Block. Confirm the block entry exists.

Verify: Malware test domain is blocked at Network tier; SSH to test.rebex.net fails; both blocks appear in Gateway Network logs.

15.7 Create a targeted SMTP block and test it

Why: The previous step used a broad "block all non-HTTP/HTTPS" policy. In production, you often want surgical port blocks for specific protocols. SMTP (port 25) is the classic example: blocking outbound SMTP from endpoints prevents compromised machines from sending spam directly while still allowing users to send email through their webmail or authenticated SMTP relay.

Traffic controls > Firewall policies > Network tab.

Click + Add a policy. Configure:

FieldValue
NameBlock-Outbound-SMTP
ActionBlock
TrafficDestination port equals 25

Save.

Test from the Windows 11 desktop. Open PowerShell:

Test-NetConnection -ComputerName smtp.gmail.com -Port 25

The connection should fail (TcpTestSucceeded: False) because the Network policy blocks port 25 outbound. 8. Compare: test a port that is NOT blocked:

Test-NetConnection -ComputerName smtp.gmail.com -Port 443

This should succeed (TcpTestSucceeded: True) since HTTPS is allowed. 9. Inspect logs: Insights & Logs > Logs > Network logs. Filter by destination port 25. The block entry shows the Block-Outbound-SMTP policy matched.

Verify: Port 25 is blocked, port 443 is allowed. The Network log shows the SMTP block with the correct policy name.

Lab 16 - Order-of-Enforcement, DLP Basics & Mode-Switch Prediction

With DNS, Network, and HTTP all switched on, only one tier fires for any given request, and knowing which one wins is the daily skill that separates a competent operator from a confused one. You run the full order-of-enforcement exercise across five test cases, add a DLP policy that blocks financial data from leaving, then predict how the same three requests behave as you flip the client between DNS-only, Traffic-only, and Traffic-and-DNS modes. Estimated time: 45-60 minutes.

16.1 Order-of-enforcement exercise: enable all policies and predict

Why: This is one of the most useful exercises in the lab. When DNS, HTTP, and Network policies are all enabled simultaneously, only one fires per request. The order is fixed by Cloudflare's policy engine: DNS evaluates first (before resolution succeeds), then Network (before TLS handshake), then HTTP (after TLS handshake completes). For a single request like "browser navigates to https://malware.testcategory.com", which tier wins?

Re-enable all DNS policies (Step 14.3 disabled them): Traffic controls > Firewall policies > DNS tab. Toggle each policy back On.

Re-enable all HTTP policies (Step 15.4 disabled them): Traffic controls > Firewall policies > HTTP tab. Toggle each back On (including isolation from 4.6, untrusted cert from 4.8, and TLS bypass from 4.9).

Confirm Network policies are still on from Step 15.5.

Predict before testing. For each scenario, write down which tier (DNS, Network, or HTTP) you think will fire:

Browser navigates to http://malware.testcategory.com (HTTP, not HTTPS)

Browser navigates to https://malware.testcategory.com (HTTPS)

Browser navigates to https://dlptest.com/sample-data/ (the isolation domain)

Browser navigates to https://www.cnn.com (the TLS bypass domain)

SSH from PowerShell to test.rebex.net:22

Test each scenario. Visit each URL. Note: was it blocked, allowed, or isolated? After each test, check Insights & Logs > Logs and open the relevant dataset card (DNS, Network, or HTTP) filtered by destination/hostname. The dataset where the entry appears tells you which tier fired.

Expected results (typical Cloudflare order: DNS → Network → HTTP):
RequestWhich tier firesWhy
http://malware.testcategory.comDNSDNS policy resolves first; security category match blocks before any TCP connection
https://malware.testcategory.comDNSSame - DNS evaluation precedes TLS
https://dlptest.com/sample-data/HTTPDNS allows (not in security categories), Network allows, HTTP isolation policy fires
https://www.cnn.comHTTP Do-Not-InspectDNS allows, Network allows, HTTP first match is the Do Not Inspect rule which lets the connection through unmodified
SSH to test.rebex.net:22NetworkDNS allows (it's a legit domain), Network policy blocks port 22 outbound
Verify: Your predictions match the actual matching tier shown in the Logs page. Document any surprises - those are the learning moments.

16.2 Build a DLP policy for financial data

Why: Data Loss Prevention scans outbound HTTP/HTTPS traffic for sensitive patterns (credit cards, SSNs, financial data, custom regex). When a match is found, the policy can block, allow with logging, or allow with notification. The lab uses the built-in Financial Information profile against the public dlptest.com test site.

Verify the DLP profile: Navigate to Data loss prevention > Profiles. Confirm the Financial Information built-in profile exists.

Disable isolation for dlptest.com: Navigate to Traffic controls > Firewall policies > HTTP tab. Toggle All-HTTP-Domain-Isolate to Off (otherwise it isolates dlptest.com and you cannot test DLP on it).

Create the DLP policy: On the same HTTP tab, click + Add a policy.

FieldValue
NameBlock-DLP-Financial
ActionBlock
TrafficDLP Profile Financial Information matches Present

Click Save.

Verify: The DLP policy is in the HTTP policy list, enabled.

16.3 Test the DLP policy

Why: Confirm DLP detects financial data being uploaded and blocks it.

Navigate to https://dlptest.com/sample-data/. Download sample-SSN.txt and sample-CCN.txt. Open one in Notepad to confirm it contains test data.

Navigate to https://dlptest.com/https-post/ and run these tests:

ActionExpected
Paste SSN/CCN data into the text box and submitBlocked by DLP policy
Upload a sample SSN/CCN fileBlocked by DLP policy

Inspect logs: Insights & Logs > Logs > HTTP request logs. Filter by hostname dlptest.com and action Block. Confirm the DLP Profile column shows Financial Information.

Verify: Paste fails; upload fails; logs show the DLP block with the matching profile.

16.4 Mode-switch prediction exercise

Why: This is the second key intermediate exercise (alongside Step 16.1). The Cloudflare One Client supports several modes that change which tiers of Gateway policy can match. The lab cycles through modes and asks the student to predict which policies fire in each.
ModeWhat routes through CloudflareWhich policies apply
Traffic and DNS mode (default)All device traffic + DNS resolution forwarded to client DNS resolverDNS, Network, and HTTP
DNS only modeDNS resolution only; device traffic goes directDNS only
Traffic only modeAll device traffic; DNS resolution managed by device OSNetwork and HTTP only
Posture only modeNothing; client only reports device health and posture dataNone (posture data available for policies but no traffic routing)
Local proxy modeOnly explicitly-directed local HTTP trafficHTTP only (for traffic sent to the local proxy)

How to switch modes

Mode is set in the Device Profile, not in the client UI. Navigate to Team & Resources > Devices > Device profiles > Lab-Profile. Scroll to Service mode and select the mode you want. Click Save Profile. The Cloudflare One Client on the device will pick up the change within a minute.

Test 1: DNS only mode

In the Lab-Profile, change Service mode to DNS only mode and click Save Profile. Wait for the client to update.

Predict which tier(s) fire, then test each:

https://malware.testcategory.com

https://dlptest.com/sample-data/

SSH to test.rebex.net:22

Expected results in DNS only mode:
RequestResultWhy
https://malware.testcategory.comBlocked by DNSDNS still routes to Gateway, security category match fires
https://dlptest.com/sample-data/Loads normally (NOT isolated)DNS allows; HTTP/Network policies do not apply because traffic does not proxy through Cloudflare; isolation policy never evaluates
SSH to test.rebex.net:22SucceedsNetwork policy does not apply (no proxy); DNS allows

Test 2: Traffic only mode

In the Lab-Profile, change Service mode to Traffic only mode and click Save Profile. Wait for the client to update. Predict, then test, the same three requests.

Expected results in Traffic only mode:
RequestResultWhy
https://malware.testcategory.comBlocked by Network or HTTPDNS does NOT route to Gateway (so DNS policy can't fire), but the traffic still proxies through Cloudflare; Network or HTTP security category match fires
https://dlptest.com/sample-data/IsolatedSame as default mode - traffic proxies through Cloudflare
SSH to test.rebex.net:22Blocked by NetworkNetwork policy still applies (traffic proxies through Cloudflare)

Restore default mode

In the Lab-Profile, change Service mode back to Traffic and DNS mode and click Save Profile.

Verify: All three modes behave as predicted. The mode-switch exercise demonstrates a core architectural truth: which tier of policy can fire depends on which Cloudflare One Client mode is active.

Lab 17 - SWG Advanced: Custom Pages, Clientless RBI, Advanced DLP, PAC & Tenant Control

Five features that show up in real deployments once the basics are in. You brand the block page so users know where to get help, stand up clientless RBI for partners and personal devices that can't run the client, write a custom DLP detection for AcmeCorp's own employee-ID pattern (ACME- followed by six digits) with an encryption keypair so analysts can decrypt matches offline, route agentless devices through Gateway with a PAC file, and inject tenant-control headers so nobody signs into a personal SaaS account on a managed device. This is screening extended to everyone, even the users who never installed the client. Estimated time: 60-75 minutes.

17.1 Create a custom Gateway block page

Why: The default Cloudflare block page is functional but generic. A custom block page lets you brand the experience, provide a help desk URL, and explain to users why their request was blocked.

Navigate to Reusable components > Custom pages. Click Create custom page.

FieldValue
Page nameLab Block Page
Page typeBlock page (Gateway)
Content"This site has been blocked by your organization's security policy. Contact helpdesk@acmecorp.com if you believe this is an error."

Click Save.

Attach the block page to a policy: Traffic controls > Firewall policies > HTTP tab > open an existing Block policy > Edit.

Under Actions and settings > Modify Gateway block behavior, select Custom block page and choose Lab Block Page. Save.

Trigger the block by navigating to a site that matches the policy.

Verify: The block page renders with your custom text, not the default Cloudflare block page.

17.2 Enable clientless Remote Browser Isolation

Why: Clientless RBI lets users access isolated browser sessions without the Cloudflare One Client installed. Traffic is rendered through a Cloudflare-hosted browser as a video stream.

Step 1. Enable clientless RBI settings

Navigate to Browser isolation > Browser isolation settings.

SettingValue
Allow users to open a remote browser without the device clientOn
Allow isolated HTTP traffic when user identity is unknownOn (if available)

Save.

Step 2. Create a clientless isolation policy

Navigate to Traffic controls > Firewall policies > HTTP tab. Click Add a policy.

FieldValue
NameClientless-Isolate-AcmeCorp
ActionIsolate
TrafficHostname matches 10.91.0.X
Copy from remote to clientDo not allow
PrintingDo not allow

Save.

Step 3. Test

Open an InPrivate browser window. Navigate to https://<team-name>.cloudflareaccess.com/browser

In the isolated browser, navigate to http://10.91.0.X

Try to copy text from the page. Try to print.

Verify: The page renders inside the isolated browser. Copy and print are blocked. Check Insights & Logs > Logs > HTTP request logs to confirm the isolation policy matched.

17.3 Create a custom DLP detection entry with regex and payload encryption

Why: Custom DLP detection entries let you detect company-specific data like employee IDs or project codes. Payload encryption lets you decrypt matched content in logs.

Note: If you hit a permissions wall on "Create profile," use Data loss prevention > Detection entries > Create a detection entry instead.

a. Generate a DLP payload encryption keypair

Navigate to Data loss prevention > DLP settings. Find Set a DLP payload and prompt encryption public key.

On the Ubuntu Server, generate the keypair:

openssl genrsa -out dlp_private.pem 2048 && openssl rsa -in dlp_private.pem -pubout -out dlp_public.pem

Paste the contents of dlp_public.pem into the dashboard field. Save.

Why: This is an asymmetric keypair. You hand Cloudflare only the public key, which it uses to encrypt any matched sensitive content before writing it to the logs. The private key never leaves the server, so only someone holding that file (a security analyst) can decrypt a match. That way the caught data (an SSN, an employee ID) is never stored in plaintext where a log reader could see it.

b. Create a custom detection entry

Navigate to Data loss prevention > Detection entries. Click Create a detection entry.

FieldValue
NameAcmeCorp Employee ID
TypeCustom regex
PatternACME-\d{6}

Save.

c. Create a DLP profile

Navigate to Data loss prevention > Profiles. Click Create profile.

FieldValue
Profile nameAcmeCorp Employee ID
Detection entriesAttach AcmeCorp Employee ID

Save.

d. Create a Gateway HTTP policy

Navigate to Traffic controls > Firewall policies > HTTP tab. Click Add a policy.

FieldValue
NameBlock-DLP-EmployeeID
ActionBlock
TrafficDLP Profile matches AcmeCorp Employee ID

Save.

e. Test

Navigate to https://dlptest.com/https-post/. Type My employee ID is ACME-789012 and submit.

Verify: The submission is blocked. Insights & Logs > Logs > HTTP request logs shows the DLP match with profile AcmeCorp Employee ID.

17.4 Decrypt and inspect a DLP payload match in the logs

Why: Step 17.3 set up payload encryption so that matched DLP content is encrypted in the logs with your public key. This step closes the loop by finding a match in the logs and decrypting it with the private key. In production, this is how a security analyst reviews what data was actually caught without the matched content being stored in plaintext.

In the Zero Trust dashboard, click Insights & Logs > Logs > HTTP request logs.

Click + Add filter, set Field Policy, Operator equals, and pick Block-DLP-EmployeeID from the Value list. (Alternatively, filter Field Host equals dlptest.com and add a second condition Field Action equals Block. The Operator only offers equals / does not equal, so there is no substring match.)

Find the blocked request from Step 17.3 (the one where you typed ACME-789012).

Click into the log entry to expand its details.

Find the DLP match section. The matched content is shown as an encrypted payload (base64-encoded ciphertext).

Copy the encrypted payload string and paste it below:

Fill in here: encrypted DLP payload

On the Ubuntu Server (SSH session or console), decrypt the payload using the private key you generated in Step 17.3:

echo "<paste-encrypted-payload-here>" | base64 -d | openssl pkeyutl -decrypt -inkey dlp_private.pem

(Exact decryption command may vary depending on the encryption format Cloudflare uses. If pkeyutl fails, try openssl rsautl -decrypt -inkey dlp_private.pem.) 8. The decrypted output should contain the matched text (ACME-789012).

Verify: The decrypted payload reveals the original matched content. If the log entry does not show an encrypted payload (shows plaintext or shows nothing), verify that the public key was saved correctly in Step 17.3 sub-step 1 and that at least one new DLP match occurred after the key was configured.

17.5 Create a Gateway Authorization Proxy and PAC file

Why: Not every device can run the Cloudflare One Client. A proxy endpoint with a PAC file routes web traffic through Cloudflare Gateway for agentless devices.

Step 1. Create the proxy endpoint

Navigate to Traffic controls > Resolvers & Proxies > Proxy endpoints tab. Click Add a proxy endpoint. In the Create a new proxy endpoint dialog, leave Authorization selected and click Continue.

FieldValue
NameLab Proxy Endpoint

Save.

Step 2. Create and deploy the PAC file

Click Add a PAC file. Configure to route HTTP/HTTPS through the proxy. Copy the PAC file URL.

On the Windows 11 desktop, open Settings > Network & internet > Proxy.

Under Automatic proxy setup, enable Use setup script. Paste the PAC URL. Save.

Step 3. Create and test a proxy-routed policy

Navigate to Traffic controls > Firewall policies > HTTP. Click Add a policy.

FieldValue
NameProxy-Block-Test
ActionBlock
TrafficDomain matches example.com

Save. Navigate to http://example.com

Verify: example.com is blocked. Insights & Logs > Logs > HTTP request logs shows the traffic came through the proxy endpoint.

Clean up: Disable the proxy in Windows Settings. Delete the Proxy-Block-Test policy.

17.6 Configure tenant control policies

Why: Tenant control injects custom HTTP headers so SaaS applications can identify your organization. This prevents personal account access on managed devices.

Navigate to Traffic controls > Firewall policies > HTTP > Add a policy. Configure:

FieldValue
NameTenant-Control-Header
ActionAllow
TrafficHostname matches lab.cfiq.io

Under Actions and settings > Add custom headers:

Header nameHeader value
x-get-token2KdBpMuDeBAn8GtDv, <team-name> (replace with your slug)

Save.

On the Windows 11 desktop (Cloudflare One Client connected), navigate to https://lab.cfiq.io/zt/v1/seed

Verify: The page returns a token string, proving the custom header was injected by Gateway and authenticated your tenant.

>Labs 14-17 Wrap-up

By the end of Lab 4 you should have:

Milestone
☐DNS recommended policies built and tested with malware.testcategory.com and a country-block site
☐HTTP recommended policies built and tested
☐A Browser Isolation policy for dlptest.com with copy/paste/print/upload restrictions
☐Untrusted certificate policy tested against expired.badssl.com (Block vs. Pass through)
☐A TLS inspection bypass for cnn.com, verified by certificate inspection
☐Network recommended policies (security categories blocklist and non-HTTP/HTTPS deny)
☐A targeted SMTP block (port 25) tested with Test-NetConnection
☐Order-of-enforcement exercise complete with predicted vs. actual matches across DNS/Network/HTTP tiers
☐A DLP policy blocking financial data exfiltration, tested with sample SSN/CCN files
☐Mode-switch prediction exercise complete with DNS only mode and Traffic only mode
☐A custom Gateway block page with branding
☐Clientless Remote Browser Isolation enabled and tested with clipboard restrictions
☐A custom DLP detection entry matching ACME-\d{6} with payload encryption
☐DLP payload decryption verified using the private key
☐A Gateway Authorization Proxy with PAC file tested
☐Tenant control via custom HTTP header injection verified against lab.cfiq.io

>Labs 14-17 Troubleshooting

SymptomLikely causeFix
DNS policies created but malware.testcategory.com still resolves and loadsThe browser cached the previous DNS resultOpen an InPrivate browser window or run ipconfig /flushdns in PowerShell
TLS bypass on cnn.com not working (still shows Cloudflare Managed G2 cert)Do Not Inspect policy is below an Allow policy that decryptsRe-check Step 15.2 - move the bypass to the top of the HTTP list
Isolation policy not isolating dlptest.comEither the list doesn't contain the domain, or another HTTP policy (like a higher-priority allow) is matching firstCheck the Lists entry; check policy order; check Insights & Logs > Logs > HTTP request logs for which policy actually matched
DLP policy created but does not block paste/uploadDLP requires TLS decryption to inspect the request bodyConfirm TLS decryption is On (Lab 1 Step 1.5); confirm the DLP policy is enabled; confirm the All-HTTP-Domain-Isolate policy is OFF (Step 16.2 sub-step 2)
Mode-switch shows greyed-out options in Cloudflare One ClientMode switch lock from Lab 1 Step 5.4 is still onTemporarily toggle Mode switch to On in the device profile (Step 16.4), then re-lock it after testing
DEX-style notifications fire during Step 16.1 testingExpected: intentional malware/Russian/SSH tests look anomalousIgnore or temporarily disable DEX notifications during Lab 4

>Labs 14-17 to Lab 18

Lab 5 installs Cloudflare Mesh on the Ubuntu Server so it functions as both a tunnel endpoint and a Cloudflare One Client device, with Gateway policies applying to the server's own traffic.

Lab 18 - Cloudflare Mesh on the Ubuntu Server

One last on-ramp. A server can publish itself through a tunnel (you did that in Lab 6), but it can also enroll as a Cloudflare One device using Cloudflare Mesh, which pushes the server's own outbound traffic through Cloudflare and under the same Gateway policies you spent Labs 14 through 17 building. You install Mesh alongside the existing Azure-Tunnel, watch the Ubuntu Server show up in the dashboard as a Mesh device, and confirm your DNS and HTTP policies now apply to curl requests that originate from the server itself. That's how you protect infrastructure that makes its own outbound calls, like an app server talking to a SaaS API. Estimated time: 30-45 minutes.

Note: The partner-bootcamp Lab 5 deploys Mesh on an HQ Router as a full site gateway with four troubleshooting exercises. This pod deploys Mesh on the Ubuntu Server instead, demonstrating the same connector technology in a single-server context.

18.1 Create a dedicated Device Profile for the Mesh connector

Why: Cloudflare Mesh enrolls as a special Cloudflare One Client device using a service-token-like identity: warp_connector@<team-name>.cloudflareaccess.com. A dedicated profile ensures it gets different settings from human users.

In the Zero Trust sidebar, click Team & Resources > Devices > Device profiles.

Click + Add profile. Configure the following:

FieldValue
Profile nameMesh Connector Profile
Matching rule: SelectorEmail
Matching rule: Operatoris
Matching rule: Valuewarp_connector@<team-name>.cloudflareaccess.com
Auto connectOn (0 minutes)
Allow device to leave organizationOff

Save. Drag the profile to position #1 in the profile list.

Verify: The Mesh Connector Profile is at the top of the Device profiles list.

18.2 Install Cloudflare Mesh on the Ubuntu Server

Why: Mesh turns the Ubuntu Server into a Cloudflare One Client device in addition to being a tunnel endpoint. The server's own internet traffic will egress through Cloudflare, and Gateway policies will apply to it.

In the Zero Trust sidebar, click Networks > Tunnels & Mesh.

Click Create a tunnel.

Select Mesh as the tunnel type.

Name: Ubuntu-Mesh

OS: Linux, Distribution: Debian/Ubuntu, Architecture: 64-bit

The wizard generates three install commands. Copy them.

SSH into the Ubuntu Server (cloudflare / #Savetheinternet).

Paste each install command one at a time. Wait for each to complete before pasting the next.

Verify the Mesh service is running:

warp-cli --accept-tos status

Output should show Connected.

Verify: In the Zero Trust dashboard, Team & Resources > Devices > Your devices shows the Ubuntu Server as a Mesh-type device with email warp_connector@<team-name>.cloudflareaccess.com.

18.3 Test that Gateway policies apply to the server's traffic

Why: With Mesh connected, the Ubuntu Server's internet traffic now egresses through Cloudflare. Gateway policies should apply.

From the Ubuntu Server SSH session, test internet access:

curl ifconfig.io

The returned IP should be a Cloudflare IP (the server's traffic egresses through Cloudflare). 2. Test that security policies block malicious destinations:

curl http://malware.testcategory.com

This should be blocked by your DNS or HTTP security-category policies from Lab 4.

Verify: The ifconfig.io response shows a Cloudflare IP. The malware.testcategory.com request is blocked. In Insights & Logs > Logs > HTTP request logs, the server's requests appear with the Mesh device as the source.

>Lab 18 Wrap-up

By the end of Lab 5 you should have:

Milestone
☐Cloudflare Mesh installed on the Ubuntu Server alongside the existing Azure-Tunnel
☐The Ubuntu Server appearing in the dashboard as a Mesh-type device
☐Gateway policies applying to the server's own internet traffic

>Lab 18 Troubleshooting

SymptomLikely causeFix
Mesh install failsPermissions or service conflictCheck systemctl status warp-connector for errors. Both cloudflared and Mesh can coexist
curl ifconfig.io returns a non-Cloudflare IPMesh not connectedRun warp-cli --accept-tos status and verify Connected. Check the Mesh profile matches the connector email
curl http://malware.testcategory.com is NOT blockedGateway policies not evaluating Mesh trafficVerify the Mesh device appears in Team & Resources > Devices > Your devices. Check DNS/HTTP policies are enabled

Course wrap-up

You've completed all labs available on this pod:

LabWhat you built
Lab 1Identity, posture, and endpoint deployment: SAML IdP, MSI install, MDM config, pre-login, posture checks, DEX monitoring, and internal DNS
Lab 2Private networking: Cloudflare Tunnel, private routes, internal DNS resolution, and a DNS location for server-side filtering
Lab 3ZTNA access patterns: browser-rendered RDP, multi-user SSH access, pre-login with service tokens, self-hosted Access apps, App Launcher, JWT inspection, Policy Tester, posture-based blocking, and Infrastructure Access SSH
Lab 4SWG, RBI, DLP, and filtering: recommended policy stacks, browser isolation, clientless RBI, TLS bypass, custom DLP profiles with payload encryption, mode-switch prediction, order-of-enforcement, custom block pages, proxy endpoints with PAC files, and tenant control
Lab 5Cloudflare Mesh: deployed on the Ubuntu Server with Gateway policies applying to server traffic

Day 5: Zero Trust Capstone Lab

Day 5 is the individual Zero Trust Capstone Lab: "AcmeCorp from the inside." Working on your own in your enrolled lab environment, you rebuild AcmeCorp's Zero Trust posture end to end, and an automated grader scores your work.

What you build (16 graded items): device posture and split tunnel; a billing app published through a Cloudflare Tunnel and gated by Access; service-token monitoring access; Access for Infrastructure SSH with short-lived certificates; a clientless contractor app; a browser-rendered infrastructure app; Gateway DNS, Network, and HTTP policies with a deny-all floor; Browser Isolation; TLS decryption with a Do-Not-Inspect bypass; a DLP policy; and a student-built Logpush accountability sink provisioned with Terraform.

The capstone is a self-contained guide on its own site. Open it and sign in with your email and the access code your instructor gives you:

https://zt-capstone.cloudflareacademy.com

Work through that guide start to finish. It carries the full checklist of graded items and the one-line command you run on your enrolled Windows box to submit your build for grading.

Course Feedback

Course feedback QR code

https://docs.google.com/forms/d/e/1FAIpQLScU9VxSa8fuCwOwha1Gneo2S7O4C6R5TwFlW30xJ5gajZo8RA/viewform