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
- Open Xcode → Settings → Accounts → Manage Certificates and confirm the new cert shows up, not the old one.
- Run
security find-identity -v -p codesigningto see which certs are usable on your machine; old ones should be deleted. - Check that your
Info.plistbundle ID matches the App ID the profile is tied to — even a case mismatch fails. - 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 matchfrom day one — it eliminates manual.p12handling 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.
Related reading
- TestFlight build stuck in processing
- New build not appearing
- TestFlight tester can’t redeem code
- TestFlight build expired
Tags: #Troubleshooting