How to install an IPA over the air (OTA) — complete 2026 guide
What's in this guide
If you've ever shipped an iOS build to someone outside the App Store, you've hit the question: "How do I get this .ipa onto a real iPhone?"
The official answer is TestFlight. It works, but it has friction: testers need an Apple ID, an invitation email, the TestFlight app installed, and Apple has to approve each build first. For internal testing, ad-hoc beta distribution, or sharing a build with a client at 11pm before a demo, that's a lot of steps.
The shortcut is over-the-air (OTA) installation. It's been built into iOS since iOS 4. The user opens a link in Safari, taps Install, and the app downloads directly to their home screen — no App Store, no TestFlight. This guide walks through exactly how it works and how to set it up.
Why OTA install at all?
- No App Store review. Apple isn't in the loop. You ship a build, the tester installs it, end of story. Builds are live the moment your CI finishes.
- No invitation flow. The tester doesn't need an Apple Developer account, an App Store Connect invite, or even an Apple ID logged into the device.
- Same install UI as TestFlight or the App Store. The system install sheet that iOS shows is the same flow users already understand.
- One link, multiple devices. Send the same URL to ten testers. Each one's device just needs to be in the signed provisioning profile.
The catch: every device that installs your IPA must already be registered in the provisioning profile at the time you signed the build. Apple's anti-piracy mechanism. Add a tester after the fact and you need to re-sign and re-share.
What you need before you start
- A signed IPA. Either an ad-hoc build (signed against an ad-hoc provisioning profile with specific UDIDs listed) or an in-house enterprise build (signed against an Apple Developer Enterprise certificate). App Store builds don't work for OTA — they're locked to App Store distribution.
- The tester's UDID, registered in the profile. Apple checks this at install time. If their UDID isn't on the list, iOS refuses to launch the app (it'll install fine; it just won't open). Need a tester's UDID? Send them to our UDID lookup tool.
- HTTPS hosting. Both the manifest plist and the IPA file must be served over HTTPS. iOS refuses plain HTTP and self-signed certs since iOS 7.1. Let's Encrypt is free; CloudFront, S3, GitHub Pages, Vercel, and Netlify all work. Hosted on your own server? Make sure TLS is set up.
- A modern enough iOS. The mechanism has been around since iOS 4, but iOS 12.2+ tightened the install confirmation flow. The flow works on every iOS version anyone is currently running.
The manifest plist explained
The manifest is an XML property-list file that tells iOS three things: where the IPA is, what's inside it, and what to call it on the home screen. 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>items</key>
<array>
<dict>
<key>assets</key>
<array>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>https://your-host.com/path/to/YourApp.ipa</string>
</dict>
<dict>
<key>kind</key>
<string>display-image</string>
<key>url</key>
<string>https://your-host.com/path/to/icon-57.png</string>
</dict>
<dict>
<key>kind</key>
<string>full-size-image</string>
<key>url</key>
<string>https://your-host.com/path/to/icon-512.png</string>
</dict>
</array>
<key>metadata</key>
<dict>
<key>bundle-identifier</key>
<string>com.yourcompany.yourapp</string>
<key>bundle-version</key>
<string>1.2.0</string>
<key>kind</key>
<string>software</string>
<key>title</key>
<string>Your App Name</string>
</dict>
</dict>
</array>
</dict>
</plist>
The three things iOS reads from this:
software-packageURL — where to download the actual.ipabytes.display-image— small (57×57) icon shown during install. Optional.full-size-image— large icon (512×512). Also optional, but bothicons let the install sheet look polished.bundle-identifier+bundle-version— must match the IPA'sInfo.plistexactly, or iOS aborts.title— display name on the install sheet.
Save the plist with a.plistextension and serve it withContent-Type: application/xml(or omit the content-type; most servers default to a reasonable value).
The itms-services:// URL scheme
Once the manifest is hosted, you wrap its URL in iOS's special install URL scheme:
itms-services://?action=download-manifest&url=https://your-host.com/path/to/manifest.plist
That URL is what you put in the install button on your share page:
<a href="itms-services://?action=download-manifest&url=https://your-host.com/manifest.plist">
Install on iPhone
</a>
When the tester taps it in Safari, iOS recognises the scheme, fetches the manifest, fetches the IPA, and offers to install. Open the same link in Chrome or Edge on iOS and it'll silently fail — only Safari is wired to handle itms-services://.
Where to host the IPA and manifest
You need somewhere HTTPS-accessible. The cheapest options for ad-hoc:
- App On The Go — purpose-built for this. Free up to 100 MB, signed plist generated automatically, QR code included. Upload your IPA and skip this whole guide.
- Amazon S3 + CloudFront — bulletproof. ~$0.50/month for occasional ad-hoc shares. Make the bucket public-read, point CloudFront at it, you're done.
- GitHub Pages — free, but a 100 MB file size limit per file and the obvious privacy issue (your IPA is public on the open internet).
- Vercel / Netlify static hosting — works the same way as GitHub Pages but with higher size caps.
- Your own server — fine as long as TLS is set up. Apache and Nginx both work; just serve the
.ipaand.plistas static files.
What the tester sees, step by step
- Tester opens your share link in Safari.
- Page renders with a "Install" button (which is just an
<a href="itms-services://...">). - Tester taps Install.
- iOS shows a confirmation: "This website is trying to install YourApp". Tester taps Install.
- The icon appears on the home screen with a progress ring. App downloads in the background.
- (First install only) Tester opens the app → iOS shows "Untrusted Developer". Tester goes to Settings → General → VPN & Device Management, taps the developer name, taps Trust. Subsequent installs from the same developer skip this step.
Troubleshooting
"Unable to download app" / "Could not be installed at this time"
- Manifest URL isn't HTTPS, or the cert isn't trusted by iOS. Test the URL in Safari directly — if Safari shows a security warning, fix that first.
- IPA URL inside the manifest also isn't HTTPS. Both must be.
- The
bundle-identifierorbundle-versionin the manifest doesn't match the IPA'sInfo.plist.
App installs but won't launch ("This app cannot be installed because its integrity could not be verified")
- The tester's UDID isn't on the provisioning profile. They need to send you their UDID, you re-sign the build with their UDID added, and re-share.
- The provisioning profile has expired. Profiles last 1 year max — check with our .mobileprovision decoder.
"Untrusted Developer" on first launch
- Normal. Tester goes to Settings → General → VPN & Device Management → [Developer name] → Trust. iOS 16+ phrases this slightly differently but the flow is the same.
The link doesn't do anything in Chrome / Firefox / Edge on iOS
- The
itms-services://scheme is only wired up in Safari. Tell testers to open the link in Safari specifically.
The easier way: skip all of this
If you're not building a hosting pipeline for the long haul, the manual approach above is more friction than it's worth for ad-hoc shares. App On The Go was built specifically to remove every step here:
- Drop an IPA → we host it, generate the signed plist, create the
itms-services://URL. - Get a short URL + QR code in under 3 seconds.
- Send it to testers. They open it on their device, tap install. Done.
- Free up to 100 MB without signup, free above with a 30-second account.
Related reading
- How to share an APK with testers — the Android counterpart to this guide.
- Fastlane integration for OTA distribution — auto-share every build from your CI.
- Decode a .mobileprovision online — confirm UDIDs are on your profile.
- What is my UDID? — for testers who need to send you their UDID.