Fastest fix: the rejection email names the artifact. “signing certificate… no longer valid” (often ITMS-90165) means your distribution certificate expired, so create a new iOS Distribution certificate and a fresh App Store profile bound to it, then re-archive. “provisioning profile… has expired” (ITMS-90161) means just the profile lapsed, so regenerate the profile in the portal. Don’t change both blindly: regenerating the artifact that is still valid wastes 20 minutes and resets your CI secrets for nothing.
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 three artifacts with independent expiries: the distribution certificate (valid 1 year from creation), the provisioning profile (1 year from creation, also invalidated the moment the cert it depends on expires), and Apple’s WWDR intermediate certificate that signs the whole chain. CI/CD systems frequently sign with a stale profile that was checked into a config repo or secret manager months ago. The fix is mechanical, regenerate the artifact that expired and rebuild, but knowing which one expired saves an hour of guessing. One nuance that trips people up: you cannot “renew” an expired distribution certificate, you can only create a new one (Apple confirms this on its certificates support page).
Which bucket are you in?
Match the email text to the artifact before you touch anything.
| Email / log text | Likely error code | Artifact to fix | Section |
|---|---|---|---|
| ”signing certificate… no longer valid” | ITMS-90165 | Distribution certificate | Cause 1, Step 2 |
| ”provisioning profile… has expired” | ITMS-90161 | Provisioning profile | Cause 2, Step 3 |
| Build signs locally, fails only in CI | (no email, fails at sign) | Stale CI profile/cert | Cause 3, Step 4 |
| ”doesn’t include the entitlement” / “doesn’t support capability” | ITMS-90164 family | App ID drift | Cause 4 |
| ”membership has expired” / portal locked | n/a | Program enrollment | Cause 5 |
errSecInternalComponent / chain not trusted at codesign | n/a | WWDR intermediate | Cause 6 |
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 Developer Program enrollment lapsed
The Apple Developer Program membership (still $99/year as of June 2026, with fee waivers available for eligible nonprofits, accredited schools, and government entities) didn’t renew, usually a declined or 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 locked until you renew.
6. WWDR intermediate certificate expired or missing in build keychain
Apple’s intermediate Worldwide Developer Relations (WWDR) certificate signs your distribution cert and has its own expiry. Your local Keychain or a CI runner image may not have the current one. Even with a valid distribution cert, the chain then fails to validate and codesign errors out (often errSecInternalComponent). This is also what produces ITMS-90165 “Invalid Provisioning Profile Signature” when the WWDR cert that signed the profile lapses.
How to spot it: open Apple’s Certificate Authority page. After the original WWDR intermediate expired on February 7, 2023, Apple issued segmented intermediates: the renewed WWDR G3 (expires February 20, 2030 as of June 2026) signs Apple Distribution / iOS Distribution certs, and G6 is the newer ECDSA software-signing intermediate (expires March 19, 2036). Modern Xcode (15+) auto-installs eligible WWDR intermediates, so a missing one almost always means a headless CI runner or an old container image. Download AppleWWDRCAG6.cer (and the G3 cert if your distribution cert chains through it), import to the build Keychain, and confirm the expiry is in the future.
Before you start
- Record the exact rejection email text. “expired” and “no longer valid” point at different artifacts.
- Confirm which team you are signing under. Multi-team accounts can quietly sign with the wrong cert and never warn you.
- Back up the current cert and profile locally before you change anything (export the
.p12), so a botched rotation is reversible. - Establish whether the problem is CI-only or also reproduces on your machine. CI-only points straight at a stale secret.
Information to collect
- The exact rejection email body and error code (e.g.,
ITMS-90161,ITMS-90165). - 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… no longer valid” (
ITMS-90165) → cert problem (Step 2). - “provisioning profile… has expired” (
ITMS-90161) → profile problem (Step 3). - “doesn’t include the entitlement / capability” → App ID drift (see Cause 4; edit the App ID, then redo Step 3).
- “membership” / portal locked → membership lapse (see Cause 5; renew, then redo Steps 2-3).
If the message is ambiguous, run both Step 2 and Step 3 checks.
Step 2: Create a new distribution certificate
You cannot renew an expired certificate, so create a replacement: Apple Developer → Certificates → ”+” → Apple Distribution (this single type now covers iOS, tvOS, watchOS, and macOS App Store; pick “iOS Distribution” only if you maintain a platform-specific cert) → 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. The on-disk location changed with Xcode 16: profiles now live in ~/Library/Developer/Xcode/UserData/Provisioning Profiles/ (Xcode 15 and earlier used ~/Library/MobileDevice/Provisioning Profiles/). If you script the copy, target the path that matches the Xcode version on the machine, and note that some tools (fastlane, Rider, .NET MAUI) needed updates to find the new folder.
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
# Xcode 16+ runner path; on Xcode 15 images use ~/Library/MobileDevice/Provisioning Profiles
mkdir -p ~/Library/Developer/Xcode/UserData/Provisioning\ Profiles
cp profile.mobileprovision ~/Library/Developer/Xcode/UserData/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 + a command-line uploader:
xcodebuild -workspace Acme.xcworkspace -scheme Acme -archivePath build/Acme.xcarchive archive
xcodebuild -exportArchive -archivePath build/Acme.xcarchive -exportPath build/ipa -exportOptionsPlist exportOptions.plist
For the upload itself, prefer an App Store Connect API key over an Apple ID + app-specific password (more reliable on CI, no 2FA prompts):
# Modern: App Store Connect API key (.p8) — also what notarytool and fastlane use
xcrun altool --upload-app -f build/ipa/Acme.ipa -t ios \
--apiKey "$ASC_KEY_ID" --apiIssuer "$ASC_ISSUER_ID"
# Legacy fallback (still works for App Store uploads as of June 2026):
xcrun altool --upload-app -f build/ipa/Acme.ipa -t ios -u "$ASC_USER" -p "@keychain:ASC_PASSWORD"
Note: altool was deprecated for notarization (no uploads since November 1, 2023, use xcrun notarytool there), but it is still supported for uploading App Store binaries. The .p8 key file must sit in ~/.appstoreconnect/private_keys/ or be passed with --apiKeyPath.
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 encrypted in a backend you choose (private git repo, Amazon S3, or Google Cloud Storage), and any developer or CI runner can fetch the current state:
brew install fastlane
fastlane match init
fastlane match appstore
Now fastlane match appstore on any machine pulls the current cert + profile, and fastlane match nuke distribution followed by regeneration becomes a one-command annual ritual. On CI, always pass --readonly so a runner never tries to create or revoke certs, it just fetches what already exists:
fastlane match appstore --readonly
If a profile or cert has genuinely expired, run a full (non-readonly) fastlane match once from a developer machine to regenerate, then let CI go back to read-only.
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 you downloaded; if they differ, Xcode is using a stale cached version. Delete cached profiles (
~/Library/Developer/Xcode/UserData/Provisioning Profiles/on Xcode 16+, or~/Library/MobileDevice/Provisioning Profiles/on Xcode 15 and earlier) and restart Xcode.
FAQ
The email says the certificate expired but the portal shows it as valid. Why?
The build was almost certainly signed with an older cert still cached on your machine or CI runner, not the one you see in the portal. Run security find-identity -v -p codesigning, delete any expired identities, and confirm the build log references the new cert’s fingerprint.
Can I just renew the expired distribution certificate? No. Apple does not support renewing certificates, you create a new one and let the old one expire or revoke it. Then regenerate any profile that referenced the old cert (a profile keeps pointing at the specific cert it was built with).
Do I have to bump the build number or version to re-upload?
Yes, if the rejected upload was already accepted into processing. App Store Connect requires a unique CFBundleVersion (build number) per upload. If the build was rejected at validation before processing, the same number usually works, but bumping it is the safe default.
My old build was working fine, I changed nothing, and now it is rejected. What expired? Time did. Certs and profiles have hard 1-year expiries that fire regardless of activity. Check Apple Developer → Certificates and → Profiles for an expiry date in the past; that silent clock is the most common cause.
It signs on my Mac but fails only on CI. Where do I look first?
The CI secret. The runner is signing with a profile or .p12 stored months ago. Decode the embedded profile in CI and print its expiry (security cms -D -i embedded.mobileprovision | grep -A1 ExpirationDate) and compare it to the portal. This is Cause 3.
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