Skip to content

iOS code signing, explained by someone who hated it

· 3 min read · Amrith Vengalath

  • iOS
  • Fastlane
  • Code Signing
  • CI/CD

For my first couple of years doing mobile, iOS code signing was a black box I poked at until builds worked, with no real model of why. Errors like "no profiles for 'com.app' were found" or "the identity used to sign is not valid" sent me clicking around Xcode and the developer portal semi-randomly until the red went away. Eventually I sat down and actually learned what the pieces are, and it stopped being scary. Here's the model I wish I'd had.

The four pieces and what each one means

iOS signing has a small number of moving parts that all have to agree:

  • Certificate - proves who is building. It's tied to your Apple developer account (or your team's). There's a development cert and a distribution cert. It has a private key, which is the part that actually matters and the part people lose.
  • App ID / Bundle Identifier - what app this is, like com.mycompany.myapp. Registered in the developer portal.
  • Provisioning profile - the document that ties a certificate, an app ID, and (for development/ad-hoc) a list of allowed devices together. It says "this certificate is allowed to build this app for these devices." This is usually what's actually missing or stale when a build fails.
  • Entitlements - capabilities the app uses (push, associated domains, etc.), which must match what's enabled on the App ID.

Almost every signing error is really "these four things don't agree." The certificate doesn't match the profile, or the profile is for a different app ID, or it expired, or a device isn't in it. Once you read errors through "which two of these disagree," they get a lot less mysterious.

Why it's miserable on a team

The pain scales with people and machines. Every developer needs the certificate's private key. CI needs it too. Devices have to be registered and profiles regenerated when they're added. People share the private key over Slack (please don't), someone regenerates a cert and invalidates everyone else's setup, and CI breaks because it never had the key in the first place. The whole thing is shared mutable state with no good native sharing mechanism.

This is the actual problem. The concepts aren't that hard; the distribution and synchronization of secrets across humans and machines is.

Fastlane match: make it boring

match solves the synchronization problem with one idea: store the certificates and profiles, encrypted, in a private git repo, and have every machine sync from there. Nobody emails private keys. CI pulls them read-only. When something changes, everyone runs one command.

Setup, once:

fastlane match init        # point it at a private repo for the encrypted certs
fastlane match appstore    # generate/store the distribution cert + profile
fastlane match development  # and the development ones

After that, any machine - a new hire's laptop, a CI runner - gets everything with:

fastlane match appstore --readonly

Read-only is important on CI: the runner should use the certs, never regenerate them. Regenerating from CI is how you invalidate everyone else.

The encryption passphrase lives in an environment variable / CI secret, and the git repo is private. That's the whole security model, and it's a lot better than the private key sitting in someone's Downloads folder.

The mindset shift

What match really changed for me wasn't the commands - it was that code signing went from "a thing I fight every release" to "a thing I don't think about." New machine, run one command, done. CI just works because it pulls the same certs. Devices and profiles are managed in one place.

And understanding the four pieces meant that on the rare occasion something did break, I could read the error, figure out which two things disagreed, and fix exactly that instead of clicking around hoping. The combination - a real mental model plus match for the synchronization - is what finally made iOS signing a non-event.

If you're still hand-managing certificates across a team, this is one of the highest-return afternoons of setup you can spend. It removes an entire recurring source of release-day stress.