Deprecated headers
HPKP and Expect-CT are dead TLS experiments that browsers removed. What they were, why they died, and why the fix is to delete them.
Last update:
Two TLS security headers came and went: Public-Key-Pins (HPKP) and
Expect-CT. Both tried to protect sites against misissued certificates, HPKP
by pinning public keys in the browser and Expect-CT by requiring
Certificate Transparency
(CT) before it was mandatory.
Browsers have removed both. If a scan finds either header on your site, the fix is to delete it.
Removed from browsers
Every browser ignores both headers today: HPKP and Expect-CT were both removed from browsers years ago. HPKP was removed because a lost or hostile pin could lock users out of a site (self-DoS and hostile pinning); Expect-CT became pointless once Certificate Transparency was enforced by default. Details in Browser support.
There is no replacement header to send. What superseded them, Certificate Transparency and CAA DNS records, works without any response header, so the only action these headers call for is removing them from your server configuration.
At a glance
| Header | Status | What it was | What to do |
|---|---|---|---|
Public-Key-Pins | ⚠️ Deprecated | Pinned CA public keys in the browser | Remove |
Public-Key-Pins-Report-Only | ⚠️ Deprecated | Report-only pinning | Remove |
Expect-CT | ⚠️ Deprecated | Opt-in Certificate Transparency enforcement | Remove (CT is the default) |
Public-Key-Pins (HPKP)
HTTP Public Key Pinning (RFC 7469,
2015) let a site pin the public keys its certificate chain had to contain. The
header carried one or more pin-sha256 values, each a base64 hash of a
public key (its SPKI), plus a required max-age, with directives separated by
semicolons. The browser cached the pins and refused any future connection to
the host whose certificate chain did not include one of the pinned keys. The
spec required a backup pin, a pin for a key not in the current chain, as a
recovery path, and a Public-Key-Pins-Report-Only variant reported instead of
blocking.
This is what a deployment looked like. Never send it today; no browser reads it.
Public-Key-Pins: pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=";
pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=";
max-age=5184000; includeSubDomainsThree problems killed it:
- Self-DoS. Lose the pinned keys and every returning browser refuses your
site until
max-ageexpires, and 60-day values were typical, so a single mistake could lock every returning visitor out of the site for weeks. - Hostile pinning, known as RansomPKP. An attacker who briefly controls the server can pin keys only they hold and ransom them back; browsers keep rejecting the legitimate certificate after the site owner recovers. Chrome's removal rationale names exactly these "risks of denial of service and hostile pinning".
- Near-zero adoption. Usage peaked around 3,500 sites in the top 1 million and had fallen to about 650 by September 2019, many of them misconfigured.
Chrome removed HPKP years ago, and Firefox, which had shipped it earlier, removed it soon after, since which the headers are silently ignored. Safari and Edge never shipped it.
Expect-CT
Expect-CT (RFC 9163) was an
opt-in bridge to Certificate Transparency enforcement, for the period before
CT became mandatory. It took a required max-age, an enforce flag, and a
report-uri, separated by commas rather than HPKP's semicolons (a classic
source of confusion between the two). Both headers reported through bespoke
report-uri JSON POSTs that predate the
Reporting API; any endpoints still
configured for them are dead letterboxes today.
Expect-CT: max-age=86400, enforceThe header became pointless by policy. Chrome requires CT for all publicly trusted certificates issued after April 30, 2018, so a site had nothing left to opt into; the last pre-CT certificates expired around June 2021. The RFC was only published in 2022, as Experimental, the same year Chrome removed the header.
Chrome removed Expect-CT years ago. Firefox and Safari never implemented it.
What replaced them
- Certificate Transparency, on by default. Chrome requires CT for certificates issued after April 30, 2018, and Apple platforms for certificates issued after October 15, 2018. Certificate authorities embed the SCT proofs in the certificate itself; sites send nothing.
- CAA DNS records (RFC 8659) declare which certificate authorities may issue for your domain.
- In-app pinning for clients you control (mobile apps, machine-to-machine clients), per the OWASP Pinning cheat sheet.
One honest caveat: CT and CAA together do not cover quite every feature HPKP had, but they give comparable protection against misissuance without HPKP's risks.
Risks of keeping them
There is no enforcement risk, because nothing parses these headers anymore; a stale pin cannot lock anyone out via the header today. The costs are operational: dead bytes on every response, scanners and pentest reports that flag them as deprecated findings, and the signal a 2017-era dead header sends about an unmaintained security configuration. Run your site through the security headers scanner to check whether either header is still in your responses.
Recommendation
Remove Public-Key-Pins, Public-Key-Pins-Report-Only, and Expect-CT
from your server configuration. Certificate Transparency is enforced by
default for every certificate a public CA can issue today, per the
Chrome CT policy,
so there is nothing to configure in its place. If you genuinely need pinning
for a client you control, pin in the client following the
OWASP Pinning cheat sheet,
never through a browser header.
Browser support
Public-Key-Pins:
| Browser | Support |
|---|---|
| Chrome | Supported for a while, removed years ago |
| Firefox | Supported for a while, removed years ago |
| Opera | Supported for a while, removed years ago |
| Safari | Never shipped |
| Edge | Never shipped |
Expect-CT:
| Browser | Support |
|---|---|
| Chrome and Chromium browsers | Supported for a while, removed years ago |
| Firefox | Never implemented |
| Safari | Never implemented |
FAQ
Is Expect-CT still needed?
No. Remove it. Chrome dropped support for the header, and it was Chromium-only to begin with. Certificate Transparency is now enforced by default for every certificate a public CA can issue, so there is nothing left for the header to opt into. It is dead bytes that scanners flag as a deprecated finding.
What replaced HPKP?
Header-side, nothing to send. Certificate Transparency runs by default, so CAs embed the proof in the certificate and your site configures nothing. CAA DNS records let you declare which CAs may issue for your domain. For clients you control, such as mobile apps, pin in the client using the OWASP pinning guidance, never through a browser header.
Should I remove Public-Key-Pins?
Yes, remove both Public-Key-Pins and its report-only form. No current browser
processes them; every engine that shipped HPKP removed it. Sending it today
enforces nothing, but it adds dead bytes to every response and signals an
unmaintained security config that scanners and pentest reports flag.
See also
- Legacy headers covers the policy headers that have modern replacements (X-Frame-Options, Feature-Policy, X-XSS-Protection), a separate set from the two dead TLS headers on this page
- Security headers overview
- Information disclosure headers, the other headers a scan tells you to remove: Server, X-Powered-By, and the rest of the version banners
- Strict-Transport-Security, the TLS security header you should send
- Security headers scanner to check your deployed headers
Sources
- RFC 7469, Public Key Pinning Extension for HTTP
- RFC 9163, Expect-CT Extension for HTTP
- RFC 8659, DNS Certification Authority Authorization (CAA)
- Chrome Platform Status, Remove HTTP-Based Public Key Pinning
- Chrome Platform Status, Expect-CT deprecation entry
- Chrome Certificate Transparency policy
- Firefox release notes on HPKP removal
- OWASP Pinning cheat sheet
Information disclosure
Server, X-Powered-By, and the other headers that leak your stack and versions to attackers, the full list and how to remove them.
Subresource Integrity
SRI is the integrity attribute that makes the browser hash a fetched script or stylesheet and refuse it on mismatch, defending against a tampered CDN.