Expired Provisioning Profile Causes Build Rejection

Build uploaded, Apple emails "provisioning profile expired" — certs and profiles both have expiry.

You archive in Xcode (or push CI), upload via Organizer or Transporter, and within minutes an automated email lands from Apple titled “App Store Connect: Issues with your app” with a body like “the provisioning profile included in the bundle has expired” or “the signing certificate used to sign your app is no longer valid.” The build never appears in TestFlight; it was rejected at processing time before any reviewer saw it.

iOS signing has two artifacts with independent expiries: the distribution certificate (typically 1 year from creation) and the provisioning profile (1 year from creation, also invalidated when the cert it depends on expires). CI/CD systems frequently use stale profiles checked into a config repo months ago. The fix is mechanical — regenerate the artifact that expired and rebuild — but knowing which one expired saves an hour of guessing.

Common causes

Ordered by hit rate.

1. Distribution certificate expired

Your iOS Distribution certificate’s 1-year clock ran out. All provisioning profiles bound to it become invalid at the same moment. You did nothing else; the cert silently expired.

How to spot it: Apple Developer → Certificates. Look for “iOS Distribution” with an expiry date in the past. The status column will say Expired.

2. Provisioning profile hit its 1-year expiry

The cert is still valid but the profile was generated more than a year ago. Profiles do not auto-renew; you have to regenerate.

How to spot it: Apple Developer → Profiles → filter to App Store / Ad Hoc. Look at the “Expires” column. Anything past today’s date is dead.

3. CI/CD pulled a cached profile from months ago

Your CI keeps a profile file in a private repo or secret manager and hasn’t re-pulled. Even though you regenerated a fresh profile in the Developer Portal, the CI is signing with the old one.

How to spot it: SSH into your CI runner (or inspect the secret manager). Open the .mobileprovision file:

security cms -D -i embedded.mobileprovision | grep -A1 "ExpirationDate"

If the expiration in the file is earlier than the one in the Developer Portal, CI is stale.

4. App ID changed but profile didn’t follow

You renamed bundle ID or added a new entitlement (Push, HealthKit). The existing profile no longer matches the App ID’s capabilities, and Apple rejects signing.

How to spot it: Apple Developer → Identifiers → your App ID → check Capabilities. Then Profiles → click your distribution profile → confirm Capabilities listed there match exactly. Any drift = rejection.

5. Apple Development Program enrollment lapsed

The $99/year membership renewal didn’t go through (card declined, expired card). All certs and profiles become invalid the moment the membership lapses.

How to spot it: Apple Developer → Membership. If the renewal date is past and status is not “Active,” the whole account is dead until you renew.

6. WWDR intermediate certificate expired or missing in build keychain

Apple’s intermediate Worldwide Developer Relations certificate has its own expiry. Your local Keychain or CI runner may not have the current one. Even with a valid distribution cert, the chain doesn’t validate.

How to spot it: Apple’s WWDR page. Download the current G3 / G4 intermediate. Import to Keychain. Check expiry; old ones are still in many CI images.

Information to collect

  • The exact rejection email body and error code (e.g., ITMS-90161, ITMS-90201).
  • Your iOS Distribution certificate name, fingerprint, and expiry date.
  • The provisioning profile name, UUID, and expiry date used by the build.
  • CI build log lines around code signing.
  • Your Apple Developer Program membership status and renewal date.

Shortest path to fix

Step 1: Identify which artifact expired

In the rejection email, look for keywords:

  • “signing certificate… expired” → cert problem (Step 2).
  • “provisioning profile… expired” → profile problem (Step 3).
  • “missing entitlements” → App ID drift (Step 5).
  • “membership” → membership lapse (Step 6).

If the message is ambiguous, run both Step 2 and Step 3 checks.

Step 2: Regenerate the distribution certificate

Apple Developer → Certificates → ”+” → iOS Distribution → Create.

You’ll need a CertificateSigningRequest (CSR) from Keychain Access:

Keychain Access → Certificate Assistant → Request a Certificate From a Certificate Authority
Email: your@team.com
Common Name: Your Name
Saved to disk → CertificateSigningRequest.certSigningRequest

Upload the CSR. Download the new .cer, double-click to install into Keychain. Export both the cert and its private key as a .p12 for backup and CI use:

Keychain Access → My Certificates → right-click → Export

Step 3: Regenerate provisioning profile

Apple Developer → Profiles → ”+” → App Store (or Ad Hoc / In House depending on distribution).

  • Select your App ID.
  • Select the new distribution cert from Step 2.
  • Select devices (Ad Hoc only).
  • Name with date suffix: AppStore_Acme_2027-05.
  • Generate, then Download.

In Xcode, double-click the .mobileprovision to install. Or place in ~/Library/MobileDevice/Provisioning Profiles/.

Step 4: Update CI/CD secrets

If your CI references the profile by file:

# Replace the file in your secret manager / git repo
# Example: GitHub Actions
gh secret set IOS_PROVISIONING_PROFILE_BASE64 --body "$(base64 -i AppStore_Acme_2027-05.mobileprovision)"
gh secret set IOS_CERT_P12_BASE64 --body "$(base64 -i DistributionCert.p12)"
gh secret set IOS_CERT_P12_PASSWORD --body "your-cert-password"

In your CI workflow, decode and import on each run:

- name: Install signing assets
  run: |
    echo "${{ secrets.IOS_PROVISIONING_PROFILE_BASE64 }}" | base64 -d > profile.mobileprovision
    mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
    cp profile.mobileprovision ~/Library/MobileDevice/Provisioning\ Profiles/

    echo "${{ secrets.IOS_CERT_P12_BASE64 }}" | base64 -d > cert.p12
    security create-keychain -p "" build.keychain
    security import cert.p12 -k build.keychain -P "${{ secrets.IOS_CERT_P12_PASSWORD }}" -T /usr/bin/codesign
    security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "" build.keychain

Step 5: Re-archive and upload

In Xcode:

  • Product → Clean Build Folder.
  • Product → Archive (Generic iOS Device).
  • Distribute App → App Store Connect → Upload.
  • Use automatic signing or pick the new profile manually.

Or via xcodebuild + xcrun altool / Transporter:

xcodebuild -workspace Acme.xcworkspace -scheme Acme -archivePath build/Acme.xcarchive archive
xcodebuild -exportArchive -archivePath build/Acme.xcarchive -exportPath build/ipa -exportOptionsPlist exportOptions.plist
xcrun altool --upload-app -f build/ipa/Acme.ipa -u "$ASC_USER" -p "@keychain:ASC_PASSWORD"

Step 6: Switch to fastlane match for long-term sanity

Manual cert management is the root cause of recurring expiry pain. fastlane match stores certs and profiles in a private git repo (or S3) encrypted, and any developer or CI runner can fetch the current state:

brew install fastlane
fastlane match init
fastlane match appstore

Now match appstore on any machine pulls the current cert + profile, and match nuke distribution + regeneration becomes a one-command annual ritual.

How to confirm the fix

  • A re-archive completes in Xcode without signing errors in the build log.
  • Upload to App Store Connect proceeds past the validation step.
  • A processing-success email arrives within 30-60 minutes.
  • The new build appears in App Store Connect → TestFlight → Builds.
  • No follow-up ITMS-90xxx rejection email arrives within 2 hours.

If it still fails

  1. Open Xcode → Settings → Accounts → Manage Certificates and confirm the new cert shows up, not the old one.
  2. Run security find-identity -v -p codesigning to see which certs are usable on your machine; old ones should be deleted.
  3. Check that your Info.plist bundle ID matches the App ID the profile is tied to — even a case mismatch fails.
  4. Compare the profile’s UUID in the build log against the UUID downloaded; if they differ, Xcode is using a stale cached version. Restart Xcode after deleting cached profiles in ~/Library/MobileDevice/Provisioning Profiles/.

Prevention

  • Set a calendar reminder 30 days before any cert or profile expiry; Apple does send a warning email but it can land in spam.
  • Use fastlane match from day one — it eliminates manual .p12 handling and makes CI rotation trivial.
  • Pin profile and cert expiry dates in a shared team doc; renew at the start of the month, not the day of expiry.
  • For App Store distribution, keep at most 2 active distribution certs (your “current” and “backup”); rotate them so no team member relies on a soon-to-expire one.
  • In CI, add a step that prints the embedded profile’s expiry date on every build, so an out-of-date profile is caught loudly.

Tags: #Troubleshooting