All posts
EngineeringSeptember 30, 2026 · The Ubriot team · 5 min read

Read the expiry date inside your signing files

An iOS distribution certificate and a provisioning profile each carry their own expiry date. Most teams find out about it from a failed build. Where the dates live, what each one stops, and the warning window we now show in the Ubriot dashboard.

Most mobile teams learn that a signing certificate has expired from a build log. The build ran, the tests passed, and the signing step failed with an error about a certificate that is no longer valid. Someone then has to find whoever holds the Apple Developer account, create a new certificate, export it, upload it, regenerate the profiles that referenced the old one, and run the build again. On a quiet week that is an irritation. On the day a fix has to ship, it is the whole afternoon.

The frustrating part is that the date was never secret. Every certificate and every provisioning profile records when it stops being valid, inside the file you already uploaded. This week we started reading that date and putting it in front of people a month early. This post covers where the dates live, what each expiry actually stops, and why we settled on the window we did.

Two files, two clocks

iOS signing depends on two separate things, and they expire separately.

The distribution certificate proves who built the app. It usually reaches you as a .p12 file: the certificate plus its private key, protected by a password. Apple distribution certificates are valid for one year from the day they are created. The expiry is the certificate's notAfter field, the same field any X.509 certificate has.

The provisioning profile says which app, which certificate and, for some kinds of distribution, which devices are allowed. It is a signed property list, and it carries an ExpirationDate key in plain text. A profile is only usable while the certificate it names is still valid, so renewing the certificate usually means regenerating the profiles that point at it as well.

Because the two were created at different times, their dates rarely line up. A team can renew a certificate in March and still have a profile from the previous autumn that runs out in October. Watching one date is not enough.

What each expiry stops

It helps to be precise here, because the consequences are different and people tend to assume the worst.

When a distribution certificate expires, you cannot sign new builds with it. Apps already live on the App Store keep working and keep downloading. The damage is to your ability to ship the next version, which is usually discovered at the least convenient moment.

When a profile for ad hoc distribution expires, builds already installed with it stop launching on those devices. That is the one that reaches people: a tester, a client reviewing a build, or a field team using an internal app finds it will not open.

TestFlight builds have a third clock, ninety days from upload, which is set by the store and is separate from both files. We mention it because teams sometimes blame a certificate for what is really an old TestFlight build.

Reading the dates

The profile is the easy one. You can open a .mobileprovision file in a text editor, search for ExpirationDate and read the date on the next line. On a Mac, security cms -D -i profile.mobileprovision prints the whole property list if you prefer something tidier.

The certificate takes one more step, because it is inside an encrypted .p12. OpenSSL will extract it with the password, and the expiry is then one command away.

openssl pkcs12 -in dist.p12 -clcerts -nokeys | openssl x509 -noout -enddate

# older .p12 exports may need the legacy provider on OpenSSL 3
openssl pkcs12 -legacy -in dist.p12 -clcerts -nokeys | openssl x509 -noout -enddate

The second line matters more than it looks. Many .p12 files exported from older versions of Keychain Access use an encryption scheme that OpenSSL 3 no longer reads by default, and the error it gives is not obviously about that. If a date check on a perfectly good certificate fails, try the legacy flag before assuming the file or the password is wrong. Our own reader tries both.

What we show now

When you upload a distribution certificate or a provisioning profile to Ubriot, we read its expiry date from the file and show it beside the credential. From thirty days out the credential carries an amber badge with the number of days left. Once the date has passed the badge turns red and reads Expired. The dashboard overview and the account page also show a banner listing every signing credential inside that window, each with a Replace link that goes straight to the right place, whether the credential belongs to one app or to the whole account.

If we cannot read a date from a file, we show no date. We would rather leave the line blank than display a guess that someone later plans around.

Why thirty days

Replacing a certificate is rarely hard. What makes it slow is that it needs a particular person. Creating a distribution certificate requires someone with the right role on the Apple Developer account, and on many teams that is one founder, one lead, or an agency contact who handles several clients. Thirty days is long enough to survive that person's holiday and a release freeze, and short enough that the warning still feels like news when it appears. A warning that sits on screen for three months turns into wallpaper.

There is also a pairing to plan. Because profiles depend on the certificate, the tidy approach is to renew the certificate first, then regenerate and upload the profiles that used it, and only then remove the old ones. Doing it in that order means there is never a moment when your builds have nothing valid to sign with.

A calendar entry is enough to start

You do not need a tool for any of this. Read the two dates with the commands above, add them to a shared calendar a month early, and name the person who will do the renewal. The failure we are trying to remove is not a technical one. It is a date nobody was looking at, landing on the one day it costs the most.

Try Ubriot AI

React Native CI/CD from one CLI.

Get started