Updating 1000's of iOS Devices - Update Plan

New Contributor III

Hi All,

Looking for some input from Admins who manage very large iOS estates.

The current setup i manage from Jamf is around 5000+ active iOS devices.
Now these devices need to be updated to at least iOS 13 with a view to moving them to iOS 14.

Most of them are on iOS 12 with around 20% on iOS 13.

These are all store based devices with internet breakdown via DSL. I was thinking of caching servers, but that wouldn't make a difference as it goes out over the DSL anyway.

The only way i can think of is to break the updates down into smaller smart groups and update on a rolling schedule with Remote Commands.


New Contributor

35k devices here. JAMF Fails sending commands to device groups of over 500.

We're also seeing a large % of failure of updates on shared iPads and on 1:1s, again we have an open ticket with JAMF on this.

Some devices are get bricked and have to do a DFU restore, we're seeing this on devices on IOS 11 and 12. We're having a major push to move the shared iPads to iOS 14 but with the failure rate needing major chunk of onsite technicians time to restore we're holding off until we can do it remotely.

Devices on IOS13.6/7 are also failing remote update commands.

My advice is to start slow with onsite help available to mop up the mess. Do it in chunks of 500 or less so JAMF doesn't time out. If you can set up local caching servers, that will help caching the ~3GB iPadOS/iOS image needed.

Contributor III

Hey @simon.brooke I've used a couple different API scripts I wrote to send MDM commands to large groups of iPads (sometimes up to 9,000 of them) without overwhelming the jamfPro server. I add a sleep 1 between each command to slow down the process.

Here is the bash script I used to send the command to update the iPadOS version to the latest available based on device eligibility. The script wants the group id of a static or smart group to send the commands to. You can grab the id of the group from the URL while viewing the group in jamfPro. https://github.com/baconflip/jamf_scripts/blob/master/mobile_group_update_iOS.sh

You'll have to specify the jamfPro account (api) user/password, group id and jamf url in the script. You'll need to use a jamfPro account that has some basic read permissions for the mobile devices and also the "Send Mobile Device Remote Command to Download and Install iOS Update" permission under "Jamf Pro Server Actions" section of the permissions for the account. It's best practice to use an api specific account that has the absolute bare minimum required for the script to function.

If you refer to the API help page (https://jss.company.com/api) you can see under the classic API you can either update the iPad to a specific iPadOS version or the latest based on the device. You can edit the script to adjust for that if you'd like.

Once you've downloaded this script and made the variable adjustments for your environment I would suggest creating a smart or static group of 1 device to test. Then inputting that group id in the script. Then you open terminal.app on your Mac and execute the script with bash name_of_script.sh and watch the output to confirm it worked. Then you can view the history tab of the device record you sent the command to and verify it went through.


Hi @ssrussell Thanks for sharing that script. I've run into an issue though. For some reason, when I run the script it does not find any devices in the group. If I use the same API creds on the /JSSResource/ page, I can find the device, and see that it is indeed in the group. Any thoughts/suggestions? Thanks!

Not applicable

@nberanger As a quick test you can use an API account that has full privileges and see if that works. If it doesn't you can at least rule out the jss api account as the culprit.

Are you using the the group ID of a static or smart group? I've really only tested this with a Smart Group, but I don't see why a Static Group wouldn't work. If it isn't working, try creating a Smart Group and using criteria to scope one device to test.

If you run the script with bash -x path_to_script.sh the -x will give us a verbose output of the script with might help troubleshoot the problem.

You can also try just running this command in terminal by hand-inputting the variables and see the output. That might also shed some light on what is happening.

curl -sku jssuser:'jss_passw0rd' "https://jss.company.com/JSSResource/mobiledevicecommands/command/ScheduleOSUpdate/2/id/mobile_id_here" -X POST

You can grab the mobile_id of an iPad from the URL as well.


Thanks for the quick response. Turns out that if I hardcode the user creds into the script, it works fine. When I prompt the user for the creds instead, that is when it comes back with no members in the group. Oddly enough, it also does this if I enter just any random infor for the username and password. So it seems like it isn't actually accepting the user input.

This is what I was using to prompt the user to enter the creds:

##### Prompt for API credentials to execute script
# Read Username
printf "Please enter API username:"
read -r api_user

# Read Password (the -s flag hides the input from showing on the screen)
printf "Please enter API user password (input hidden on screen):"
read -rs api_pass


Just updated how I was getting the password, went with this code instead

stty -echo
printf "Please enter API user password (input hidden on screen): "
read api_pass
stty echo
printf "

That seems to have sorted things out, as I am now seeing the script report back on the correct number of devices in the group. I will continue to test tomorrow when my colleague is back in the office, with the test devices.

Thanks again @_ssrussell

Release Candidate Programs Tester

I am running into an issue with this script.  Not sure what to do to fix it.  The error I am getting is:


/usr/bin/xpath5.30 [options] -e query [-e query...] [filename...]


If no filenames are given, supply XML on STDIN. You must provide at

least one query. Each supplementary query is done in order, the

previous query giving the context of the next one.




-q quiet, only output the resulting PATH.

-s suffix, use suffix instead of linefeed.

-p postfix, use prefix instead of nothing.

-n Don't use an external DTD.



Try adding the “-e” after ‘xpath’ 


xpath -e //…

Release Candidate Programs Tester

Ill give that a try.  I was able to fix it by putting this in the original script.


{ # the xpath tool changes in Big Sur if [[ $(sw_vers -buildVersion) > "20A" ]]; then /usr/bin/xpath -e "$@"


/usr/bin/xpath "$@"