guideonce.
← All guides

macOS · Endpoint Management

Managing Browser Auto Updates Across a Mac Fleet with One Configuration Profile

A practical walkthrough of standardising browser update behaviour across Chrome, Brave, Edge and Firefox using Jamf Pro and Microsoft Intune.

Why We Did This

Keeping browsers up to date is one of the most important things you can do for endpoint security. Browsers are the single most exposed piece of software on most machines. They render untrusted content all day long, they hold session tokens and saved credentials, and they are the first thing attackers target with zero day exploits. A browser that is two or three versions behind is a genuine risk, not a cosmetic one.

The challenge in a mixed fleet is that no two browsers handle updates the same way, and users do not reliably restart their browser when an update is waiting. It is very common to find a machine where the update has downloaded days ago but the user has simply never quit the browser, so the patched version is sitting on disk doing nothing.

We wanted three things:

  1. Every managed browser updates itself automatically in the background without relying on the user.
  2. Once an update is downloaded, the user is given a clear and consistent prompt to relaunch, with a sensible grace period before it happens for them.
  3. The whole thing is delivered through our existing MDM tooling so it scales across the fleet and is easy to audit.

We run a split environment. Some Macs are managed by Jamf Pro and some by Microsoft Intune, mostly for historical reasons. So whatever we built had to work in both, and produce the same end result regardless of which MDM happened to own the device. This post walks through exactly how we did it, the gotchas we hit, and how you can verify and troubleshoot it yourself.

A Quick Primer on How Browsers Update on macOS

Before touching any configuration, it helps to understand that "browser updates" actually involves two completely separate concerns, controlled by two completely separate things.

The first is update delivery. This is the mechanism that actually downloads and installs a new version. Crucially, this is almost never handled by the browser itself. It is handled by a dedicated background updater that the vendor ships alongside the browser.

The second is the relaunch experience. This is what the user sees once an update has been downloaded and is waiting to be applied. This part is handled by the browser, through its own policy engine.

Getting these two mixed up is the single most common mistake, and it is the reason a lot of people think they have enabled something when they have not. You can have updates downloading perfectly while the user never gets told to restart, or you can have a relaunch prompt configured for a browser whose updater you never actually enabled.

Here is how the major browsers break down. Chrome and Brave both use Google's update engine, known as Keystone. Even though Brave is a separate company and a separate browser, it is built on Chromium and it registers with Keystone for updates just like Chrome does, so a single Keystone policy can govern both at once. Edge uses Microsoft AutoUpdate, usually shortened to MAU, the same updater that keeps the rest of Office current, so on most managed Macs it is already present and often already configured. Firefox is the odd one out. It does not use a separate updater daemon in the same way. Its update behaviour is controlled directly through its own enterprise policy engine, which has to be explicitly switched on before any policy is honoured at all.

The relaunch experience is more consistent. Chrome, Brave and Edge are all Chromium based, so they all understand the same two policy keys for controlling relaunch behaviour. Firefox does not support these keys and manages its own restart prompting internally.

The Policy Keys We Used

Everything below is delivered as a property list, or plist, which is just an XML file of key and value pairs. Each block targets a specific preference domain, the reverse domain identifier that tells macOS which application the settings belong to.

Keystone, for Chrome and Brave update delivery

Preference domain: com.google.Keystone

<dict>
    <key>updatePolicies</key>
    <dict>
        <key>global</key>
        <dict>
            <key>UpdateDefault</key>
            <integer>1</integer>
        </dict>
        <key>com.google.Chrome</key>
        <dict>
            <key>Update</key>
            <integer>1</integer>
        </dict>
    </dict>
</dict>

UpdateDefault set to 1 is the global default for everything Keystone manages, which enables automatic updates. The Chrome specific Update key set to 1 makes the intent explicit. Because Brave also registers with Keystone, the global default covers it too.

Integer values for update keys

0 disabled · 1 automatic updates enabled · 2 manual updates only · 3 automatic disabled but manual through the updater still allowed. We used 1 everywhere.

Chrome browser relaunch behaviour

Preference domain: com.google.Chrome

<dict>
    <key>RelaunchNotification</key>
    <integer>2</integer>
    <key>RelaunchNotificationPeriod</key>
    <integer>14400000</integer>
</dict>

RelaunchNotification controls how forceful the prompt is: 0 none, 1 recommended, 2 required, which will eventually force the relaunch. RelaunchNotificationPeriod is the grace period in milliseconds. We chose 14400000, which is four hours, enough time to finish what you are doing while still guaranteeing the patch applies the same working day. Other common values are 3600000 for one hour, 7200000 for two hours, and 86400000 for a full day.

Brave, update and relaunch combined

Preference domain: com.brave.Browser. Because Brave uses Keystone for updates and understands the Chromium relaunch keys, we put both concerns in one block.

<dict>
    <key>Update</key>
    <integer>1</integer>
    <key>RelaunchNotification</key>
    <integer>2</integer>
    <key>RelaunchNotificationPeriod</key>
    <integer>14400000</integer>
</dict>

Edge browser relaunch behaviour

Preference domain: com.microsoft.Edge

<dict>
    <key>RelaunchNotification</key>
    <integer>2</integer>
    <key>RelaunchNotificationPeriod</key>
    <integer>14400000</integer>
</dict>

This block only covers the relaunch experience. The actual update delivery for Edge is handled by Microsoft AutoUpdate, which in our environment was already configured by a separate existing profile. We deliberately left that where it was rather than duplicating it. More on that decision shortly.

Firefox, enterprise policies and update delivery

Preference domain: org.mozilla.firefox

<dict>
    <key>EnterprisePoliciesEnabled</key>
    <true/>
    <key>AppAutoUpdate</key>
    <true/>
</dict>
The Firefox master switch

Firefox will ignore every other policy you send it on macOS unless EnterprisePoliciesEnabled is set to true first. It is effectively the master switch that tells Firefox to start honouring managed configuration at all.

Note that these are boolean true and false values, written as <true/> and <false/>, not integers. This trips people up because the Chromium browsers all use integers for their equivalent settings. As a belt and braces measure you can also add DisableAppUpdate set to false, which explicitly undoes any earlier policy that might have disabled updates. Firefox does not support the relaunch keys. If you add them they are simply ignored, because Firefox handles its own restart prompting on its own schedule.

A Note on Other Browsers

We focused on the four browsers that are actually in use across our fleet, plus the two underlying updaters. If your environment includes others, the picture is mixed. Most other Chromium based browsers such as Vivaldi support the same Chromium policy keys and can be managed in a very similar way using their own preference domain. Arc has only limited plist based settings and no full policy catalogue, so it can be lightly configured but not managed to the same standard. Opera is worth calling out specifically because, despite being Chromium based, it has chosen not to support Google's policy management structure at all on either macOS or Windows, so there is no supported way to manage its updates through a configuration profile. Safari is updated through macOS system updates rather than a browser specific updater, so it sits outside this entirely and is governed by your operating system update policy instead.

The general rule of thumb is that Chromium based browsers usually share policy keys, while anything with its own engine or its own ideas about management needs to be checked individually against the vendor's documentation before you assume anything works.

Putting It All in One Profile

We chose to deliver all of the browser policies in a single configuration profile with multiple payloads, rather than scattering them across several profiles. It keeps everything related to browser behaviour in one auditable place, it is scoped once, and anyone looking at it can see the whole browser story at a glance. The one thing we deliberately kept separate was the Microsoft AutoUpdate configuration, because it already existed in its own profile from an earlier Microsoft deployment.

#Preference domainPurpose
1com.google.KeystoneChrome and Brave update delivery
2com.google.ChromeChrome relaunch behaviour
3com.brave.BrowserBrave update and relaunch behaviour
4com.microsoft.EdgeEdge relaunch behaviour
5org.mozilla.firefoxFirefox enterprise policies and update delivery

Deploying It in Jamf Pro

This is the path we used first. In Jamf Pro, browser settings like these are delivered through a configuration profile using the Application and Custom Settings payload.

  1. In Jamf Pro, go to Computers, then Configuration Profiles, and choose New.
  2. Under General, give the profile a clear descriptive name such as "Browser Update and Relaunch Policy". Use the description field to note that Microsoft AutoUpdate settings live in a separate profile, and to cross reference related profiles. Future you will be grateful for this.
  3. Set the level so the profile applies at the computer level, since these are machine wide settings.
  4. In the left hand payload list, find Application and Custom Settings and select it.
  5. Click Add, then choose the Upload option, sometimes labelled external applications or custom settings depending on your Jamf version.
  6. In the Preference Domain field, enter the first domain, com.google.Keystone.
  7. Paste the inner content for that domain. When pasting directly into Jamf's field rather than uploading a file, Jamf only wants the inner dictionary, so you start at <dict> and end at </dict>. No XML declaration or plist wrapper. If you upload an actual file instead, then you do need the full wrapper.
  8. Repeat the previous three steps for each remaining domain, adding a new payload each time, until you have five.
  9. Go to the Scope tab. For the first test, scope to a single machine, ideally your own, rather than a group.
  10. Save the profile.

Once saved, the profile is delivered the next time the target Mac checks in, which can take up to around eight hours, or you can force it from the device.

# Force a check in on a Jamf managed Mac
sudo jamf recon
sudo jamf mdm
Consistency tip

Jamf's field wants only the inner dictionary when you paste, but a profile uploaded as a file uses the full XML wrapper. Both are valid. Just be consistent within any single payload and do not mix the two.

Deploying It in Intune

The Intune path achieves the same result, but the terminology and file handling differ. Intune delivers custom plist settings through a Preference file configuration profile, which expects a complete plist file rather than pasted snippets.

  1. In the Intune admin centre, go to Devices, then Configuration, and create a new policy.
  2. Set the platform to macOS and the profile type to Preference file, found under the templates.
  3. Give the profile a name and, as with Jamf, use the description to cross reference related profiles.
  4. Set the Preference domain name to the domain you are targeting, for example com.google.Keystone.
  5. Upload the property list file for that domain. This is where Intune differs from Jamf. It wants a complete and valid plist file, including the XML declaration, the document type definition, the plist wrapper and the dictionary inside.
  6. A Preference file profile handles one preference domain per profile. This is the key structural difference from Jamf. Where Jamf stacks multiple payloads into one profile, in Intune you generally create one Preference file profile per domain. So the equivalent of our single Jamf profile is a small set of Intune profiles, one each for Keystone, Chrome, Brave, Edge and Firefox. Name them consistently so they group together.
  7. Assign the profile to the appropriate group. For first testing, assign to a group containing only your test machine.
  8. Save.

A full file, as Intune expects it, looks like this:

<?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>updatePolicies</key>
    <dict>
        <key>global</key>
        <dict>
            <key>UpdateDefault</key>
            <integer>1</integer>
        </dict>
        <key>com.google.Chrome</key>
        <dict>
            <key>Update</key>
            <integer>1</integer>
        </dict>
    </dict>
</dict>
</plist>

To force an Intune managed Mac to check in sooner, use the Company Portal app and trigger a sync, or run the following. The end result on the device is identical to the Jamf delivery. The settings land in exactly the same place, /Library/Managed Preferences/, regardless of which MDM sent them.

sudo profiles renew -type enrollment

Finding Conflicts Before They Happen

This is the part that saved us from a real problem, and it is the step most people skip. Before pushing any new policy, check whether something is already managing that preference domain. If two profiles both try to manage the same domain you get unpredictable behaviour, and you may silently overwrite settings another team relies on.

The quickest check is to read the managed preferences directly on a representative machine. If a domain reports that it does not exist, you are starting from a clean slate. If it returns values, something is already managing it and you need to find out what before you add your own.

defaults read /Library/Managed\ Preferences/com.google.Keystone.plist
defaults read /Library/Managed\ Preferences/com.google.Chrome.plist
defaults read /Library/Managed\ Preferences/com.brave.Browser.plist
defaults read /Library/Managed\ Preferences/com.microsoft.Edge.plist
defaults read /Library/Managed\ Preferences/com.microsoft.autoupdate2.plist
defaults read /Library/Managed\ Preferences/org.mozilla.firefox.plist

When we ran this, every browser domain came back empty, which was exactly what we wanted. But the Microsoft AutoUpdate domain, com.microsoft.autoupdate2, came back with existing values. It was already set to automatically download updates, already on the standard release channel, and it had been delivered weeks earlier. An existing profile, almost certainly part of a Microsoft 365 deployment, was already managing Edge's update delivery.

This is exactly the situation you want to catch before you act, not after. If we had blindly created our own Microsoft AutoUpdate payload we would have ended up with two profiles fighting over the same domain. Instead, we left the existing one untouched, confirmed it did not contain an Edge browser payload that would clash with our relaunch settings, and simply added our Edge relaunch behaviour to our own profile. Two profiles, two different domains, no conflict.

To find which profile owns an existing setting, dump all installed profiles and search through them, and check the file's age and ownership for clues about where it came from.

sudo profiles show -all > /tmp/profiles_dump.txt
cat /tmp/profiles_dump.txt | grep -i "autoupdate"
ls -la "/Library/Managed Preferences/com.microsoft.autoupdate2.plist"

The modification date told us the policy predated our work, and the root ownership confirmed it was MDM managed rather than something set locally. The fastest way to identify the exact source profile, though, is to look at the device record in your MDM console and read through the list of profiles scoped to it. It is also worth confirming which browsers are actually installed:

ls /Applications | grep -i -E "chrome|brave|firefox|edge"

We chose to push the Brave policy even though Brave was not installed on the test machine. In a developer heavy organisation people install Brave and Firefox of their own accord all the time, so having the policy already waiting means it applies the moment the browser appears. A policy for an absent application does no harm. It just sits dormant.

Verifying It Worked

Once the profile has landed, verify at two levels. First confirm the raw managed preferences are present on disk, then confirm the browser itself is actually honouring them.

sudo profiles show -all
defaults read /Library/Managed\ Preferences/com.google.Keystone.plist
defaults read /Library/Managed\ Preferences/com.google.Chrome.plist
defaults read /Library/Managed\ Preferences/com.brave.Browser.plist
defaults read /Library/Managed\ Preferences/com.microsoft.Edge.plist
defaults read /Library/Managed\ Preferences/org.mozilla.firefox.plist

You should see your values reflected back. For Keystone, the update policies with values of 1. For each Chromium browser, the relaunch notification set to 2 and the period set to 14400000. For Firefox, enterprise policies enabled and auto update enabled. Then confirm inside each browser, which is the real proof that the application is reading the policy.

BrowserPolicy page
Google Chromechrome://policy
Microsoft Edgeedge://policy
Bravebrave://policy
Firefoxabout:policies

On the policy page you should see your keys listed with the correct values and a status of OK. This is the same universal check regardless of whether Jamf or Intune delivered the profile, which makes it especially handy in a split environment.

Troubleshooting and Gotchas

Keystone disables the manual update button in Chrome

After deployment, Chrome's about page reported that automatic updates were enabled but that manual updates were disabled by the administrator. This is expected. Once Keystone is managing updates at the system level, Chrome hands over control and intentionally hides the manual check button, because Keystone is now the authority. The two are designed to be mutually exclusive. Since updates were happening automatically and the relaunch prompt was working, we left this as is. Users can still see their current version on the about page, they just cannot force a check from there.

A pending update can mask the real state

When we first looked, Chrome was prompting for a relaunch and showing the disabled manual button. We relaunched first to clear the pending update before drawing any conclusions. The browser updated cleanly and confirmed the policy was doing its job. Always clear a pending relaunch before troubleshooting, otherwise you may be looking at a transient state rather than the steady state.

Firefox ignores everything until enterprise policies are enabled

If your Firefox policies appear to do nothing, the first thing to check is that EnterprisePoliciesEnabled is set to true. Without it, Firefox quietly ignores the rest. Confirm what Firefox has accepted at about:policies.

Boolean versus integer

Firefox uses boolean true and false values where the Chromium browsers use integers. Sending the wrong data type will not work. Match the data type to the browser.

The managed file and the user file are different

When investigating Microsoft AutoUpdate we found two files with the same name in different locations. The one under /Library/Managed Preferences/ is the MDM delivered policy and is owned by root. The one under the user's own Library Preferences folder is written by the application itself as it runs and reflects local state. The managed one is the policy, the user one is just the app's own bookkeeping.

Profiles can take time to appear

If a freshly scoped profile is not showing up, give it time to check in or force the check in manually. A profile that is scoped correctly but has not been collected yet looks identical to one that failed, so rule out timing before assuming something is broken.

The Command Reference

A consolidated list of everything used above, for quick reference.

Profile management

# Show all MDM delivered profiles and their full content
sudo profiles show -all

# Show a shorter summary list of installed profiles
sudo profiles list

# Show MDM enrolment status
sudo profiles status -type enrollment

# Force a check in on a Jamf managed Mac
sudo jamf recon
sudo jamf mdm

# Force a check in on an Intune managed Mac
sudo profiles renew -type enrollment

Reading managed preferences

defaults read /Library/Managed\ Preferences/com.google.Keystone.plist
defaults read /Library/Managed\ Preferences/com.google.Chrome.plist
defaults read /Library/Managed\ Preferences/com.brave.Browser.plist
defaults read /Library/Managed\ Preferences/com.microsoft.Edge.plist
defaults read /Library/Managed\ Preferences/com.microsoft.autoupdate2.plist
defaults read /Library/Managed\ Preferences/org.mozilla.firefox.plist

Checking for conflicts and sources

# Dump all profiles to a file for searching
sudo profiles show -all > /tmp/profiles_dump.txt

# Search the dump for a specific domain
cat /tmp/profiles_dump.txt | grep -i "autoupdate"

# Search for anything Microsoft related
sudo profiles show -all | grep -i "microsoft"

# Check modification date and ownership of a managed file
ls -la "/Library/Managed Preferences/com.microsoft.autoupdate2.plist"

# Get full file metadata including all timestamps
stat "/Library/Managed Preferences/com.microsoft.autoupdate2.plist"

# Check for a user level version of a preference file
ls -la ~/Library/Preferences/com.microsoft.autoupdate2.plist

Checking installed browsers

ls /Applications | grep -i -E "chrome|brave|firefox|edge"

Closing Thoughts

The actual configuration here is not complicated once you understand the split between update delivery and relaunch behaviour, and once you know which mechanism each browser uses. The real value is in the discipline around it. Check what already exists before you push anything. Keep related policies cross referenced so the next person can follow the trail. Test on a single machine before you touch the fleet. Verify at both the file level and the browser level. And do not be surprised when a managed setting changes the user experience in a way you did not expect, such as the manual update button disappearing, because that is usually the policy working as designed rather than a fault.

Done well, the payoff is a fleet where every browser keeps itself current automatically, users get a fair and consistent nudge to restart, and you have a clear and auditable record of exactly what is being enforced and why.

← All guides