Vedran Burojevic
← WritingOctober 18, 2026

Provisioning and signing failures: a field guide for indie iOS releases

A practical field guide to diagnosing certificate, profile, entitlement, and capability failures before they consume a release day.

On this page

Code signing failures rarely happen on a quiet Tuesday. They happen on release day, after the build passed, after the screenshots were approved, and after you told someone the update would be live tonight.

Most of them come from a small set of causes. The error messages are bad at naming those causes, so teams end up toggling "Automatically manage signing", deleting profiles, and restarting Xcode until something works. That sometimes fixes the build. It never tells you what was wrong, so the same failure comes back on the next release or the next CI machine.

This guide is about diagnosing signing problems from evidence: what to inspect, in what order, and which fixes actually address the cause.

1. Know the four things that have to agree

A signed iOS build is valid when four pieces agree with each other:

  • the certificate: the signing identity, with its private key in a keychain on the machine doing the signing
  • the provisioning profile: which certificates, App ID, devices, and entitlements Apple allows for this build
  • the entitlements file: what your app claims it needs, such as App Groups, iCloud containers, push, or Associated Domains
  • the App ID capabilities: what is enabled for the bundle identifier in the developer portal

Nearly every signing error is one of these disagreeing with another. A certificate exists but its private key does not. A profile was generated before a capability was enabled. The entitlements file asks for an App Group that the extension's App ID never received.

Once you think in terms of which pair disagrees, the error messages become readable. "Provisioning profile doesn't include the com.apple.security.application-groups entitlement" means the profile and the entitlements file disagree. The fix is to regenerate the profile after the capability is enabled, not to remove the entitlement until the build goes green.

2. Check the certificate and its private key first

A certificate without its private key looks fine in the developer portal and useless on your machine.

List the identities that can actually sign:

security find-identity -v -p codesigning

If the expected Apple Distribution identity is missing from this list, nothing downstream matters. Common causes:

  • the certificate was created on another Mac and only the .cer was downloaded, without the private key
  • the certificate expired; distribution certificates last one year
  • the private key is in a keychain that is not on the search list, which is common on CI
  • someone revoked the certificate to "clean up" the team account

Revocation deserves a note. Revoking a distribution certificate does not pull your live App Store builds. It does invalidate every profile that referenced it, so CI and every other machine signing with it will break until profiles are regenerated. Do it on purpose, not as a troubleshooting step.

For a small team, keep one distribution identity, export it as a .p12 with its private key, and store it in your secret manager. One documented identity beats five half-remembered ones scattered across old laptops.

3. Read the profile instead of trusting its name

Profile names lie. Packerly AppStore might be the one from last year, generated before you added push notifications.

Decode the profile and read what it actually grants:

security cms -D -i Packerly_AppStore.mobileprovision > profile.plist
plutil -p profile.plist

Check these fields:

  • ExpirationDate: profiles expire, and an expired profile fails late in the process
  • Entitlements: the capabilities this profile allows, including application-identifier and com.apple.developer.team-identifier
  • DeveloperCertificates: which certificates may sign with this profile
  • ProvisionedDevices: present for development and ad hoc profiles, absent for App Store profiles

Xcode keeps downloaded profiles on disk. Recent versions use ~/Library/Developer/Xcode/UserData/Provisioning Profiles, and older setups use ~/Library/MobileDevice/Provisioning Profiles. When a build picks the wrong profile, it is usually because several profiles for the same bundle ID sit in that folder and the stale one wins. Delete the stale profiles deliberately after checking what they are. Deleting the whole folder and hoping is the less informative version of the same move.

4. Compare entitlements between source, profile, and binary

Entitlement mismatches cause the most confusing failures, because the build can succeed and the problem shows up later as an upload rejection, a missing push token, or an App Group container that silently returns nil.

Compare three things:

  1. the .entitlements file in your project
  2. the Entitlements dictionary in the decoded profile
  3. the entitlements actually embedded in the signed app

Read the signed binary's entitlements directly:

codesign -d --entitlements - --xml Build/Products/Release-iphoneos/Packerly.app

Then check the signature itself:

codesign --verify --deep --strict --verbose=2 Packerly.app

Things to look for:

  • aps-environment is development in a build you are about to upload for distribution
  • get-task-allow is true in a distribution build, which means it was signed for development
  • an App Group, iCloud container, or keychain access group is present in the source entitlements but missing from the profile
  • an Associated Domains entry exists in one target and not in the extension that needs it

The binary is the source of truth. Build settings and project files describe what should happen. codesign -d shows what did.

5. Treat every extension as its own signing target

Widgets, share extensions, notification service extensions, App Clips, and watch apps each have their own bundle identifier, App ID, profile, and entitlements.

This is where indie releases break most often. You add an App Group to the main app, the widget starts reading shared data in development, and the release build fails because the widget's App ID never got the capability. Or it does not fail, and the widget shows empty data in production.

When you add a capability that crosses targets, check each target separately:

Capability: App Group group.com.example.packerly
 
com.example.packerly              App ID enabled   profile regenerated
com.example.packerly.widget       App ID enabled   profile regenerated
com.example.packerly.share        App ID enabled   profile regenerated
com.example.packerly.notification not needed

Writing this table takes two minutes. It prevents the "works in Debug, empty in TestFlight" bug that otherwise takes an afternoon to trace back to signing.

Also check that every extension's bundle ID is prefixed by the parent app's bundle ID, and that version and build numbers match across targets. App Store Connect rejects mismatches at upload, which is better than finding out from a customer but still not a great time to find out.

6. Know what automatic signing does and does not do

Automatic signing is the right default for most indie apps. It creates and refreshes development profiles, registers devices, and keeps capabilities in sync with the project.

It does not fix:

  • a missing private key for the distribution certificate
  • a capability that requires explicit configuration in the portal, such as a specific iCloud container or Merchant ID
  • a CI machine with no Apple ID session and no API key
  • two team members fighting over the same certificate slot

On CI, automatic signing needs credentials. With an App Store Connect API key, xcodebuild can update profiles without an interactive login:

xcodebuild archive \
  -scheme Packerly \
  -archivePath build/Packerly.xcarchive \
  -allowProvisioningUpdates \
  -authenticationKeyPath "$ASC_KEY_PATH" \
  -authenticationKeyID "$ASC_KEY_ID" \
  -authenticationKeyIssuerID "$ASC_ISSUER_ID"

If you prefer manual signing in CI, which is reasonable for reproducibility, commit to it fully: explicit profile specifiers per target, profiles installed by the pipeline, and a documented process for regenerating them. Mixing automatic signing locally with manual signing in CI works until a capability changes and only one side finds out.

7. Fix CI keychain problems before blaming Apple

A CI signing failure with a message like errSecInternalComponent, or a job that hangs at the codesign step, is almost always a keychain problem. The certificate is imported but codesign cannot use the private key without a UI prompt nobody will ever click.

A reliable CI setup creates a temporary keychain per job:

security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
security set-keychain-settings -lut 21600 build.keychain
security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
security import signing.p12 -k build.keychain -P "$P12_PASSWORD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain
security list-keychains -d user -s build.keychain login.keychain

The set-key-partition-list line is the one people skip. Without it, the key exists but access requires confirmation, and a headless build cannot give it.

Delete the temporary keychain at the end of the job. A CI Mac that accumulates keychains from old runs eventually signs with the wrong identity, and that one is unpleasant to debug.

8. Read upload rejections as signing reports

Some signing problems only appear when you upload to App Store Connect. The build archived, export succeeded, and then the upload fails with an invalid signature or an entitlement complaint.

Those messages usually name the bundle and the entitlement. Treat them as precise reports:

  • invalid entitlement for a specific bundle means that target's profile and entitlements disagree, so check it with step 4
  • a missing or invalid provisioning profile in an embedded framework or extension usually points to a target that was signed differently from the rest
  • an invalid signature on a third-party framework often means it was re-signed incorrectly or shipped with stale signing artifacts

Validate before uploading when the release is time sensitive. Xcode's Organizer can validate an archive, and xcodebuild -exportArchive with a proper ExportOptions.plist catches many export-level problems locally. A failed validation at 17:00 is cheaper than a failed upload at 18:55.

9. Keep a short signing runbook in the repo

The most useful signing document is short and lives next to the code. It should answer:

  • which team ID and distribution identity sign this app
  • where the .p12 and API key live in the secret manager
  • every bundle ID, including extensions, and the capabilities each one needs
  • whether local and CI builds use automatic or manual signing
  • the commands from this article, ready to paste
  • when the certificate and profiles expire

Put the expiry dates on the calendar. Profile and certificate expiry is the only signing failure that announces itself a year in advance, and teams still discover it on release day.

10. A diagnosis order that holds up

When signing breaks, go through it in this order:

  1. security find-identity -v -p codesigning: is the identity present with its key?
  2. Decode the profile: is it current, does it include the certificate, does it grant the entitlements?
  3. Compare source entitlements, profile entitlements, and the signed binary.
  4. Repeat for every extension target.
  5. On CI, check the keychain setup and partition list before anything else.
  6. Read upload errors for the bundle and entitlement they name.

Each step either finds the disagreement or rules out a whole class of causes. That is slower than toggling checkboxes for the first five minutes and much faster for the rest of the day.

Signing is one of the few parts of iOS development where the system is strict, deterministic, and fully inspectable. The tools to see exactly what was signed, with what, and allowing what, are already on your Mac. Use them before you start guessing.

Command menu

Navigate the site or run an action