Packaging & Deployment
Controlling Google Chrome Auto-Updates on macOS with Jamf Pro
The Control Mechanism Is the Same — the Delivery Is Not
On Windows, Chrome does not update through WSUS or Configuration Manager by default. It updates itself through Google Update (GoogleUpdate.exe), a dedicated service you governed through ADMX Group Policy templates or, in some environments, through a third-party patch catalog in SCCM. The important point: you were already managing a vendor-specific update service, not an OS-level one. WSUS manages Microsoft content; Chrome was always its own system.
macOS is the same story with a different delivery channel. Chrome brings Google Software Update — historically called Keystone, a name that persists in the managed preference domain even though Google has migrated the underlying agent to its cross-platform Chromium Updater. By default, Google Software Update checks in with Google on its own cadence and silently applies whatever it finds, entirely outside your MDM’s software-deployment pipeline. If you deploy a specific Chrome version through a Jamf Pro policy this morning, a device can be on the next version by lunch without a line in your policy logs.
The good news: Google Software Update reads managed preferences, which means an MDM configuration profile can put the update channel back under your control — declaratively, fleet-wide, and without a GUI. The managed preference domain is com.google.Keystone, and it is the closest thing you have to the Google Update ADMX policies you already know.
Reference: Manage Chrome updates (Mac)
Where the Windows Model Maps
| Windows concept | macOS / Chrome equivalent | Notes |
|---|---|---|
Google Update service (GoogleUpdate.exe) | Google Software Update | Same role: vendor-owned updater, governed by vendor-specific policy keys. |
| Google Update ADMX: Update policy override | com.google.Keystone → updatePolicies → UpdateDefault | Same four-state concept: automatic, scheduled-only, manual-only, disabled. |
| ADMX: Target version prefix (pin) | TargetVersionPrefix | Pin the fleet to a milestone, e.g. NNN.. |
| ADMX: Update time window | UpdatesSuppressedStartHour + UpdatesSuppressedDurationMin | Defines the daily window when auto-updates are allowed (in the device’s local time). Outside that window, updates are suppressed. |
| ADMX: Target channel | TargetChannel | Stable, Extended Stable, Beta, or Dev. |
| GPO scoping (OU / security group) | Jamf Pro configuration-profile scope (smart group) | The profile is the policy container; the smart group is the filter. |
The translation point: on Windows you governed Chrome’s updater through the registry via ADMX. On macOS you govern the same updater through a managed preference domain via MDM. The updater still exists and still runs — it obeys the policy you deployed.
Choosing Your Update Posture
Before reaching for a freeze, understand that Google provides several mechanisms with different risk profiles. A hard freeze (UpdateDefault = 3) disables updates entirely — including security patches — and Google explicitly advises against it except as a temporary exception with a documented owner and removal date.
| Need | Mechanism | Risk |
|---|---|---|
| Normal fleet — automatic updates, current security | Stable channel, UpdateDefault = 0 | Lowest. This is the default. |
| Slower feature cadence, still receiving security fixes | Extended Stable channel (TargetChannel = extended) | Low. Eight-week feature cycle; security fixes continue. |
| Hold at a tested milestone while validating the next | TargetVersionPrefix pin (e.g. NNN.) with UpdateDefault = 0 | Medium. The updater stays active but only moves to the pinned milestone. |
| Emergency freeze — stop all updates | UpdateDefault = 3 with a documented owner and expiry | High. No security patches. Time-box and monitor. |
Extended Stable is often the answer a Windows administrator is actually looking for: the WSUS-like posture of “update less frequently, but stay patched.” It delivers an eight-week feature cadence while continuing to receive security fixes. Set it with TargetChannel in the same managed preference domain.
Reference: Set Chrome release channel
The Managed Preference Payload
Google Software Update’s behavior is driven by the updatePolicies dictionary in com.google.Keystone. It has a global block that applies to every Google product on the device, and per-application override blocks keyed by the application identifier — com.google.Chrome for the browser. Per-application settings override global settings.
The key that matters most is UpdateDefault, an integer:
UpdateDefault | Behavior |
|---|---|
0 | Auto-updates on — updates are applied whenever detected. This is the default. |
1 | Updates are installed only during the scheduled update check; manual checks from the browser do not trigger an install. |
2 | Auto-updates off — the updater does not install automatically, but a user can still trigger an update manually through Chrome’s UI or Google’s update utility. |
3 | Updates are never applied — neither automatic nor manual. Google advises against this except as a temporary measure. |
Note that 2 (manual only) does not prevent users from updating Chrome themselves. If your goal is to hold a specific version, 2 does not achieve it — use a TargetVersionPrefix pin with UpdateDefault = 0, or a temporary 3 with a documented removal date.
The profile
Here is a minimal configuration profile that pins Chrome to a tested milestone. Upload it to Jamf Pro as a custom (com.google.Keystone) configuration profile and scope it to a test smart group first. Generate real UUIDs with uuidgen and validate with plutil -lint before deploying.
Jamf Pro also supports deploying managed preferences through the Application & Custom Settings payload using a JSON Schema. If your environment uses that approach, you can define the com.google.Keystone keys in a JSON schema and deploy them without hand-crafting XML. The raw XML profile shown here works universally and makes the structure explicit for administrators coming from ADMX, but the JSON Schema method is worth knowing about for environments that standardize on it.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PayloadContent</key>
<array>
<dict>
<key>PayloadType</key>
<string>com.google.Keystone</string>
<key>PayloadIdentifier</key>
<string>com.yourorg.chrome.updatecontrol</string>
<key>PayloadUUID</key>
<string>REPLACE-WITH-A-UNIQUE-UUID</string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>PayloadDisplayName</key>
<string>Google Chrome Update Control</string>
<key>PayloadOrganization</key>
<string>Your Organization</string>
<key>updatePolicies</key>
<dict>
<key>com.google.Chrome</key>
<dict>
<key>UpdateDefault</key>
<integer>0</integer>
<key>TargetVersionPrefix</key>
<string>NNN.</string>
</dict>
</dict>
</dict>
</array>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadIdentifier</key>
<string>com.yourorg.chrome.updatecontrol.profile</string>
<key>PayloadUUID</key>
<string>REPLACE-WITH-ANOTHER-UNIQUE-UUID</string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>PayloadDisplayName</key>
<string>Google Chrome Update Control</string>
<key>PayloadScope</key>
<string>System</string>
</dict>
</plist>PayloadScope is System because the managed policy is read from the system-level preference domain (/Library/Managed Preferences/com.google.Keystone.plist), not per-user.
Replace NNN. with your current approved milestone. The trailing dot matters: NNN. pins to the entire milestone family (NNN.0.xxxx.xxx, etc.) rather than an exact build. When you finish testing the next milestone, change one string in the profile. Google’s rollout timing still applies to when the pinned version becomes available — this is a target, not an instant deployment. Downgrading to a previous version requires a separate RollbackToTargetVersion key and is limited to the three most recent major releases.
For an emergency freeze, replace the com.google.Chrome block with UpdateDefault = 3 alone — no TargetVersionPrefix. Document an owner and a removal date. A freeze is disabling updates, including security patches, and it must be time-boxed.
Reference: Manage Chrome updates (Mac) — rollback
What a freeze does not cover
Disabling Chrome browser updates does not automatically stop component updates. Components like Widevine DRM and the updater recovery component may continue to update independently. Google documents a separate ComponentUpdatesEnabled policy to control these, but notes that not every component is governed by it. If your security posture requires freezing everything Chrome delivers, you need both UpdateDefault = 3 and ComponentUpdatesEnabled = false — and you need to understand that even then, some components are exempt.
Important: ComponentUpdatesEnabled is a com.google.Chrome browser policy, not a com.google.Keystone updater policy. It belongs in a separate managed-preference payload targeting the com.google.Chrome domain — do not add it to the Keystone profile above. The minimal payload content for a com.google.Chrome profile is:
<key>PayloadType</key>
<string>com.google.Chrome</string>
<!-- ... standard PayloadIdentifier, PayloadUUID, PayloadVersion ... -->
<key>ComponentUpdatesEnabled</key>
<false/>One top-level configuration profile can carry both the Keystone and Chrome payloads as separate entries in the PayloadContent array, but they are distinct PayloadType entries with distinct preference domains. After deployment, confirm the policy landed by navigating to chrome://policy in the browser — ComponentUpdatesEnabled should appear with a value of false and a source of “Platform.” Google’s Chrome Enterprise installer includes a com.google.Chrome.manifest file that documents the current policy template.
Verify From the Command Line
A profile that installs is not a profile that took effect. On Windows you confirmed with gpresult /r or by reading the resultant registry value. On macOS, verify by reading the managed plist directly. Do not rely on defaults read with a domain name for nested dictionaries — the output order of keys is not guaranteed, and piping it through grep and awk is fragile across macOS versions. Use PlistBuddy for precise key-path reads:
#!/bin/zsh
# Confirm the Chrome update-control profile is effective on this Mac.
MANAGED_PLIST="/Library/Managed Preferences/com.google.Keystone.plist"
# 1. Is the managed-preferences file present at all?
if [[ ! -f "$MANAGED_PLIST" ]]; then
echo "FAIL: Managed preferences not present. Profile not scoped or not yet installed."
exit 1
fi
# 2. Validate the file is well-formed.
/usr/bin/plutil -lint "$MANAGED_PLIST"
# 3. Read the per-app UpdateDefault for Chrome using PlistBuddy.
UPDATE_DEFAULT=$(/usr/libexec/PlistBuddy -c \
"Print :updatePolicies:com.google.Chrome:UpdateDefault" \
"$MANAGED_PLIST" 2>/dev/null)
echo "Chrome UpdateDefault (effective): ${UPDATE_DEFAULT:-<not set>}"
case "$UPDATE_DEFAULT" in
3) echo "OK: Chrome updates are frozen (never applied)." ;;
2) echo "WARN: Chrome is manual-only — users can still update through Chrome's UI." ;;
1) echo "OK: Chrome updates are scheduled-only." ;;
0) echo "OK: Chrome auto-updates are on." ;;
*) echo "WARN: UpdateDefault not managed for Chrome." ;;
esac
# 4. Check for a version pin.
TARGET=$(/usr/libexec/PlistBuddy -c \
"Print :updatePolicies:com.google.Chrome:TargetVersionPrefix" \
"$MANAGED_PLIST" 2>/dev/null)
[[ -n "$TARGET" ]] && echo "Version pin: $TARGET" || echo "No version pin set."Turn it into a Jamf Pro extension attribute
The diagnostic script above prints human-readable output. A Jamf Pro extension attribute must return a single <result> value. Wrap the key read into the EA format so your fleet reports its own state:
#!/bin/zsh
# Jamf Pro Extension Attribute: report Chrome update-control state.
MANAGED_PLIST="/Library/Managed Preferences/com.google.Keystone.plist"
UD=$(/usr/libexec/PlistBuddy -c \
"Print :updatePolicies:com.google.Chrome:UpdateDefault" \
"$MANAGED_PLIST" 2>/dev/null)
TVP=$(/usr/libexec/PlistBuddy -c \
"Print :updatePolicies:com.google.Chrome:TargetVersionPrefix" \
"$MANAGED_PLIST" 2>/dev/null)
echo "<result>${UD:-not-managed}|${TVP:-none}</result>"The EA returns a combined value like 0|126. (auto-updates on, pinned to milestone 126) or 3|none (frozen, no pin). Build a smart group on the EA value to surface devices whose policy state doesn’t match your approved posture.
This EA confirms policy intent, not the installed Chrome version. A device reporting 0|126. may still be running 125 while Google’s rollout proceeds. For version compliance, use Jamf Pro’s native application inventory — it already collects the installed version of Google Chrome.app at every inventory update. A complete compliance picture is two signals: the Keystone EA confirms the policy is in place, and the application inventory confirms Chrome actually reached the approved milestone. Define a rollout grace period (a few days after Google publishes the pinned milestone) before treating a version mismatch as non-compliant.
For the underlying mechanics of managed preferences, domains, and plutil, see The Shell Crossover, Part 4: macOS Preferences, Plists, and the defaults Command.
Confirm the updater is honoring the policy
Profile presence proves MDM delivered the setting. To confirm Google Software Update is actually obeying it, check the updater logs. The log location has moved across updater versions; as of the Chromium Updater migration, look in:
/Library/Application Support/Google/GoogleUpdater/updater.log
~/Library/Application Support/Google/GoogleUpdater/updater.logUse the log to look for updater transactions and errors — evidence that the updater ran, what it checked, and whether it suppressed or applied an update. Google documents the log locations but not stable message formats, so do not expect specific strings. Validate the policy separately with PlistBuddy (above) and confirm the installed Chrome version through Jamf inventory.
The Honest Caveat: Not Every App Plays This Way
Chrome is the well-behaved case. It exposes a documented managed-preference domain, so a profile is the clean answer. Do not assume every self-updating Mac app does.
- Some apps have no managed update key at all. Many direct-download apps update through their own bundled mechanism with no documented preference to disable it. For those, control moves up a layer: you deploy and own the version through packaging — ship a specific
.pkgvia a Jamf Pro policy, and re-package when you approve a new build. The app updates itself in place only if you let the installed build reach the internet on its terms; version discipline comes from your deployment cadence, not a profile. Check the vendor’s admin documentation before assuming there is no managed key — some vendors document one but don’t make it obvious. - The Mac App Store variant is a different animal. Mac App Store apps use Apple’s managed-app and App Store update mechanisms rather than Google Software Update. Available per-app controls depend on the app’s management and assignment method, macOS version, and Jamf Pro’s current support for managed distribution. Treat MAS and direct-download builds as separate deployment designs — the update mechanism follows the install source, and Chrome itself is only available as a direct download.
- Value semantics can shift. Google has revised its policy keys over time — the preference domain survived a complete updater rewrite. Treat the key reference above as current-at-writing and confirm against Google’s live documentation before you push a freeze to production. This is exactly why the profile belongs in a test ring first.
That last point is the discipline, not the exception. The macOS reflex a Windows admin has to build is: find the vendor’s managed domain, deploy it as a profile, then verify the effective value on-device before you trust it. Chrome is where you practice the pattern. Every other self-updating app you inherit is a variation on it.
The Takeaway
Google Software Update remains the updater on both platforms. Jamf Pro supplies the policy-delivery channel. The posture hierarchy for most fleets:
- Start with Extended Stable if you want a slower feature cadence with continued security coverage — it is the closest thing to a WSUS-style “test and approve” rhythm without the risk of falling behind on patches.
- Pin to a milestone with
TargetVersionPrefixwhen you need to hold a specific tested version while validating the next. The updater stays active and applies the pin. - Freeze with
UpdateDefault = 3only as a temporary, documented exception with a named owner and a removal date. A freeze disables security patches. It is not a long-term update strategy.
Deploy to a test smart group first. Verify with PlistBuddy, not assumptions. Record an expiry for any pin or freeze, and build a tested path back to supported Chrome updates.