
When an Apple Distribution certificate expires, Xcode stops archiving and your CI pipeline stops signing. You do not need Keychain Access.app to fix that on a rented Mac. The security command and openssl handle the full rotation—generate the CSR, import the signed certificate, confirm the identity chain, and export an encrypted .p12—from a terminal session. Apple's overview of the signing flow is Preparing your app for distribution, and the authoritative notes on Developer ID expiry behavior live on the Developer ID support page.
An Apple code-signing identity has three parts:
A "valid identity" means all three are present and the certificate links to its private key. If the intermediate is absent or the private key is on a different machine, xcodebuild -allowProvisioningUpdates fails with a signing error.
Apple is rolling out G2 Developer ID authorities. Check the current state of your own certificates in the Apple Developer account portal rather than trusting a third-party deadline write-up.
Keep the private key on the machine that will sign builds. Generate it in the terminal on the remote Mac; do not create it locally and copy the private key across a network.
#!/usr/bin/env bash
set -euo pipefail
umask 077
CERT_NAME="acme_distribution_$(date -u +%Y%m%dT%H%M%SZ)"
CSR_FILE="${CERT_NAME}.certSigningRequest"
KEY_FILE="${CERT_NAME}.key"
EMAIL="releases@example.com"
COUNTRY="US"
# 2048-bit RSA key (minimum Apple accepts for code-signing CSRs)
openssl genrsa -out "$KEY_FILE" 2048
openssl req -new \
-key "$KEY_FILE" \
-out "$CSR_FILE" \
-subj "/emailAddress=${EMAIL}/CN=${CERT_NAME}/C=${COUNTRY}"
echo "Upload ${CSR_FILE} at developer.apple.com → Certificates, Identifiers & Profiles."
echo "Keep ${KEY_FILE} on this Mac; it is required to use the issued certificate."
Upload $CSR_FILE and pick Apple Distribution for App Store/TestFlight signing or Developer ID Application for direct distribution and notarization.
Download the .cer from the developer portal, transfer it to the remote Mac, and import it into the same login keychain that holds the private key.
#!/usr/bin/env bash
set -euo pipefail
CER_FILE="${1:?pass the downloaded .cer path}"
KEY_FILE="${2:?pass the matching .key path from the CSR step}"
KEYCHAIN="$HOME/Library/Keychains/login.keychain-db"
# Make sure the login keychain is unlocked for this shell
security unlock-keychain "$KEYCHAIN"
# Import the issued certificate; -T grants codesign access without a GUI prompt
security import "$CER_FILE" \
-k "$KEYCHAIN" \
-T /usr/bin/codesign \
-T /usr/bin/productbuild
# Import the matching private key (same keychain so the identity is complete)
security import "$KEY_FILE" \
-k "$KEYCHAIN" \
-T /usr/bin/codesign
security find-identity -v -p codesigning "$KEYCHAIN"
The -T /usr/bin/codesign flag lets codesign use the key non-interactively; without it, a CI job can hang at a GUI password prompt. Pass the real file paths as arguments rather than globbing the key filename, so a stray .key file from a past rotation cannot be imported by accident.
security find-identity -v -p codesigning
A healthy result:
1) A1B2C3D4E5F6... "Apple Distribution: Example Corp (TEAM123ABC)"
1 valid identities found
If find-identity reports 0 valid identities found, one of these is missing:
sudo security add-trusted-cert -d -k /Library/Keychains/System.keychain <intermediate>.cer.security import for the .key file; the login keychain only forms an identity when key and cert are both present..cer from the developer portal.Confirm expiry and issuer with:
security find-certificate -a -c "Apple Distribution" -p \
| openssl x509 -noout -subject -issuer -dates
A .p12 bundles the certificate and its private key in a password-protected container. Use it to seed another signing host or to store a recovery copy in your secret manager—never in source control.
#!/usr/bin/env bash
set -euo pipefail
umask 077
P12_FILE="distribution_$(date -u +%Y%m%d).p12"
KEYCHAIN="$HOME/Library/Keychains/login.keychain-db"
# Omit -P so security prompts interactively; the passphrase never hits shell history
security export \
-k "$KEYCHAIN" \
-t identities \
-f pkcs12 \
-o "$P12_FILE"
echo "Encrypted p12 written to ${P12_FILE}"
Hand the passphrase to recipients out-of-band (a secret manager, not email). On the receiving Mac, run security import backup.p12 -k ~/Library/Keychains/login.keychain-db -P '<passphrase>' inside that user's shell. Provisioning profiles are separate files—place them in ~/Library/MobileDevice/Provisioning Profiles/; they do not belong in a keychain.
| Certificate | Typical validity | What expiry breaks |
|---|---|---|
| Apple Development | ~1 year | Debug builds on test devices |
| Apple Distribution | ~1 year | New App Store / TestFlight archives |
| Developer ID Application | ~5 years | Signing new Mac app updates (already-signed binaries keep running per Gatekeeper) |
| Developer ID Installer | ~5 years | Signing new installer packages |
| WWDR Intermediate | Set by Apple | Any identity relying on the missing intermediate |
Confirm each exact expiry on the Developer portal before you plan a rotation; Apple reissues intermediates and has shifted some Developer ID chains to G2 authorities. Set a calendar reminder about 30 days before any Distribution certificate expires—Xcode does not warn you before the first failing build.
This runbook is terminal-only. No GUI, no GPU, no Apple silicon instruction set is needed to generate a CSR, import a certificate, or export a .p12.
security, openssl, codesign, and compatible Intel Xcode toolchains (Xcode 26 or earlier per Apple's porting guide) handle this certificate workflow. Your shell runs inside the VM.If the only moving part is certificate rotation on one host, the $29 virtual Intel macOS plan is enough. If an engineer is about to spend a week wiring CI, signing, and a secret-manager restore, the Managed Build Mac usually recovers its cost in that one onboarding.
Request a plan at Figment Mac Hosting →
Related guides: