How Certificate Pinning Actually Works
Try the interactive lab for this articleTake the quiz (6 questions · ~6 min)Certificate pinning is one of those security controls that sounds simple when described badly. The simple version says an app remembers a certificate and refuses anything else. That description is not wrong, but it leaves out the part that matters: where pinning sits in the trust decision, what object is actually pinned, which attacks it changes, and why it is so easy to deploy badly.
Most engineers first meet pinning when something breaks. A banking app starts failing after a certificate renewal. A corporate proxy works in Safari but not in the mobile app. Someone rotates a key at 02:00 and discovers that the old clients did not have a backup pin. The real lesson is that pinning is not a generic TLS hardening checkbox. It is a deliberate narrowing of trust from a very large public PKI to a very small set of expected keys, and that makes it both powerful and dangerous.
This article goes through the mechanism from the ground up. We will look at why ordinary TLS validation still trusts more than some applications want, what pinning usually hashes in practice, how a pinned client evaluates a connection, how to derive the exact value that gets baked into an app, why backup pins matter more than the active pin, what attack classes pinning really helps against, why HTTP Public Key Pinning became a cautionary tale, and where pinning still makes sense in 2026.
Public PKI Trusts Far More Keys Than Your App Probably Wants To Trust
Ordinary TLS validation answers a specific question: does the certificate chain presented by the server satisfy the client's trust policy?
For a public-web client, that usually means:
- the hostname matches the leaf certificate SAN
- the certificate is currently valid in time
- the leaf chains through one or more intermediates to a locally trusted root
- the chain satisfies X.509 policy constraints such as EKU and CA flags
- revocation or status policy does not reject the chain
If all of that passes, the connection is accepted.
That is a reasonable default for browsers because browsers need to work with millions of sites they have never seen before. They cannot ship a custom trust decision for each domain. They need a scalable rule, and the scalable rule is public PKI.
The problem is that public PKI is intentionally broad. A client that trusts the system root store is not trusting one certificate authority. It is trusting every root and subordinate path that the platform accepts. That may be fine for ordinary web browsing. It may be too broad for a mobile banking app, a medical device gateway, an internal corporate client, or a piece of embedded equipment that only ever talks to one service.
Suppose an Android app in Athens talks only to api.bank.example.eu. Without pinning, the effective trust statement is not:
Trust this bank's certificate.It is closer to:
Trust any certificate chain for api.bank.example.eu
as long as it terminates in any root the device trusts
and satisfies ordinary validation rules.That set is much larger.
The larger set creates several classes of risk:
- a publicly trusted CA could misissue a certificate for the hostname
- an enterprise device could have an inspection root installed by MDM
- a hostile local administrator on a rooted device could add a custom root
- a middlebox could substitute a locally trusted certificate on managed endpoints
Pinning exists for cases where the application owner wants to say something narrower:
Even if the operating system trusts this chain,
I only want to accept one expected public key,
or one of a very small set of expected public keys.That is the conceptual heart of pinning. It does not make TLS stronger in the abstract. It narrows which authenticated keys count as acceptable for one specific service.
The Thing You Usually Pin Is Not The Whole Certificate
The phrase certificate pinning encourages a bad mental model. In real deployments, pinning the whole certificate document is often the wrong choice.
Why? Because certificates change all the time for reasons that are operationally normal:
- the validity dates change on renewal
- the serial number changes
- the issuing intermediate may change
- SCT embedding can change
- non-key extensions can change
If you pin the exact certificate bytes, every ordinary reissue becomes a coordinated client update event. That is brittle.
What most mature implementations pin instead is the hash of the certificate's SubjectPublicKeyInfo, usually called SPKI. SPKI is the X.509 structure that contains:
- the public-key algorithm identifier
- the public key bits themselves
That matters because the SPKI stays stable when you renew a certificate but keep the same key pair. The surrounding certificate can change while the pinned key identity stays the same.
At a high level, the object looks like this:
SubjectPublicKeyInfo ::= SEQUENCE {
algorithm AlgorithmIdentifier,
subjectPublicKey BIT STRING
}When people say they pin the public key, they often mean they pin a hash of the DER encoding of this SPKI structure, not the raw RSA modulus or the raw elliptic-curve point by itself.
That distinction matters operationally.
Consider three strategies:
Strategy 1: Pin the exact leaf certificate
This is the most brittle option.
It only survives as long as the leaf certificate stays byte-for-byte the same. Renew the certificate with the same key and you still break the pin.
Strategy 2: Pin the leaf SPKI hash
This survives ordinary certificate renewal if the same key pair is reused.
That is usually much better.
Strategy 3: Pin a small SPKI set, including a backup key
This survives ordinary certificate renewal and also supports planned key rotation.
That is usually what people mean when they talk about production-grade pinning.
The difference becomes obvious in a simple matrix:
Event Cert pin SPKI pin SPKI pin + backup
--------------------------------------------------------------------------------
Leaf reissued, same key fails passes passes
Leaf reissued, new key fails fails passes if backup shipped
Misissued cert, wrong key fails fails fails
Corporate proxy, substitute key fails fails failsThe control is still called pinning, but what you pin changes the operational blast radius completely.
Pinning Happens After Ordinary Path Validation, Not Instead Of It
A common implementation mistake is to treat pinning as a replacement for ordinary TLS validation. That is wrong.
A correct pinned client normally does both:
- run ordinary TLS validation
- apply an additional local key constraint
That order matters.
If you skipped ordinary validation and accepted any certificate whose key matched a pinned hash, you would throw away checks that still matter:
- hostname validation
- expiry
- EKU /
serverAuth - path constraints
- revocation or status policy
Pinning is a narrowing layer, not a licence to ignore the rest of PKI.
A simplified evaluation pipeline looks like this:
receive certificate chain
build and validate a path to a local trust anchor
verify hostname, time, EKU, and policy
extract candidate SPKI values from the accepted chain
hash SPKI values
compare against local allow-list
accept only if both ordinary validation and pin match succeedIn pseudo-code:
if not verify_path(chain, trust_store):
reject
if not verify_hostname(chain.leaf, requested_hostname):
reject
if not verify_time_and_usage(chain.leaf):
reject
for cert in chain:
pin = sha256(der(cert.subjectPublicKeyInfo))
if pin in allowed_pins:
accept
rejectThere are two implementation details hidden in that pseudo-code.
Which certificate in the chain do you compare?
Many client implementations compare against the leaf key. Some compare any certificate in the chain against the pin set. That allows you to pin an intermediate rather than the leaf.
Pinning an intermediate gives you more operational flexibility because leaf certificates can rotate under it. It also broadens the trust surface again, because now any leaf issued under that intermediate may satisfy the pin. That might be acceptable for a tightly controlled private PKI. It is usually too broad for a single high-value public service.
At what layer do you hook the check?
The exact hook depends on the platform:
- a mobile app may use the platform trust callback
- a desktop client may use OpenSSL or BoringSSL verification hooks
- a reverse proxy or service mesh may use custom TLS verifier logic
- an HTTP client library may expose a certificate-pinning abstraction on top of TLS
The mechanism differs. The logical place stays the same: after the chain has been evaluated, before the connection is handed to application code as trusted.
Deriving The Real Pin Value Is A Byte-Level Operation
Because pinning usually targets SPKI, the value you ship is derived from certificate structure, not copied from the pretty text view of a certificate.
Suppose you want the SPKI hash for api.bank.example.eu. One representative OpenSSL pipeline is:
openssl s_client -connect api.bank.example.eu:443 -servername api.bank.example.eu -showcerts </dev/null \
| openssl x509 -pubkey -noout \
| openssl pkey -pubin -outform DER \
| openssl dgst -sha256 -binary \
| openssl enc -base64What that does, stage by stage:
- fetch the server certificate
- extract the public key material
- encode the public key structure as DER
- hash those bytes with SHA-256
- base64-encode the digest for shipping in config
The result usually ends up represented as something like:
sha256/8A1euX3N7sLQW0fE4y0QxQ8Fm7g1bVn0m3fV2H1x6Eg=That is not a fingerprint of the whole certificate. It is the fingerprint of the SPKI bytes.
If you want to inspect the certificate chain first:
openssl s_client -connect api.bank.example.eu:443 -servername api.bank.example.eu -showcerts </dev/nullIf you want to see the parsed certificate fields:
openssl x509 -in leaf.pem -text -nooutThis byte-level detail matters because pinning bugs often come from hashing the wrong thing.
Common mistakes include:
- hashing the PEM text including headers
- hashing the whole certificate instead of SPKI
- hashing the raw key bits but not the algorithm identifier
- pinning a staging certificate and forgetting production uses a different chain
- pinning the active key only and forgetting the backup key entirely
The output format also matters because different platforms want different representations.
Android network security config
Android's Network Security Configuration expects SHA-256 pins for SPKI hashes. A representative configuration looks like this:
<network-security-config>
<domain-config>
<domain includeSubdomains='true'>api.bank.example.eu</domain>
<pin-set expiration='2027-05-17'>
<pin digest='SHA-256'>8A1euX3N7sLQW0fE4y0QxQ8Fm7g1bVn0m3fV2H1x6Eg=</pin>
<pin digest='SHA-256'>wY3R9W1vL6x4pQF5dD4nS5qP2tXg8rM4aC1uZ7bK0LA=</pin>
</pin-set>
</domain-config>
</network-security-config>The second pin is the important one. The backup pin is what lets you recover from key rotation without immediately marooning old clients.
iOS trust evaluation
On Apple platforms, pinning often lives in custom trust evaluation logic. The flow is conceptually the same:
system trust evaluation passes
extract leaf SPKI or chain SPKIs
hash with SHA-256
compare against baked-in pin set
allow or cancel the connectionThe application code differs between URLSession, Network.framework, or a wrapper library. The mechanism does not.
Backup Pins Matter More Than The Primary Pin
Teams often fixate on the active pin because it is the value in production today. The active pin is not the difficult part. The difficult part is recovery.
Ask a harder question: what happens if you lose the current private key, must revoke the current certificate, or need to move to new hardware immediately?
If the client trusts only the active key, you have created a trap:
- the current key is compromised or unavailable
- you generate a new key and deploy a new certificate
- the new chain is perfectly valid under public PKI
- the old clients reject it anyway because the new key is not pinned
Now you are forced into a race between infrastructure recovery and client rollout.
That is the disaster pattern backup pins are designed to avoid.
A sane rotation story looks like this:
- generate a second key pair that is not yet live
- ship its SPKI hash in the client as a backup pin
- leave the current key active for a release cycle or more
- rotate the server to the backup key when needed
- optionally generate a fresh standby backup and repeat
The application then trusts a set of keys, usually very small, rather than exactly one key.
Think of the backup pin as a recovery runway. It is not there because you expect two live keys every day. It is there because outages and compromise do not politely wait for app-store review queues.
There are still tradeoffs.
Reusing the same key across renewals
Reusing a key across certificate renewals makes SPKI pinning easy. It also means the same private key stays live longer. That increases the exposure window if the key is ever compromised.
Rotating keys aggressively
Frequent key rotation is healthy from a key-lifecycle perspective, but it becomes operationally expensive if clients do not already trust the next key.
Pinning pushes you to plan key transitions earlier than ordinary PKI would force you to.
Setting pin expiry
Some platforms let you set an expiry date for pins. That reduces the risk of permanently bricking old clients, but it also weakens the security property after the expiry date. Expiry is a safety valve, not a substitute for rotation planning.
The general rule is simple: a pinning deployment without a rotation story is not finished.
Pinning Helps Against A Narrow Set Of Threats, And It Does Nothing Against Others
Pinning is often oversold. It is useful precisely because it changes a specific part of the trust model. Outside that part, it does not help.
What pinning can help against
Misissued public certificates
If a publicly trusted CA issues a valid certificate for your hostname to the wrong party, ordinary PKI may accept it. A pinned client can still reject it because the key is not one of the pre-authorised keys.
Locally trusted TLS inspection on managed devices
If an enterprise root is installed on the device, the system trust store may accept substitute certificates minted by an inspection proxy. A pinned app can refuse those substitute keys.
Some targeted interception scenarios
If an attacker can reroute traffic and present a chain that validates under the local trust model but uses the wrong key, pinning can still block the session.
What pinning does not solve
A compromised client
If the attacker controls the device, hooks the TLS stack, or patches the app binary, pinning can often be bypassed. Pinning assumes the client enforces the rule honestly.
A stolen server private key
If the attacker has the actual pinned private key, pinning offers no extra protection. The attacker is now presenting the expected key.
Broken application logic above TLS
Pinning does not fix CSRF, IDOR, deserialisation bugs, SSRF, or poor authorisation.
Availability mistakes you create yourself
Pinning is not only about blocking attackers. It is also about avoiding self-inflicted denial of service. A bad rollout can lock out legitimate users more effectively than any external adversary.
This narrow threat model is why pinning makes sense mainly where the connection identity is both high-value and stable enough to justify the extra operational burden.
A retail browser session to a random news site does not meet that bar. A payment app that talks only to one API endpoint might.
HPKP Took A Real Idea And Turned It Into A Public-Web Footgun
The most important historical lesson in pinning is HTTP Public Key Pinning, or HPKP.
HPKP was standardised in RFC 7469 in 2015. The idea was that a site could send an HTTP response header telling browsers which public keys should be accepted for future connections. In theory, this extended the benefits of pinning to the public web.
A representative header looked like this:
Public-Key-Pins: pin-sha256='base64=='; pin-sha256='backup=='; max-age=5184000; includeSubDomainsThe model had one appealing property. Browsers could learn pins dynamically, rather than every site needing its own custom app.
The problem was operational recovery.
If a site operator sent a bad pin set, lost the pinned key, or let a hostile intermediary inject a malicious pin header, the browser would remember that rule for the full max-age. The site could effectively deny service to its own users for weeks or months.
That was not a theoretical concern. HPKP created a failure mode where the recovery path itself was often harder than the original interception attack it tried to stop.
Several things made HPKP especially fragile:
- the browser was enforcing long-lived memory of server-supplied pins
- the site operator needed strong key-management discipline to avoid self-lockout
- header injection or control of the domain for even a short window could create hostile pins
- debugging client state became difficult because the failure persisted beyond the current certificate
The web platform moved away from HPKP because the internet scale version of pinning asked too much of average operators and created too much irreversible client-side state.
Browsers did not conclude that key narrowing was a nonsense idea. They concluded that server-declared pinning for the open web was too dangerous.
The replacement strategy was less direct but much safer:
- keep standard PKI validation
- require Certificate Transparency evidence for public issuance
- let domain owners monitor CT logs for misissuance
- use pinning only in controlled clients where the operator also controls rollout
That distinction matters. HPKP's failure does not mean every form of pinning is bad. It means the public-web browser model was the wrong operational surface for it.
Certificate Transparency, CAA, And Pinning Solve Different Problems
Because HPKP disappeared from browsers, teams sometimes blur several controls together. They are not interchangeable.
Certificate Transparency
Certificate Transparency, or CT, makes certificate issuance publicly auditable through append-only logs. A certificate that appears in the public web ecosystem is expected to have Signed Certificate Timestamps proving it was logged.
CT helps with detection and accountability. If a CA issues a bad certificate, the issuance can be observed later.
CT does not narrow which keys a client accepts in the moment. A valid but maliciously obtained certificate can still work before the operator notices and responds.
CAA DNS records
CAA lets a domain owner declare which certificate authorities are authorised to issue for the domain.
That reduces issuance scope at the policy layer, but it is still a CA-side control. It does not give the client a per-connection key check.
DANE
DANE binds certificate expectations into DNS and relies on DNSSEC for authenticity. It is elegant on paper and useful in some controlled environments, especially outside the ordinary browser web path.
It never became a mainstream browser-web replacement for public PKI.
Pinning
Pinning is a client-side acceptance rule. It says: even if PKI says yes, the key still has to be one of these expected keys.
The controls therefore live at different points in the system:
CAA -> restricts who should issue
CT -> exposes what was issued
PKI -> validates the presented chain
Pinning -> narrows which validated keys the client acceptsIf you mix them together, you tend to design the wrong defence.
A CT monitoring pipeline will not save a device that must reject substitute certificates immediately. A pin set will not tell you that a CA attempted misissuance for a domain your client never reached.
Modern Pinning Works Best In Controlled Clients, Not On The Open Web
In 2026, the strongest use cases for pinning are usually controlled clients with narrow communication patterns.
Examples include:
- a mobile banking app that talks to one API estate
- an embedded industrial gateway that calls one vendor endpoint
- an internal enterprise client shipped through MDM to managed devices
- a service-to-service client with tightly owned server identity and release cadence
These environments share three useful properties:
- the client talks to a small number of services
- the operator can ship client updates or staged pin sets deliberately
- the operational team can coordinate key lifecycle with software release lifecycle
That combination is rare on the open web and common in managed applications.
A mobile-app example
Suppose a Greek bank ships an iOS and Android app. The app talks only to:
api.bank.example.euauth.bank.example.eu
The bank owns the backend, certificate lifecycle, and app release cadence. It can maintain a key set per service, keep standby keys offline, and test rotations in staging before production. Pinning is painful, but the pain is contained.
A browser-web example
Now suppose you run a public website behind a CDN, sometimes move between certificate providers, sometimes use different edge keys per region, and need enterprise browsers, developer tools, and third-party integrations to keep working across many trust environments.
Pinning becomes much less attractive. The operational surface is larger than the security gain for most sites.
That is why most web operators today rely on:
- ordinary TLS validation
- CT enforcement and monitoring
- strong certificate issuance controls
- HSTS for downgrade resistance
- careful key management and short certificate lifetimes
rather than client pinning.
A service-to-service note
Between internal services, pinning is often not the only option and sometimes not the best one. Mutual TLS with a private PKI, SPIFFE-like workload identity, or service-mesh identity assertions can solve the same underlying trust problem more cleanly inside a controlled environment.
The question is not whether pinning is strong. The question is whether it is the cleanest way to express the identity constraint you actually need.
CDNs, Multi-Region Edges, And Pin Scope Get Awkward Fast
Pinning gets harder the moment one logical service is no longer one tidy certificate boundary.
Suppose the mobile app talks to api.bank.example.eu, but the bank delivers that hostname through a CDN with TLS termination in Frankfurt, Amsterdam, and Milan. On paper the hostname is one thing. Operationally, several models are possible:
- one shared key pair deployed to every edge
- one provider-managed certificate with one provider-managed key lifecycle
- several regional edge certificates under one hostname
- a migration period where two providers can both terminate the same hostname
Those models do not feel the same to a pinned client.
If the CDN uses one stable edge key everywhere, SPKI pinning is straightforward. If the CDN uses provider-managed keys that can rotate outside your application release cycle, the pin set now freezes a commercial dependency as well as a cryptographic identity. If two providers terminate during migration, the client may need to trust both key sets simultaneously for a period.
That is where teams discover that pinning scope is not only about cryptography. It is about infrastructure ownership.
A few common awkward cases:
Same hostname, different environments
Teams often want to pin production but keep staging flexible. That only works cleanly if staging uses distinct hostnames and distinct client configuration.
Bad pattern:
api.example.eu -> sometimes staging, sometimes production, depending on build flagBetter pattern:
api.example.eu -> production pin set
api.staging.example.eu -> staging pin setIf you reuse hostnames loosely, you will eventually ship the wrong pins or train people to disable checking when a test environment changes faster than production.
One app, many backends
A modern mobile app rarely calls only one service. It may call:
api.bank.example.euauth.bank.example.eumedia.bank.example.euconfig.bank.example.eu
Each hostname can have a different certificate lifecycle and perhaps a different infrastructure owner. One giant global pin set is tempting because it is easier to ship. It also widens trust inside the app. If every service pin is trusted for every hostname, you have recreated some of the sprawl you were trying to avoid.
The cleaner design is usually per-hostname or per-service pin sets:
api.bank.example.eu -> pins A, B
auth.bank.example.eu -> pins C, D
media.bank.example.eu -> pins E, FThat is more work. It keeps failure domains smaller. A media-stack certificate change does not need to share fate with the login stack.
Provider migration
Imagine the bank is moving from one CDN to another. During cutover, traffic may legitimately hit either edge fleet. The app therefore has three options:
- ship both providers' keys before migration
- route all app traffic to one provider until clients update
- disable pinning temporarily and accept the broader trust model
Option 1 is the clean one if you have enough release lead time. Option 2 can work if you can segment traffic cleanly. Option 3 is sometimes chosen under time pressure, and it should be called what it is: a temporary reduction in assurance for the sake of operational recovery.
Wildcards and subdomains
Wildcard certificates can tempt teams to pin one key and reuse it across many subdomains. That reduces management overhead. It also broadens the blast radius of key compromise and operational error. If one wildcard key covers public web, admin APIs, and internal tools, the pin set has silently become a central dependency.
Pinning is usually healthiest when the key scope matches the trust boundary you actually mean.
Delegated credentials and similar mechanisms
Some advanced TLS deployments use delegated credentials or other short-lived edge identity mechanisms to avoid pushing the main certificate private key everywhere. That can reduce private-key exposure on the server side, but it does not automatically simplify pinning on the client side. The client still needs a stable trust story for the authenticated chain and whichever public key identity is expected to appear at validation time.
The general lesson is that pinning interacts directly with infrastructure topology. Before you pin, map the real termination surface:
hostname -> provider -> region -> key owner -> rotation cadenceIf that map looks unstable, pinning will inherit the instability.
Test The Failure Path Before Production Or The First Real Rotation Will Be Your Test
Teams usually test the happy path:
- correct hostname
- correct certificate
- correct pin
- connection succeeds
That test is necessary and almost useless by itself.
The expensive part of pinning lives in the unhappy path. You need to know what the client, telemetry, and support process do when the pin does not match.
At minimum, test these cases deliberately in staging:
- valid PKI chain, wrong key
- renewed certificate, same key
- rotated certificate, backup key
- rotated certificate, no backup key
- enterprise inspection root inserted on a managed test device
- expired pin configuration if the platform supports pin expiry
The point is not only to confirm accept versus reject. The point is to verify the full operational loop:
- does the app emit a distinct error?
- is the hostname captured?
- is the platform version captured?
- can support tell pin failure from packet loss or DNS failure?
- do dashboards show a sudden region-specific mismatch?
A production-quality pinning rollout needs observability that looks more like incident tooling than like simple feature telemetry.
A useful failure log shape is something like:
event=pin_validation_failed
host=api.bank.example.eu
platform=android
app_version=8.4.2
os_version=15
path_validation=passed
presented_spki=sha256/hp2vQ0cP8LqKj4qD9zY3yG8nT6vF1mB5sR7uA1cN4Lo=
expected_pins=[sha256/8A1eu..., sha256/wY3R9...]
network=wifi
region=de-frankfurtThat lets the operations team answer the first crucial question quickly: is this one broken handset, one enterprise environment, one region, one app version, or a global rollout mistake?
Rehearse rotation, not just mismatch
A staged exercise is worth more than a policy document.
For example:
- generate a backup key
- ship it in staging clients
- move the staging hostname to the backup key
- verify old staging clients still connect
- remove the old key from the pin set in a later staging release
- repeat until the runbook feels boring
If the first time the team performs that dance is during a live compromise or an emergency certificate replacement, the system is not operationally ready.
Decide what the user sees
Some applications should fail closed with a blunt hard-stop message. Some should offer a retry and diagnostics path. Some internal clients may surface a support code and environment details. What they should not do is turn a trust failure into a vague generic network error that leads users and helpdesk staff to chase the wrong problem for hours.
Remember old clients
A pinning migration is never only about today's release. It is about the oldest still-installed client you are willing to support. If the answer is eighteen months, your pin strategy has to be viable across eighteen months of certificate and key operations.
That is why disciplined teams keep a simple matrix:
client version -> trusted pin set -> earliest safe server key removal dateWithout that matrix, people guess. Guessing is how you end up revoking the only key an older supported app still trusts.
The happy-path test proves that the crypto works. The failure-path drill proves that the system is operable. Pinning needs both.
The Decision To Pin Should Start With Operational Questions, Not Crypto Enthusiasm
A team considering pinning should ask operational questions first.
Can the client be updated reliably?
If old clients may remain in the field for years with no guaranteed update channel, pinning raises the cost of every certificate and key decision.
Can the service keep a staged backup key?
If the answer is no, you do not yet have a recovery story.
Does the service sit behind infrastructure that may legitimately change keys?
CDNs, DDoS scrubbing providers, regional TLS termination, and corporate inspection requirements can all complicate the key identity you are trying to freeze.
Is the threat model really rogue trust anchors or misissuance?
If the bigger risks are stolen bearer tokens, broken authorisation, or a compromised client device, pinning may consume effort without moving the dominant risk.
What is the failure mode if the pin is wrong?
This question should make people uncomfortable. That is good. If the answer is 'our payment app bricks itself across Europe until an emergency release clears review', the team should feel the weight of the choice.
A practical checklist before rollout looks like this:
[ ] exact hostname scope decided
[ ] pin object chosen: cert, leaf SPKI, or chain SPKI
[ ] backup key generated and stored safely
[ ] staged pin set shipped before rotation
[ ] expiry / recovery policy decided
[ ] staging and production chains both tested
[ ] incident runbook written for pin mismatch and key compromise
[ ] observability added for pin failures by platform versionThat list is more important than the hash algorithm. Most modern deployments use SHA-256 and move on. The failures come from lifecycle mistakes, not from forgetting which digest to pick.
Pinning Is Best Understood As A Trust-Narrowing Tool With An Expensive Recovery Story
The shortest honest description of certificate pinning is this:
Pinning is a client-side rule that says a normally valid TLS chain is still not acceptable unless its key material matches a pre-authorised set.
That rule is useful when the ordinary trust store is broader than your application is willing to tolerate. It is risky because the same rule can block your own legitimate certificate changes just as effectively as it blocks an attacker.
Once you see pinning in those terms, most design choices become clearer.
- Pinning the whole certificate is usually too brittle.
- Pinning SPKI is usually the right level.
- A backup pin is not optional if you want a real recovery path.
- Ordinary PKI validation still has to run first.
- HPKP failed because public-web recovery was worse than the problem for most operators.
- Controlled clients remain the natural home for pinning.
The hard part was never computing the hash. The hard part is living with the consequences of saying, in code, exactly which keys the client will trust and no others.