Back to Blog
Security

Certificate Pinning: When It Actually Helps (And When It Doesn't)

Pinning locks your app to specific certificates. Sounds secure. But most teams who enable it wish they hadn't. Here's when it makes sense and when it'll bite you.

CertGuard Team··7 min read

Why Pinning Exists

TLS works because everyone trusts a set of Certificate Authorities. A CA vouches for a server, your browser accepts it. Done.

Problem: any trusted CA can issue a certificate for any domain. If DigiNotar gets compromised (spoiler: they did, in 2011), an attacker walks away with valid certificates for Gmail. The browser has no way to know the difference between a legit Google certificate and one issued by a compromised CA.

Certificate pinning sidesteps the whole CA trust model. Your app says: "For api.example.com, only accept these specific certificates or keys. Everything else? Rejected, even if Comodo signed it."

Sounds great until you need to rotate your certificate and every app in the wild breaks.

Pin The Certificate or The Key?

You've got two options. Pinning the full leaf certificate means storing the exact cert your server presents. Any renewal — even with the same private key — breaks the pin. Nobody does this unless they hate themselves.

Public key pinning is smarter. You pin the hash of the Subject Public Key Info (SPKI). As long as the server uses the same key pair across renewals, the pin stays valid. Most implementations go this route.

Here's how to generate an SPKI hash from a certificate:

openssl x509 -in server.crt -pubkey -noout | \
  openssl pkey -pubin -outform DER | \
  openssl dgst -sha256 -binary | \
  base64

That base64 string is your pin. Compare it at connection time. Ship it in your app. Pray you never lose the private key.

Mobile Apps: Where Pinning Actually Works

Mobile apps talking to a known backend API are the sweet spot. You control both ends. The set of valid certificates is small and predictable. And mobile traffic crosses sketchy networks all the time, so the extra protection matters.

Banking apps, healthcare platforms, anything with sensitive user data — pinning adds real value here. The risk of a rogue CA is higher than the operational pain of managing pins. Just make sure the team knows what they're signing up for.

IoT devices fall into the same bucket. Firmware updates are rare, they connect to one or two endpoints, and the attack surface is huge. Pin the keys, ship the device, hope nothing goes wrong for the next five years.

How Android and iOS Handle It

Android uses a network security config XML file. Drop this in your app:

<network-security-config>
  <domain-config>
    <domain includeSubdomains="true">api.example.com</domain>
    <pin-set expiration="2027-01-01">
      <!-- primary pin -->
      <pin digest="SHA-256">YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg=</pin>
      <!-- backup pin (key you haven't deployed yet) -->
      <pin digest="SHA-256">Vjs8r4z+80wjNcr1YKepWQboSIRi63WsWXhIMN+eWys=</pin>
    </pin-set>
  </domain-config>
</network-security-config>

Notice the expiration date. After that, pinning disables itself and the app falls back to normal cert validation. Android's way of saying "we don't trust you to maintain this correctly forever." And they're right.

Always include a backup pin. Generate a key pair, hash the public key, add it to the config, then lock the private key in a vault somewhere. When your primary certificate dies unexpectedly, that backup pin is the only thing standing between you and an app store emergency update.

iOS doesn't have built-in pinning (NSAppTransportSecurity doesn't count — it's useless for this). Most teams use TrustKit:

let config: [String: Any] = [
    kTSKSwizzleNetworkDelegates: true,
    kTSKPinnedDomains: [
        "api.example.com": [
            kTSKPublicKeyHashes: [
                "YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg=",
                "Vjs8r4z+80wjNcr1YKepWQboSIRi63WsWXhIMN+eWys="
            ],
            kTSKEnforcePinning: true,
            kTSKIncludeSubdomains: true
        ]
    ]
]
TrustKit.initSharedInstance(withConfiguration: config)

Same rules apply. Two pins minimum. Document which key is which. Test the rotation process before production, not during the outage.

HPKP Failed For Good Reasons

HTTP Public Key Pinning (HPKP) tried to bring pinning to browsers. Servers sent a header with pin hashes and a max-age:

Public-Key-Pins:
  pin-sha256="YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg=";
  pin-sha256="Vjs8r4z+80wjNcr1YKepWQboSIRi63WsWXhIMN+eWys=";
  max-age=5184000;  // 60 days
  includeSubDomains

Browsers would cache the pins and reject any future connection that didn't match. Sounds reasonable.

In practice? A disaster. Site operators pinned the wrong key and locked themselves out for the entire max-age period. Others lost access to their pinned keys (M&A events, CA changes, key compromise). Security researchers demonstrated "HPKP super cookies" for cross-site tracking. Chrome removed support in 2018. Firefox followed.

The web moved on to Certificate Transparency instead. CT logs give domain owners a way to detect mis-issued certificates without the self-destruct potential of pinning. Less powerful, way safer.

The Operational Cost Nobody Talks About

Pinning creates a hard dependency between your certificate infrastructure and every deployed client. When that dependency breaks, users can't connect. And there's no quick fix if the clients are mobile apps.

Real scenario: Symantec's CA business got distrusted by browsers in 2018. Any server using a Symantec certificate needed to switch CAs, which usually means generating a new key pair. If your mobile app had pinned the old key? Every user with that version of the app is now locked out. App store review takes 2-7 days. Good luck.

Another fun one: emergency key rotation after a suspected compromise. If you don't have backup pins already deployed in app versions in the wild, you're looking at an outage until users update. And users update slowly. Some never do.

Mitigation strategies exist. Backup pins (we covered this). Pinning the intermediate CA instead of the leaf (survives leaf changes but less secure). Short expiration dates (limits blast radius). But each adds complexity. Teams that don't have a documented rotation procedure are gambling.

Better Options For Most Teams

Certificate Transparency monitoring catches mis-issued certificates without locking out users. Services like CertGuard watch CT logs for unexpected certificates issued to your domains and alert immediately. Zero deployment risk. Zero maintenance. Just works.

CAA (Certificate Authority Authorization) DNS records restrict which CAs can issue for your domain:

example.com. IN CAA 0 issue "letsencrypt.org"
example.com. IN CAA 0 iodef "mailto:security@example.com"

A compromised CA could ignore CAA (it's happened), but it raises the bar significantly. And unlike pinning, the only operational cost is updating a DNS record once.

DANE (DNS-based Authentication of Named Entities) ties certificates to DNSSEC-validated DNS records. Solves the same trust problem as pinning without the brittleness. Adoption is still low outside SMTP, but if you're already running DNSSEC, DANE is worth a look.

Should You Pin?

Run through these questions first:

  • Do you control both the client and server? If endpoints are dynamic or you don't own the client, skip it.
  • Can you coordinate certificate changes with app releases? If your release cycle is slow or user adoption is unpredictable, don't risk it.
  • Do you have a documented rotation process with backup pins already generated? If not, start there before enabling anything.
  • Would CT monitoring + CAA records provide enough protection? For web apps, the answer is usually yes.

Most web applications don't need pinning. CT monitoring, CAA, strict TLS config — that stack is strong enough without the operational headaches. Reserve pinning for high-security mobile apps and controlled environments where you can justify the maintenance burden.

If You Do Pin, Do It Right

A few rules that prevent the common disasters:

  • Pin public keys, not certificates. Survives renewals without code changes.
  • Always include backup pins. Generate them now. Store the private keys offline. Deploy them only when the primary fails.
  • Set expiration dates. Android's pin-set expiration is a safety net. Use it.
  • Consider pinning the intermediate CA. Less precise but more resilient to leaf certificate changes.
  • Monitor pin validation failures. Report-only mode helps catch issues before they become outages.
  • Test rotation in staging. Actually swap keys. Verify backup pins work. Do this before production.

Certificate pinning can meaningfully reduce your attack surface. It can also create the kind of self-inflicted outage that makes management question whether security is worth it. Know which scenario applies to you before flipping the switch.