Oblivious HTTP (OHTTP, RFC 9458) sends an encrypted HTTP request through a relay to a gateway, so the relay learns who you are but not what you asked, and the gateway learns what you asked but not who you are. It protects small, stateless requests, and it only holds if the relay and gateway operators do not collude.
That one sentence hides a lot of engineering. OHTTP is built from three other IETF documents (Hybrid Public Key Encryption, Binary HTTP and a DNS based discovery record), it was produced by a working group that has since closed, and it now sits quietly inside browser safety checks, search suggestions, ad measurement experiments and at least one cloud AI privacy design. This post goes one level deeper than our explainers on encrypted client hello, DNS protection and what a VPN does and does not protect. It walks through the message formats byte by byte, sets out exactly what each party sees, compares OHTTP with VPNs, Tor, MASQUE proxies and Oblivious DNS over HTTPS, and ends with a lab you can run on one laptop to watch what a relay can and cannot log.
Key takeaways
- OHTTP separates identity from content. The relay sees your IP address and the fact that you sent an encrypted blob to a particular gateway. The gateway sees the decrypted request but only the relay's IP address. RFC 9458 states that no entity other than the client can see the plaintext request and attribute it to the client.
- It rests on a non-collusion assumption. The RFC says the relay "cannot be operated by the same entity" as the gateway. The OHAI working group charter is blunter: if the proxy and server collude, neither privacy property holds.
- It is for small, independent requests. Safety lookups, telemetry, search suggestions, DNS style queries and anonymous counters fit. Logged in browsing, cookies, sessions and streaming responses do not.
- The building blocks are standard. Requests are encoded as Binary HTTP (RFC 9292), encrypted with HPKE (RFC 9180) under a gateway public key distributed as
application/ohttp-keys, and optionally discovered through anohttpparameter in SVCB or HTTPS DNS records (RFC 9540). - The overhead is small but real. With the common X25519, HKDF-SHA256 and AES-128-GCM suite, an encapsulated request adds 55 bytes and an encapsulated response adds 32 bytes, plus a second HTTP hop.
- Its limits are documented, not hidden. Replay, traffic analysis on timing and size, configuration partitioning, relay "differential treatment", no forward secrecy within a key's lifetime and no progressive responses are all spelled out in RFC 9458 itself.
- It is not a VPN and not Tor. A VPN hides your traffic from the local network but sees everything itself. Tor hides you from almost everyone at a latency cost. OHTTP hides one specific linkage, for one cooperating service, with little extra latency.
What is Oblivious HTTP, and what problem does it solve?
Every HTTP request carries information you cannot fully control. RFC 9458 opens with that observation: even when an IP address is not directly tied to a person, requests from it can be correlated over time into a profile of the client, and reusing a connection, which is good for performance, lets a server link the requests that share it.
Before OHTTP there were two blunt options. You could send requests through an ordinary HTTP proxy without TLS, which hides your address from the server but shows the proxy everything. Or you could tunnel TLS through the proxy, which hides the content from the proxy but needs a fresh connection for every request if you do not want the server to link them by connection, and that is slow. RFC 9458 describes exactly this trade off and proposes a third option: encrypt each request to the server's public key at the application layer, then send it through a relay that can see your address but not the content, over connections that can be reused because the encryption, not the connection, carries the per request privacy.
The result, as the RFC frames it, is that the gateway never learns the client's IP address and the relay never sees the plaintext HTTP messages.
Who produced it: the OHAI working group
OHTTP came out of the IETF's Oblivious HTTP Application Intermediation working group, OHAI, in the Security Area. The group's datatracker history shows a BoF at IETF 111 under the name OHTTP, a charter approved on 7 October 2021, and the core protocol milestone marked done once RFC 9458 was published. The charter took draft-thomson-http-oblivious as its input document.
The charter is worth reading because it states the scope in plain terms. The protocol is meant for discrete, transactional queries, each revealing a little, that add up to a lot over time, and it names DNS queries, telemetry submission and certificate revocation checking as examples. It also says general purpose web browsing is out of scope, and that transport level proxies such as HTTP CONNECT or MASQUE suit that job better because they work with unmodified servers.
The working group has now finished. The datatracker records the group's state changing to Concluded on 27 February 2026. Its outputs are RFC 9458 (Oblivious HTTP, January 2024) and RFC 9540 (discovery through service binding records, February 2024). A third document, Chunked Oblivious HTTP Messages (draft-ietf-ohai-chunked-ohttp), was submitted for publication as a Proposed Standard and, when we checked on 24 October 2026, was listed in the RFC Editor queue rather than published, so treat it as a draft until it has an RFC number.
A dated timeline
| Date | Event | Source |
|---|---|---|
| 7 October 2021 | OHAI charter approved after an OHTTP BoF at IETF 111 | IETF datatracker |
| February 2022 | RFC 9180, Hybrid Public Key Encryption, published (Informational) | RFC 9180 |
| June 2022 | RFC 9230, Oblivious DNS over HTTPS, published as Experimental, developed outside the IETF | RFC 9230 |
| August 2022 | RFC 9292, Binary Representation of HTTP Messages, published | RFC 9292 |
| August 2022 | RFC 9298, Proxying UDP in HTTP (the MASQUE CONNECT-UDP method), published | RFC 9298 |
| October 2023 | RFC 9484, Proxying IP in HTTP, published, updating RFC 9298 | RFC 9484 |
| January 2024 | RFC 9458, Oblivious HTTP, published as a Proposed Standard | RFC 9458 |
| February 2024 | RFC 9540, Discovery of Oblivious Services via Service Binding Records, published | RFC 9540 |
| March 2024 | Google describes real time Safe Browsing checks in Chrome sent through an OHTTP relay | Google Security Blog |
| 10 June 2024 | Apple describes Private Cloud Compute requests going through a third party OHTTP relay | Apple Security Research |
| 27 February 2026 | OHAI working group concluded | IETF datatracker |
| 24 October 2026 | Chunked OHTTP draft in the RFC Editor queue, not yet an RFC | IETF datatracker |
The building blocks came first. HPKE and Binary HTTP were finished in 2022 while the working group was still shaping the core protocol, which is why OHTTP could reuse them rather than invent its own encryption or message format.
How Oblivious HTTP works
RFC 9458 defines four roles. Getting them straight is most of the battle.
- Client. The software that wants to make a request privately. It is an HTTP client twice over: it talks to the relay over ordinary HTTPS, and it is the logical client of the target inside the encryption.
- Oblivious Relay Resource, or relay. An intermediary that forwards encapsulated requests and responses between clients and exactly one gateway.
- Oblivious Gateway Resource, or gateway. It decrypts the encapsulated request, forwards it to the target, takes the response, encrypts it and sends it back.
- Target Resource, or target. The ordinary HTTP resource that handles the request. It might not even know OHTTP was involved.
The RFC treats the gateway and target as logically controlled by the same party, because the gateway can decide what responses come back. Its security analysis assumes three entities: the client, the relay operator, and a server that runs both gateway and target.
The flow, step by step
RFC 9458 lists the steps in its overview. In order:
- The client builds an ordinary HTTP request for the target.
- It encodes that request as a Binary HTTP message and encrypts it with HPKE under the gateway's public key. The result is an Encapsulated Request.
- It sends a
POSTto the relay with the Encapsulated Request as the body andContent-Type: message/ohttp-req. - The relay forwards the body to the gateway in a new
POST. - The gateway removes the HPKE protection, recovers the HTTP request and handles it, usually by making a request to the target.
- The gateway encrypts the target's response into an Encapsulated Response and returns it as a
200withContent-Type: message/ohttp-res. - The relay passes the response back to the client.
- The client decrypts it.
Two details matter for privacy. First, the outer request to the relay should carry only what is needed: a POST, the relay URI, a content type and the body. Any extra header fields must be ones the relay will strip before forwarding. Second, the gateway sends a single 200 for any response it gets from the target, even an error, with the real status code hidden inside the encryption. Only errors that happen before decryption, such as an unknown key, show up as unencrypted status codes.
Key configuration and the application/ohttp-keys format
The client must know the gateway's public key before it can encrypt anything. RFC 9458 does not say how the client gets it, but it does define a format, and it requires that the key configuration be authenticated and integrity protected so that nobody can trick the client into encrypting to a key they control.
A key configuration contains:
| Field | Size | Meaning |
|---|---|---|
| Key Identifier | 8 bits | Which gateway key this is, so the gateway can rotate keys |
| HPKE KEM ID | 16 bits | The key encapsulation method, from the HPKE registry |
| HPKE Public Key | Npk bytes | The gateway's public key, sized by the KEM |
| Symmetric Algorithms Length | 16 bits | Byte length of the list that follows |
| Symmetric Algorithms | 32 bits each | Pairs of KDF ID and AEAD ID that the gateway accepts |
A collection of configurations is served with the media type application/ohttp-keys, each one prefixed with a two byte length. The RFC adds a subtle rule: clients must discard a collection with encoding errors entirely, because differences in how clients recover from broken data could be used to sort clients into smaller, trackable groups.
Worked example 1: decoding a real key configuration
The open source Rust implementation by Martin Thomson (the ohttp repository on GitHub, by one of the RFC's authors) ships a test server that prints its key configuration as hex when it starts. The README shows this example:
002d0000200109ed5f13a5eb012834f66cd133f114f143f0e3f7b899dc3f9dbfee9668496800080001000100010003
That is 47 bytes. Read against the format above and the HPKE registry in RFC 9180:
| Bytes | Value | Meaning |
|---|---|---|
| 2 | 002d | Length prefix: 45 bytes of configuration follow |
| 1 | 00 | Key Identifier 0 |
| 2 | 0020 | KEM 0x0020, DHKEM(X25519, HKDF-SHA256) |
| 32 | 0109ed5f...96684968 | The X25519 public key (Npk is 32 for this KEM) |
| 2 | 0008 | 8 bytes of symmetric algorithm pairs follow |
| 4 | 0001 0001 | KDF 0x0001 HKDF-SHA256 with AEAD 0x0001 AES-128-GCM |
| 4 | 0001 0003 | KDF 0x0001 HKDF-SHA256 with AEAD 0x0003 ChaCha20Poly1305 |
Check the arithmetic: 1 + 2 + 32 + 2 + 8 = 45, which is 0x2d. The test server source confirms what we decoded: it builds a config with key ID 0, Kem::X25519Sha256, and the two symmetric suites listed. It also generates a fresh key every time it starts, which is why the hex differs on your machine.
Binary HTTP: what actually gets encrypted
OHTTP does not encrypt HTTP/1.1 text. It encrypts a Binary HTTP message, defined in RFC 9292. A binary message has a framing indicator that says whether it is a request or response and whether lengths are known up front, then control data (method, scheme, authority and path for a request; status for a response), a header section, content, a trailer section and optional padding made of zero bytes. All integers use the QUIC variable length encoding.
The framing indicator values are small integers: 0 for a known length request, 1 for a known length response, and 2 and 3 for the indeterminate length forms. Known length is simpler and is what most OHTTP deployments send.
The padding matters more than it looks. RFC 9458 points to Binary HTTP padding as the tool clients and gateways can use to make traffic analysis less effective. Because the padding sits inside the encryption, a relay cannot tell how much of a message is real content.
HPKE: the encryption layer
Hybrid Public Key Encryption, RFC 9180, combines a key encapsulation method, a key derivation function and an authenticated encryption algorithm into one scheme for encrypting to a public key. OHTTP uses HPKE's base mode, which is unauthenticated on the sender side. That is a deliberate choice: the client must not prove who it is to the gateway.
The client side of RFC 9458 section 4.3, in prose:
- Build a 7 byte header from the key ID (1 byte) and the KEM, KDF and AEAD IDs (2 bytes each).
- Build an
infostring: the ASCII textmessage/bhttp request, a zero byte, then the header. - Call HPKE
SetupBaseSwith the gateway's public key and thatinfo. This returns a sending context andenc, the encapsulated key. - Encrypt the Binary HTTP request with the context's
Seal, with empty associated data. - The Encapsulated Request is the header, then
enc, then the ciphertext.
Because the header is bound into info, a relay that changes the algorithm IDs in transit causes decryption to fail rather than a silent downgrade.
For the response, the gateway does not use a second public key exchange. It exports a secret from the same HPKE context using the label message/bhttp response, picks a random response_nonce, and derives a fresh AEAD key and nonce from the export, enc and the response_nonce. The Encapsulated Response is just response_nonce followed by ciphertext. Only the client, which holds the matching sending context, can derive the same key.
The RFC also requires clients to generate a new HPKE context for every request. Reusing one would link requests and could expose content to the relay.
Worked example 2: how many bytes does OHTTP add?
Using the field sizes in RFC 9458 and the algorithm parameters in RFC 9180:
Suite A: X25519, HKDF-SHA256, AES-128-GCM (the first suite in the test configuration above).
- Request overhead: 7 byte header + 32 byte
enc(Nenc for X25519) + 16 byte AEAD tag = 55 bytes. - Response overhead: nonce of max(Nn, Nk) = max(12, 16) = 16 bytes + 16 byte tag = 32 bytes.
Suite B: X25519, HKDF-SHA256, ChaCha20Poly1305.
- Request overhead: 7 + 32 + 16 = 55 bytes, the same.
- Response overhead: max(12, 32) = 32 byte nonce + 16 byte tag = 48 bytes, because ChaCha20Poly1305 has a 32 byte key and the response nonce is sized to the larger of key and nonce.
Suite C: P-256, HKDF-SHA256, AES-128-GCM.
- Request overhead: 7 + 65 (Nenc for DHKEM P-256) + 16 = 88 bytes.
- Response overhead: 32 bytes, as in Suite A.
So a 120 byte Binary HTTP lookup becomes a 175 byte body with Suite A, about 46 percent larger, plus whatever padding you choose. For a 20 KB telemetry batch the same 55 bytes are noise. The real cost is not the bytes but the second hop and the HPKE public key operation on every request, which RFC 9458 says takes a non trivial amount of computation.
Discovery: the ohttp parameter in DNS (RFC 9540)
RFC 9458 leaves configuration to the application. RFC 9540 adds one standard way to find a gateway. A target can publish an ohttp parameter in its SVCB or HTTPS DNS record to say it is reachable through OHTTP, for example:
svc.example.com. 7200 IN HTTPS 1 . ( alpn=h2 ohttp )
The client then finds the gateway at the well known path /.well-known/ohttp-gateway on the target, and fetches the key configuration with a GET that asks for Accept: application/ohttp-keys. Because the well known path lives on the target, this model means the gateway and target are on the same host.
RFC 9540 is honest about two gaps. It does not help clients find relays at all; relay configuration is out of scope. And fetching the key configuration directly from the gateway exposes the client's IP address to the gateway, so the RFC suggests fetching through a proxy and running consistency checks so a client is not handed a unique key. It also notes that an attacker on the network can strip the SVCB hint from unprotected DNS answers, so clients should check the well known location anyway, and encrypted DNS with DNSSEC helps.
What does each party see?
This is the question that matters most. The table below draws on RFC 9458 sections 6 and 7 and assumes the standard three party deployment: client, relay operator, and a server running gateway and target.
| Party | Sees | Does not see |
|---|---|---|
| Local network and ISP | That you connect to the relay over HTTPS; timing and sizes of TLS records | Which gateway you use (it is inside TLS), the request content |
| Relay operator | Your IP address and port, TLS metadata, the time and size of each encapsulated message, which gateway it forwards to, the key ID and algorithm IDs in the 7 byte header | The decrypted request or response, the target path, cookies or headers inside the encryption |
| Network between relay and gateway | The relay's address and the gateway's address, sizes and timing | Your IP address, the request content |
| Gateway and target operator | The full decrypted request and response, the relay's IP address, the request's arrival time, any Date header you included | Your IP address and port, which other requests came from you (unless content links them) |
| Anyone with the gateway's private key later | Every recorded request sent under that key, if they also have the encrypted traffic | Your IP address, unless the relay cooperates |
Two rows deserve comment, and a third risk sits outside the table.
The relay sees more than people assume. It terminates your TLS connection, so it sees message boundaries, exact sizes and timing. RFC 9458 notes that this privileged position gives the relay richer features to extract from encrypted traffic, which can help traffic analysis. It also sees the unencrypted header of each Encapsulated Request, so it knows which key and suite you chose.
The gateway sees everything you put inside. OHTTP removes linkage at the transport layer and nothing else. If your request contains a cookie, an account ID, a device serial number, a precise location or a stable identifier, the gateway can link and attribute your requests as easily as without OHTTP. RFC 9458 sets the goal that independent requests are linkable only by their content. The content is your responsibility.
Key compromise is retrospective. Anyone who records relay to gateway traffic and later steals the gateway private key can decrypt it, though they still would not learn your IP address without the relay's logs. The limits section below covers why.
The non-collusion assumption
All of OHTTP's privacy comes from splitting knowledge between two operators, which is why RFC 9458 rules out one entity running both relay and gateway. The OHAI charter puts it more starkly: "if the proxy and server collude, then neither of these privacy properties hold."
Collusion does not need to be dramatic. Each of these weakens the split:
- The same company runs both, perhaps under different brand names.
- The relay forwards an
X-Forwarded-For,ForwardedorViaheader, which many reverse proxies add by default. RFC 9458 forbids a relay from adding information that could identify clients, and says it should not forward unknown fields. - The relay keeps detailed logs with timestamps and sizes, and the gateway keeps its own logs with the same. A court order, a breach or a data sharing deal can join the two.
- The gateway asks the relay to block or rate limit specific traffic. RFC 9458 warns that a relay acting on gateway feedback could deanonymise clients, by accident or by design.
That is why real deployments describe a contract and an independent operator, not just a protocol. Google's k-anonymity explainer for its Privacy Sandbox says it is engaging a third party company to run the relay and expects "a contractual relationship that ensures these entities don't collude". Apple's Private Cloud Compute post says the relay is "operated by a third party". These are statements about operations and legal arrangements, and readers should weigh them that way.
Differential treatment and the anonymity set
RFC 9458 section 6.2.1 lets a relay add information to requests only if the client knows what range of values it might add, and never anything that uniquely and permanently identifies the client, pseudonymous identifiers included. It also lets relays treat abusive clients differently, for example by rate limiting them. The catch is that every difference shrinks the crowd you hide in. If a relay blocks some clients, the gateway knows every request it does receive came from a client that was not blocked.
Configuration partitioning
The privacy considerations section adds a quieter risk. A client's configuration is the relay URI, the gateway URI and the gateway key. If a gateway hands each client a different key, the key ID alone identifies the client on every request. RFC 9458 requires applications to take steps against tracking through client configurations and points to separate work on key consistency. In practice that means few, widely shared key configurations, fetched in a way that lets clients compare them, and rotation that does not leave small groups on old keys for long.
OHTTP compared with a VPN, Tor, MASQUE proxies and ODoH
People reach for OHTTP when they mean something else, and the reverse. Here is how the five approaches differ.
OHTTP versus a VPN
A VPN tunnels all your traffic to one provider, which then sends it on with its own IP address. Your local network sees only a tunnel. The VPN provider, however, sees your IP address and every destination, and for any traffic not protected by TLS, the content too. It is one trusted party, not two. Our post on what a VPN protects and what it does not covers that trust in detail. OHTTP, by contrast, protects only the requests of applications built to use it, but it does not ask you to trust any single operator with both halves.
OHTTP versus Tor
The Tor Project's relay documentation describes a circuit as user, guard relay, middle relay, exit relay, then destination, with the exit relay's address the one the destination sees. Tor uses layered encryption over three relays chosen from a public directory, so no single relay knows both ends. It works for general browsing with unmodified sites. The costs are latency and the fact that many services block or challenge exit addresses. RFC 9458 itself places OHTTP below Tor on strength: it describes OHTTP as simpler and cheaper than systems such as Prio or Tor, which offer stronger guarantees at a higher operating cost.
OHTTP versus MASQUE proxies
MASQUE is the family of HTTP based tunnelling methods. RFC 9298 defines CONNECT-UDP, which lets an HTTP client create a UDP tunnel through an HTTP server acting as a proxy, using a URI template such as https://example.org/.well-known/masque/udp/{target_host}/{target_port}/. RFC 9484 extends this to whole IP packets. A MASQUE proxy relays a connection; the end to end TLS or QUIC session with the target runs inside it, so the proxy does not see content, and the target does not see your address. Chain two proxies and you get a two hop design.
Apple's iCloud Private Relay overview (December 2021) is the clearest public example. It sends Safari traffic through "two separate internet relays operated by different entities", an ingress proxy that sees your IP address and an egress proxy that assigns a relay IP address and connects to the site. The overview says connections use CONNECT and CONNECT-UDP over HTTP/3, from the MASQUE work, falling back to CONNECT over TLS 1.3 where QUIC is blocked. So Private Relay is a MASQUE design for browsing, not OHTTP. The difference is that MASQUE carries a long lived connection to any server, while OHTTP carries one encrypted request to one cooperating gateway.
Google's k-anonymity explainer gives a practical reason to choose OHTTP even where MASQUE exists: because each request is a small, ordinary POST to the relay, it expects OHTTP to get through restrictive networks, such as those with mandatory forward proxies, more often than CONNECT or MASQUE proxy requests.
OHTTP versus Oblivious DNS over HTTPS
ODoH, RFC 9230 (June 2022, Experimental), is OHTTP's older cousin for DNS. A client encrypts a DNS query with HPKE to an Oblivious Target's public key and sends it through an Oblivious Proxy, so that no single server can read both the client's IP address and the DNS query. The RFC says it was "developed outside the IETF" and published to guide experimentation. Apple's Private Relay overview says it uses ODoH for DNS queries, sent through the first relay, with each query padded and encrypted with HPKE so the relay cannot see the name being looked up.
OHTTP generalises the idea to any HTTP request. RFC 9458 even sketches how a future specification could reuse its encapsulation for DNS messages with labels like application/dns-message request. RFC 9540 also lets a DNS server's SVCB record advertise an ohttp gateway, so DNS over HTTPS through OHTTP is a supported path.
Comparison table
| Approach | What it hides, and from whom | Added latency | Trust model | Typical use |
|---|---|---|---|---|
| OHTTP (RFC 9458) | Client IP from the gateway and target; request content from the relay and network | One extra HTTP hop per request; connections are reused | Two operators that must not collude; gateway sees content | Safety lookups, telemetry, suggestions, anonymous counters, privacy features in apps |
| VPN | Traffic from the local network and ISP; your IP from destinations | One tunnel hop, varies by provider location | One provider sees your IP and all destinations | Untrusted Wi-Fi, changing apparent location, reaching private networks |
| Tor | Your IP from destinations; destinations from the guard; content from relays until the exit | Three relays, noticeably slower | Distributed volunteers, no single relay knows both ends; exit sees unencrypted traffic | General anonymous browsing, censorship resistance, onion services |
| MASQUE proxy (RFC 9298, RFC 9484) | Your IP from the target; content from the proxy (end to end TLS or QUIC inside) | One proxy hop, two in chained designs | One proxy, or two operators in a two hop design | Browsing privacy relays, UDP and QUIC tunnelling, enterprise egress |
| ODoH (RFC 9230) | Client IP from the resolver; query content from the proxy | One extra hop per query | Proxy and target must not collude | Private DNS resolution |
Where OHTTP is used today
Below are the main categories of use, each described only from the operator's own documentation as we read it on 24 October 2026.
Browser safety lookups
Google's Security Blog post from March 2024 describes moving real time Safe Browsing checks into Chrome's default Standard protection mode. Chrome hashes a visited URL into 32 byte full hashes, truncates them to 4 byte prefixes, encrypts the prefixes with a Safe Browsing public key, and sends them to a privacy server run by an independent company, which strips identifiers such as IP address and forwards them. The post says this privacy server is an OHTTP relay, that "Safe Browsing does not see your IP address", and that because the relay lacks the private key it cannot decrypt the hash prefixes. This is the textbook OHTTP case: small, frequent, privacy sensitive lookups with no need for state.
Browser search suggestions and telemetry
Mozilla's Firefox Privacy Notice (effective 4 May 2026 when we read it) mentions OHTTP several times. It says that in the United States Firefox fetches suggestions from Mozilla's servers as you type, and that the query and related data "are passed through this OHTTP proxy by default before being sent to Mozilla", to help prevent anyone, Mozilla included, from associating a query with you. It also says de-identified interaction data for suggestions, some limited browsing data used for performance monitoring, and technical data collected to study fingerprinting are sent using OHTTP. Read the notice itself for which regions and settings each statement covers, because it is specific about them.
Privacy preserving advertising and measurement
Google's k-anonymity server explainer for the Protected Audience API, published in the WICG repository, says Chrome's Join and Query requests to the k-anonymity server use an OHTTP relay so that Google does not learn end users' IP addresses, with keys fetched directly from the k-anonymity server and a gateway running on Google servers. It describes the payload as a Binary HTTP message encrypted with HPKE. The Bidding and Auction services API document in the Privacy Sandbox repository says requests between the browser and the seller's front end are encrypted using Oblivious HTTP, which it describes as based on bidirectional HPKE. That is a useful example of the RFC 9458 encapsulation format being reused without a separate relay, which gives content encryption but not IP hiding. The Privacy Sandbox programme has changed course more than once, so check its current documentation before assuming any of these APIs is live in the browser you care about.
AI inference privacy
Apple's Security Research post on Private Cloud Compute (10 June 2024) says PCC requests pass through an OHTTP relay "operated by a third party", which hides the device's source IP address before the request reaches PCC infrastructure. The same post says request metadata carries a single use credential based on RSA blind signatures, so a request can be authorised without tying it to a user, and that an attacker would have to compromise both the third party relay and Apple's load balancer to steer traffic by source IP. OHTTP is one layer in a larger design there, not the whole privacy story.
Private relay services
Apple's iCloud Private Relay overview describes MASQUE proxying for browsing and ODoH for DNS, not OHTTP. Two hop relays and OHTTP share a philosophy, two operators who each see half the picture, but they are different protocols for different traffic.
Other natural fits
The OHAI charter names DNS queries, telemetry submission and certificate revocation checking. RFC 9458 adds anonymous surveys, search queries and requests for location specific content such as map tiles. Anything where each request is small, independent and mildly sensitive on its own, but revealing in aggregate, is a candidate.
The limits of OHTTP
Every one of these limits comes from RFC 9458 itself, which is unusually candid.
No state between requests
RFC 9458 says OHTTP removes linkage only at the transport layer, so it helps only applications that carry no state between requests. Cookies, logins, bearer tokens and anything learned from one response and echoed in the next all relink you. The RFC even notes that a client acting on response content can rebuild a stateful system out of individually stateless exchanges. If your use case needs a session, OHTTP is the wrong tool, or you need to design per request anonymous credentials, as Apple describes for PCC.
Replay
RFC 9458 says the relay can copy and replay encapsulated requests, and its threat model allows for that. The application must either reject replays or make sure they do no harm. The RFC's suggestions: treat the enc value as a nonce and remember recent ones, ask clients to include a Date header inside the encrypted request, and reject anything outside a time window or seen before. It also forbids clients and relays from automatically retrying a failed request without a clear signal that it was not processed. A Date header has its own cost: it exposes the client's clock, which RFC 9458 says can be used to fingerprint clients.
Timing and size correlation
HTTPS between each hop hides content but not timing. RFC 9458 says traffic analysis could match the messages a relay forwards on each side, and that a relay sees message boundaries. Mitigations are padding inside the Binary HTTP message, relay side delays (only with client awareness, because delay costs the user), and volume: a relay forwarding many clients' requests to the same gateway gives an observer more candidates to match. A small deployment with a handful of users gives far weaker privacy than the same code with millions.
Trust in the relay operator
The relay knows your IP address and when you used the service. It is trusted not to log that in a way that could be joined with the gateway's records, not to forward identifying headers, and not to act on gateway feedback in ways that single you out. OHTTP cannot enforce any of that. Choose relays run by an organisation independent of the gateway, read their logging policy, and prefer arrangements where the relay's role is documented publicly.
No forward secrecy and no post compromise security for the key lifetime
As noted, a stolen gateway key opens every past request encrypted to it, if the attacker also has the traffic. RFC 9458 recommends regular key rotation to limit the number of affected messages.
No progressive or streaming responses
The whole response must arrive before the client can remove the AEAD protection, so OHTTP does not support informational 1xx responses or Expect: 100-continue. The chunked OHTTP draft addresses incremental messages but is not yet an RFC.
Incompatible with some client policies and network interception
Because the client never connects to the target, client side policies such as certificate pinning for the target do not apply. RFC 9458 also says OHTTP may not work with network interception setups that rely on installing trust anchors on clients, because middleboxes might not recognise the OHTTP media types and cannot read inside the encapsulation even after breaking TLS.
Explicit server support is required
A gateway must exist for each target. You cannot point OHTTP at an arbitrary website the way you can with a VPN or Tor. The OHAI charter says this need for explicit server support limits OHTTP's broad applicability.
Step by step: run a gateway and relay in a lab and see what the relay logs
This lab uses the Rust ohttp repository, whose README says its sample server plays both gateway and target and that "you will need to provide your own relay". We will use nginx as the relay, because its proxy and logging modules make it easy to see exactly what a relay can record. Everything runs on 127.0.0.1; nothing here sends traffic anywhere else. It is a teaching setup, not a production design.
Step 1: install the prerequisites
You need a Rust toolchain, git, openssl, nginx and xxd.
# Debian or Ubuntu
sudo apt-get install -y git curl build-essential pkg-config openssl nginx xxd
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# macOS with Homebrew
brew install nginx openssl
The README's longer build instructions are for the NSS cryptography backend. The test server's default feature is the pure Rust HPKE backend, so you should not need NSS for this lab. If your build asks for NSS libraries, follow the README's NSS steps.
Step 2: build the server and client
git clone https://github.com/martinthomson/ohttp
cd ohttp
cargo build --bin ohttp-server --bin ohttp-client --bin bhttp-convert
Check what you just built pulled in. These binaries carry an HPKE library, a TLS stack and an HTTP server framework; record the resolved versions before you trust them, even in a lab:
cargo tree -p ohttp-server --depth 1
cargo metadata --format-version 1 > ../ohttp-deps.json
Step 3: look at Binary HTTP before any encryption
The repository includes examples/request.txt, an HTTP/1.1 GET /hello.txt with a few headers. Convert it to Binary HTTP and back:
cargo run --bin bhttp-convert < ./examples/request.txt | xxd | head
cargo run --bin bhttp-convert < ./examples/request.txt | \
cargo run --bin bhttp-convert -- -d
The first byte of the binary form should be 00, the known length request framing indicator from RFC 9292. You will see the method, scheme, authority and path as length prefixed strings, followed by the header fields. This is the plaintext that OHTTP encrypts.
Step 4: create lab certificates and start the gateway
./ohttp-server/ca.sh
cargo run --bin ohttp-server
The script creates a small CA and a localhost certificate where the server expects them. The server listens on 127.0.0.1:9443, only answers POST at /, and prints one line:
Config: 002d00002001...
Copy that hex string. It is the application/ohttp-keys content you decoded in worked example 1, with this run's fresh public key. In production, clients would fetch it over an authenticated channel, for example from /.well-known/ohttp-gateway per RFC 9540.
Step 5: configure nginx as the relay
Create relay/relay.conf next to the repository. It listens on 127.0.0.1:8443, accepts only POST / with the OHTTP request media type, forwards the body to the gateway, drops the client's other header fields, and writes a log that records only what a relay can see.
worker_processes 1;
error_log logs/error.log;
pid logs/nginx.pid;
events {}
http {
log_format relay '$remote_addr [$time_local] "$request" $status '
'ctype="$content_type" in=$request_length out=$bytes_sent '
'rt=$request_time upstream_rt=$upstream_response_time';
server {
listen 127.0.0.1:8443 ssl;
server_name localhost;
ssl_certificate ../ohttp/ohttp-server/server.crt;
ssl_certificate_key ../ohttp/ohttp-server/server.key;
access_log logs/relay.log relay;
location = / {
limit_except POST { deny all; }
if ($content_type != "message/ohttp-req") { return 415; }
proxy_pass https://127.0.0.1:9443/;
proxy_http_version 1.1;
# Forward nothing from the client except the body and its type.
proxy_pass_request_headers off;
proxy_set_header Host localhost;
proxy_set_header Content-Type message/ohttp-req;
# Verify the gateway's certificate against the lab CA.
proxy_ssl_verify on;
proxy_ssl_server_name on;
proxy_ssl_name localhost;
proxy_ssl_trusted_certificate ../ohttp/ohttp-server/ca.crt;
}
}
}
Start it with its own prefix so it does not touch any system nginx. Run this from the directory that contains both ohttp and relay:
cd ..
mkdir -p relay/logs
nginx -p "$PWD/relay" -c "$PWD/relay/relay.conf"
Two settings carry the privacy weight. proxy_pass_request_headers off stops the client's headers reaching the gateway, which matches RFC 9458's advice that a relay should not forward unknown fields. And there is no X-Forwarded-For or Forwarded header anywhere, because the RFC says a relay must not add information that could identify clients. If you adapt an existing reverse proxy configuration, search it for those headers first.
Step 6: send an encapsulated request through the relay
cd ohttp
cargo run --bin ohttp-client -- --trust ./ohttp-server/ca.crt \
'https://localhost:8443/' -i ./examples/request.txt \
<paste the Config hex here>
The client encrypts the example request to the gateway key, sends it to the relay URL, and prints the decrypted response. The gateway's terminal will log that it handled a request, from 127.0.0.1 via nginx.
Step 7: inspect what the relay logged
tail -n 3 ../relay/logs/relay.log
You should see one line per request with the client's address, the time, "POST / HTTP/1.1", a 200 status, ctype="message/ohttp-req", the request and response sizes and the timings. That is the relay's whole view: who, when, how big, how long. Nothing in the log shows /hello.txt, the Host inside the request, or the Accept-Language header, because the relay never had them in plaintext.
Now compare the sizes with worked example 2. The Binary HTTP request from step 3 plus 55 bytes should match the body size the client sent, give or take the outer HTTP headers counted in $request_length.
Step 8: see the one part of the body the relay can read
The first 7 bytes of every Encapsulated Request are unencrypted. If you capture one (for example by temporarily logging the body in a separate test server, never in a real relay), you will see 00 0020 0001 0001 or 00 0020 0001 0003: key ID 0, X25519, HKDF-SHA256 and the AEAD the client chose. RFC 9458 acknowledges that a relay can observe the key identifier and the KDF and AEAD choice. This is exactly why unique per client keys would be a tracking vector, and why a deployment should keep the number of active configurations small.
Step 9: break it on purpose
Restart the server so it generates a new key, then rerun the client with the old configuration. The server README says a wrong configuration produces a 422 if the keys are bad and a 400 otherwise. Check the relay log: that error came back unencrypted, which is how RFC 9458 says errors before decapsulation behave, and it is a reminder that error responses leak a little to the relay.
Then send a plain GET to the relay with curl -k https://localhost:8443/ and confirm it is refused. A relay that forwards arbitrary methods or content types to its gateway is a general purpose proxy, which is what the one to one relay to gateway mapping in RFC 9458 section 8.2 is meant to prevent.
Step 10: tear down
nginx -p "$PWD/../relay" -c "$PWD/../relay/relay.conf" -s stop
Stop the server with Ctrl+C and delete the lab CA and keys when you are done.
What the lab does not show
The sample server does not implement replay protection, key rotation, Date checks or padding. Those are application responsibilities under RFC 9458, and a production gateway needs all of them. The lab also runs relay and gateway as one person on one machine, which is the opposite of the non-collusion model. Its value is letting you see the relay's view with your own eyes.
If you operate a relay or a gateway
A short checklist distilled from RFC 9458 sections 5 to 8 and RFC 9540.
Relay operators
- Forward only
POSTrequests withmessage/ohttp-reqbodies, and only to the gateways you have explicitly configured. - Strip all client header fields, and never add
Via,ForwardedorX-Forwarded-For. - Keep logs minimal and short lived. Decide in advance what you will retain and publish it.
- Apply rate limiting evenly, and be careful about acting on per request feedback from the gateway.
- Do not retry failed requests automatically unless HTTP/2 or HTTP/3 says the request was not processed.
Gateway operators
- Publish few key configurations, rotate them on a schedule, and keep the old key only as long as clients need to migrate.
- Serve keys over an authenticated channel, and support fetching them through a proxy.
- Reject replays using
encvalues within a time window, and use theDateheader carefully. - Allowlist the targets you forward to, so the gateway cannot be used as an open proxy.
- Pad responses whose size reveals content, and watch for timing differences that do the same.
- Treat the gateway private key as high value. Its compromise exposes every request sent under it.
Common mistakes
- Running relay and gateway under one operator. It works technically and gives no privacy. The RFC rules it out for a reason.
- Leaving forwarding headers on. Reverse proxies often add
X-Forwarded-Forby default. One header undoes the whole design. - Putting identifiers inside the request. A cookie, account ID or device serial inside the encrypted request lets the gateway link everything. OHTTP only hides transport metadata.
- Fetching keys directly from the gateway with no consistency check. That exposes the client's IP address to the gateway and lets a hostile gateway hand out unique keys.
- Reusing the HPKE context. Each request needs a fresh context. Reuse links requests and can expose content.
- Ignoring replay. The relay can resend any request. Writes, counters and votes need replay protection or idempotent design.
- Assuming OHTTP is a VPN. It protects only the requests of applications built to use it, and only toward gateways that support it.
- Treating a small deployment as anonymous. With few users, timing and size make matching easy. Anonymity grows with volume.
- Forgetting the supply chain of the parts. Gateways and relays are built on HPKE, TLS and HTTP libraries. An unpatched parser in a relay or gateway is a way around the cryptography entirely.
FAQ
What is Oblivious HTTP in simple terms?
It is a way to send a web request through a middleman so that the middleman cannot read it and the destination cannot tell who sent it. The request is encrypted to the destination's public key, and the middleman, called a relay, only passes the sealed message along.
Is Oblivious HTTP the same as a VPN?
No. A VPN carries all your traffic through one provider, which sees your IP address and your destinations. OHTTP protects individual requests from specific apps to specific services that support it, and splits knowledge between two operators so neither sees both your identity and your request.
Which RFC defines Oblivious HTTP?
RFC 9458, published in January 2024 as a Proposed Standard. It relies on RFC 9180 for Hybrid Public Key Encryption and RFC 9292 for Binary HTTP messages. RFC 9540, from February 2024, adds discovery through DNS SVCB and HTTPS records.
What does an OHTTP relay see?
Your IP address, the time and size of each message, which gateway it forwards to, and the key ID and algorithm IDs at the start of each encapsulated request. It does not see the request's method, path, headers or body, or the response.
Can the relay and gateway collude to identify me?
Yes, if they share logs or are run by the same organisation. OHTTP's privacy depends on them being independent. That is why deployments describe third party relay operators and contracts that forbid sharing data.
Does OHTTP hide my IP address from websites?
Only from services that run an OHTTP gateway and only for requests your software sends through OHTTP. Ordinary browsing of other sites is unaffected.
Who uses Oblivious HTTP today?
By their own documentation: Chrome's real time Safe Browsing checks, Firefox search suggestions and some Firefox telemetry, Privacy Sandbox k-anonymity checks, and Apple's Private Cloud Compute requests.
Is Oblivious HTTP secure against replay attacks?
Not by itself. The RFC allows that a relay may replay requests and makes the application responsible for rejecting them or making them harmless, for example by remembering recent enc values and checking a Date header.
What is the difference between OHTTP and Oblivious DNS over HTTPS?
ODoH (RFC 9230) applies the same relay and encrypted message idea only to DNS queries and was published as Experimental. OHTTP generalises it to any HTTP request and is a Standards Track RFC.
Does OHTTP add a lot of latency?
It adds a second HTTP hop and one public key operation per request. Connections between client and relay and between relay and gateway can be reused, and RFC 9458 notes that placing relays close to servers keeps the added delay small. The byte overhead is 55 bytes per request and 32 bytes per response with the common X25519 and AES-128-GCM suite.
Where Safeguard fits
Safeguard does not run OHTTP relays or gateways, does not analyse network traffic, and does not measure anonymity. Nothing in the protocol advice above depends on it. The fit is indirect and sits with the teams that build or run OHTTP components. A relay is usually a reverse proxy or a small service; a gateway is an HTTP server with an HPKE library, a Binary HTTP parser and a TLS stack; clients embed the same libraries. All of that is open source software pulled in through Cargo, Go modules, npm, Maven or container base images. Safeguard can scan those repositories, container images and SBOMs, match the components against vulnerability databases, show which of your projects carry a given library version, rank findings with reachability analysis, and open fix pull requests when you switch autonomous remediation on. A parsing bug in a gateway's request handler is a way around the cryptography, so keeping that dependency tree current is part of running OHTTP properly.
Fix it without waiting for upstream
OHTTP's privacy guarantees assume the gateway, relay and client code does what the RFC says, and that code is built from open source HPKE, TLS, HTTP and parsing libraries that receive security fixes like any other dependency. The fit with the protocol itself is indirect, but if you ship a relay, a gateway or a client library, patching and inventorying those components is part of keeping the promise you make to users.
Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.
1. Autonomous remediation: pull requests, not tickets
When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation covers eight categories, including dependencies, licenses, security posture and findings. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.
2. Gold Open Source: the safe version in seconds, free
gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds. The safeguard-sh/gold-scan-action GitHub Action can fail a build on a known exploited vulnerability, on any known vulnerability, or never, so a version you have just removed cannot quietly come back in next week's merge.
3. Gold Registry: start from components that are already fixed
registry.safeguard.sh provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs, zero malware and a completed license check, with SLSA L3 or higher provenance, a Sigstore signature and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.
When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. This fork and patch flow is in early access today, so ask which packages it covers before you plan around it.
| Your situation | What to use |
|---|---|
| A fixed version exists | Autonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version |
| You need to know today whether you are affected | Gold Open Source lookup and "Am I affected?" checker, free and without login |
| You want vulnerable versions to stop shipping | The Gold scan GitHub Action in CI, plus Safeguard policies and gates |
| Upstream has no fix yet, or will not ship one | A custom Gold request; fork and patch (early access) |
| You want to start clean on new projects | Pull hardened components and images from Gold Registry |
One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.
See how Safeguard would help
The first demo is a real Safeguard CLI session. It asks the assistant to fix a lodash CVE in a repository; the assistant reads the finding and advisory, asks before each command, edits package.json to a fixed version, and runs npm install and npm test with approval, pushing nothing. The same flow applies when a scan of your gateway or relay repository shows an outdated HTTP, TLS or HPKE library: you ask for the fix, approve each step and review the diff before anything leaves your machine.
Click through it at your own pace, or open it full screen.
The second demo sets up the Safeguard runner on your own machine, approves it with its device code, and then scans from the terminal. That suits a lab like the one above, where you want to scan the cloned ohttp workspace and your relay container locally before anything is deployed.
Click through it at your own pace, or open it full screen.
The third demo connects Claude to the Safeguard MCP server with OAuth and chooses which tools Claude can use. It helps when you want to ask an assistant which of your projects carry a particular HPKE or HTTP library version while you plan a key rotation or a gateway upgrade.
Click through it at your own pace, or open it full screen.
To try it on your own projects, download Safeguard and follow the CLI documentation.
Quick check
An OHTTP relay is run by Company A and the gateway by Company B. Which company can see both your IP address and the path of your request?
Neither, as long as they do not collude. Company A, the relay, sees your IP address and an encrypted body. Company B, the gateway, sees the decrypted request and the relay's IP address. If they share logs or are the same organisation, the separation disappears.
Your app sends its login cookie inside every OHTTP request. What has OHTTP protected?
Only your IP address and connection metadata. The gateway can still link every request to your account through the cookie, because OHTTP removes linkage at the transport layer and leaves the content to the application.
Sources
- RFC 9458: Oblivious HTTP (IETF, January 2024)
- RFC 9180: Hybrid Public Key Encryption (February 2022)
- RFC 9292: Binary Representation of HTTP Messages (August 2022)
- RFC 9540: Discovery of Oblivious Services via Service Binding Records (February 2024)
- RFC 9298: Proxying UDP in HTTP (August 2022)
- RFC 9484: Proxying IP in HTTP (October 2023)
- RFC 9230: Oblivious DNS over HTTPS (June 2022, Experimental)
- IETF datatracker: OHAI working group charter
- IETF datatracker: OHAI documents
- IETF datatracker: OHAI history
- Google Security Blog: real time, privacy preserving Safe Browsing (March 2024)
- WICG: Protected Audience k-anonymity server explainer
- Privacy Sandbox: Bidding and Auction services API
- Mozilla: Firefox Privacy Notice
- Apple Security Research: Private Cloud Compute (10 June 2024)
- Apple: iCloud Private Relay Overview (December 2021, PDF)
- Tor Project: Types of relays on the Tor network
- martinthomson/ohttp: Rust implementation of OHTTP and Binary HTTP (README)
- martinthomson/ohttp: test server and client README
- nginx: ngx_http_proxy_module documentation
- nginx: ngx_http_log_module documentation
Last reviewed: 24 October 2026. Deployment details change as operators update their services; check each operator's documentation linked above for the current state.