Skip to content
Back to Blog

How to Distribute iOS Apps Without TestFlight

BetaDrop Team
Updated
8 min read
distribute iOS apps without TestFlightiOS OTA installitms-servicesmanifest plist ios install
How to Distribute iOS Apps Without TestFlight

Reviewed by Kaushal Rola

Founder, Immersive Mobile Designs — we build the OTA tooling this post describes.

Share:

Distributing an iOS app outside TestFlight means driving Apple's over-the-air install mechanism yourself. Four things have to be true at once: the IPA is signed with an ad hoc or enterprise profile, a manifest.plist describes it correctly, both files are served over valid HTTPS, and the tester opens an itms-services:// link in the system browser. Get all four right and installation takes seconds. Get any one wrong and the tap does nothing, usually with no error at all.

This page is the mechanics of those four links in the chain — what each one is, what it must contain, and which failure it causes when it is wrong. If the question you actually have is which distribution service to use, that is a different question and it is answered on our TestFlight alternative comparison, not here.

The install chain, end to end

Nothing about this flow is a workaround. itms-services is a documented Apple scheme, and it is the same mechanism enterprise fleets have used to push in-house apps for over a decade. What it is not is a way around code signing — every gate that applies to an App Store build still applies here.

  1. The tester taps an itms-services:// link on the device.
  2. iOS fetches the manifest.plist at the URL embedded in that link, over HTTPS.
  3. iOS reads the bundle identifier, version and IPA URL out of the manifest.
  4. iOS downloads the IPA from that URL, again over HTTPS, and installs it.
  5. The signature is validated against the profile inside the IPA. Ad hoc: is this device listed? Enterprise: has this certificate been trusted on this device?

Steps 1 through 4 are yours to get right. Step 5 belongs to Apple and no hosting arrangement changes it.

The itms-services URL

The link itself is a fixed shape with exactly one variable in it:

itms-services://?action=download-manifest&url=https://example.com/builds/manifest.plist

Three things go wrong here more than anything else:

  • The manifest URL is not URL-encoded. If your manifest URL carries a query string of its own — a signed S3 or CDN URL, for instance — its & and = characters terminate the outer url= parameter early and iOS fetches a truncated address. Percent-encode the whole inner URL.
  • The link is opened in an embedded browser. Chat and mail clients that render links in an in-app web view often do not hand unknown schemes to the OS. The tap does nothing. Safari, or any browser opened as a real app, handles it.
  • It is put behind a redirect. A shortener that returns an HTTP redirect to the itms-services URL is unreliable; put the scheme in the anchor's href directly, and let the landing page be an ordinary HTTPS page that contains it.

If the link is reaching testers and nothing happens, the itms-services troubleshooting guide walks the failure cases one at a time.

manifest.plist, key by key

The manifest is a plain property list. It exists so iOS can learn the bundle identifier and version before downloading, which is how it decides whether this is a new install or an update to something already on the device.

<?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://example.com/builds/MyApp.ipa</string>
        </dict>
        <dict>
          <key>kind</key>
          <string>display-image</string>
          <key>url</key>
          <string>https://example.com/builds/icon-57.png</string>
        </dict>
        <dict>
          <key>kind</key>
          <string>full-size-image</string>
          <key>url</key>
          <string>https://example.com/builds/icon-512.png</string>
        </dict>
      </array>
      <key>metadata</key>
      <dict>
        <key>bundle-identifier</key>
        <string>com.example.myapp</string>
        <key>bundle-version</key>
        <string>1.0.0</string>
        <key>kind</key>
        <string>software</string>
        <key>title</key>
        <string>My App</string>
      </dict>
    </dict>
  </array>
</dict>
</plist>

What each part is doing:

  • software-package — the only required asset. Its url is the IPA, and it must be reachable by the device with no authentication step, because iOS fetches it outside your web session and carries no cookies with it.
  • display-image and full-size-image — optional, and worth setting anyway. They are the artwork shown on the Home Screen placeholder while the download runs. Omit them and testers watch a grey square, which reads as a failed install often enough to generate support messages.
  • bundle-identifier — must match the identifier inside the IPA exactly. A mismatch is a silent failure: iOS installs against the identifier it was told about, and you get a placeholder icon that never resolves.
  • bundle-version — how the device decides whether to replace an existing copy. Ship two different builds with the same version string and testers can end up convinced your fix did not land when it simply was not installed.
  • title — the name in the install confirmation sheet. Cosmetic, but this is the string a tester reads before tapping Install.

HTTPS is not optional, and neither is the certificate

Both the manifest and the IPA must be served over HTTPS with a certificate from a trusted authority. This is the most common cause of an install link that produces no visible response whatsoever:

  • Plain http:// for either file fails.
  • A self-signed certificate fails, even if the device browser will let a human click past the warning — the install fetch has no human to ask.
  • An expired or incomplete chain fails. Missing intermediate certificates are a classic here, because desktop browsers often paper over them and iOS does not.
  • A redirect that drops to HTTP anywhere in the chain fails.

Serve the IPA as application/octet-stream and the manifest as text/xml or application/xml. A server that hands back text/plain for a .plist, or that renders it as a downloadable file, will break the fetch in step 2.

Ad hoc: the device list is baked into the signature

An ad hoc profile comes with the ordinary Apple Developer Program and names devices explicitly. A device that is not in the profile at export time cannot install the build, full stop — this is not something the host can relax.

Apple allows registering up to 100 devices per product family per membership year, and disabling a device during the year does not free the slot; the count resets at renewal (Apple, checked August 2026). The workflow for each new tester:

  1. Get the device identifier. On a Mac: connect the device, open Finder, select it, and click the serial number until the UDID appears. Remotely: send the tester our free UDID checker, which uses Apple's configuration-profile mechanism to have the device report its own identifier back. The cable routes and the two valid UDID shapes are on the UDID checker help page.
  2. Add it under Devices in the Apple Developer portal.
  3. Regenerate the ad hoc provisioning profile so it includes the new device.
  4. Re-export the IPA with that profile. The old IPA will never install on the new device, because the device list is part of what was signed.

To check what a profile already covers before you rebuild, drop it into the provisioning profile decoder. The concepts are covered in what ad hoc distribution is and provisioning profiles explained.

Enterprise: no device list, but a trust step

An enterprise profile registers no devices at all, which is why large internal fleets use it. Two things come with that.

First, eligibility. The Apple Developer Enterprise Program is 299 USD per membership year, and Apple restricts its use to “proprietary, in-house apps for internal use… distributed privately and securely to employees within the organization” (Apple, checked August 2026). It is not a route around the ad hoc device count for outside testers, and Apple revokes certificates used that way.

Second, the trust step. An enterprise build installs, then refuses to launch until someone approves the signing organization on that device: Settings → General → VPN & Device Management, tap the organization under ENTERPRISE APP, tap Trust. The device must be online, because iOS checks the certificate's revocation status before offering the option — which is why the Trust button appears greyed out on locked-down corporate Wi-Fi. The approval is stored per device and per certificate, so it is once per tester, not once per build. Send testers the untrusted enterprise developer fix rather than explaining it each time, and see the enterprise certificate distribution guide for the signing side.

How long an install link lives

Link lifetime is a hosting decision, not a property of the protocol, and it is worth being precise about because two separate clocks are running.

The hosting clock. On your own server, the link works until you delete the files. On a distribution service it works until that service's retention window closes: on BetaDrop a link starts at 3 days, a free account can extend it to 7 days, anonymous guest links last 24 hours, and paid retention runs to a year on Pro and 10 years on Studio. Treat any of these as an archive and you will find a dead link mid-cycle.

The signing clock. Independent of hosting, the provisioning profile inside the IPA expires — in-house profiles on a twelve-month clock, ad hoc profiles with the certificate that signed them. Once that lapses the app stops launching even for testers who already installed it, and no amount of keeping the download URL alive helps. The build has to be re-signed and redistributed.

When TestFlight is still the right answer

Driving this yourself is the right call for internal builds, client demos and same-day bug-fix verification. It is the wrong call in three situations, and knowing them up front saves discovering the ceiling halfway through a test cycle:

  • More testers than ad hoc provisioning can hold. Past a hundred devices per product family you are fighting arithmetic, and TestFlight registers no devices at all.
  • You want the release candidate to travel the pipeline it will ship on. A TestFlight build goes through the same submission path as your release and surfaces entitlement, privacy-manifest and metadata problems while they are still cheap to fix.
  • You need the feedback loop. Crash reports and in-app tester feedback come free with TestFlight; over the air, you build that yourself.

The cost on the other side is the queue: Apple sends the first build added to an external group through Beta App Review, and publishes only that 90% of App Review submissions are reviewed in under 24 hours — usually a day or less, never a guarantee. For the full side-by-side, including where the retention trade actually runs against you, see the TestFlight alternative comparison. If it is the tester cap rather than the wait that is biting, overcoming TestFlight's user limits covers the arithmetic.

When the tap fails

“Unable to Install”

Almost always signing rather than hosting: the device is not in the ad hoc profile, or the IPA was exported with a development profile, which cannot install over the air at all. Decode the profile and check the device list before re-uploading anything.

Nothing happens at all

Look at HTTPS and the URL first. An unreachable manifest, an untrusted certificate, a truncated url= parameter, or an embedded browser swallowing the scheme all produce exactly this: no error, no sheet, no icon.

Icon appears, stays grey, never finishes

The manifest was fetched but the IPA behind software-package was not. Check that URL independently, including from outside your network — a build sitting behind a VPN or a signed URL that expired between generating the manifest and the tester tapping it looks identical from the device.

Installs, then will not launch

Enterprise trust not yet granted, or a revoked certificate or expired profile. The first is fixed on the device in Settings; the other two require a re-signed build.

Doing it by hand, or not

Every step above is reproducible with any HTTPS web server: export the IPA, write the manifest, upload both, publish the link. That is worth doing once, because it is the only way the failure modes above stop being mysterious.

What makes it tedious is that the manifest has to be rewritten for every build — new version string, new IPA URL — and a typo produces a silent failure rather than an error. Uploading the signed IPA to BetaDrop generates the manifest from the binary's own metadata, serves both files over HTTPS, and returns the install link and a QR code, with a CLI for the same thing from a build script. It changes none of the signing rules on this page, and it is not the only tool that does this — the comparison of the ones that do is on the TestFlight alternative page. It removes the boilerplate between a signed build and a tester, and that is all it removes.

Frequently asked questions

What exactly does an itms-services link do?

It hands iOS the URL of a property list rather than the app itself. Tapping the link makes the system fetch your manifest.plist over HTTPS, read the bundle identifier, version and IPA URL out of it, download that IPA and install it. The scheme is a documented Apple mechanism, the same one enterprise deployments have used for years, and it does nothing to the code signature: an unsigned or wrongly signed build fails at install exactly as it would over any other route.

Why does over-the-air installation require HTTPS?

iOS refuses to fetch either the manifest or the IPA over plain HTTP, and it refuses a certificate it cannot validate, so a self-signed certificate fails the same way an http URL does. Both files have to sit behind a certificate from a trusted authority, and any redirect in the chain has to stay on HTTPS too. This is the single most common reason a manifest that looks correct produces nothing at all when tapped.

How many devices can install an ad hoc build?

Apple lets a membership register up to 100 devices per product family per membership year, and only devices already in the provisioning profile when you exported can install. Adding a tester later means registering the identifier, regenerating the profile and re-exporting the IPA, because the device list is baked into the signature. Disabling a device mid-year does not return the slot.

Do testers need to trust the developer certificate?

Only for enterprise-signed builds. Those install fine but refuse to launch until someone approves the signing organization once under Settings, General, VPN and Device Management, and the device has to be online for that approval to go through. Ad hoc builds signed with an ordinary Apple Distribution certificate skip the step entirely, which is one of the practical reasons to prefer ad hoc for a small external group.

How long does an over-the-air install link stay live?

For as long as whoever hosts the files keeps serving them, which is a hosting decision rather than a property of the protocol. On your own server that is until you delete the IPA or the certificate lapses. On BetaDrop a link starts at 3 days, a free account can extend it to 7 days, anonymous guest links last 24 hours, and paid retention runs to a year on Pro and 10 years on Studio. The provisioning profile has its own separate expiry that no host controls.

Why does the install link open Safari instead of installing?

The itms-services scheme has to be handed to the system browser. Embedded in-app browsers, such as the one Slack or a mail client opens a link in, frequently do not pass unknown schemes to the OS, so the tap silently does nothing or lands on a blank page. Telling testers to open the link in Safari, or to long-press and copy it out of the chat app first, resolves almost every report of this.

Ready to Distribute Your App?

Upload your IPA or APK file and get a shareable install link in seconds. Your first upload needs no account. Completely free.