Quick Start
This quickstart will walk you through all four parts of an Oblivious HTTP setup:
- Running an HTTP service.
- Setting up an OHTTP Gateway for that service.
- Provisioning an Oblivious Network OHTTP Relay.
- Configuring an OHTTP Client to make requests.
Let’s go!
1. HTTP service
Instead of running our own HTTP service, we’re going to use httpbin.org to demonstrate. We’ll make a request to the /anything endpoint, which responds with a body containing information about the request.
Try it out now, so you can see what to expect:
curl https://httpbin.org/anything -H "Content-type: text/plain" --data "request body here" -vvvFull curl output
* Host httpbin.org:443 was resolved.
* IPv6: (none)
* IPv4: 18.209.97.55, 34.198.95.5, 34.202.168.137, 3.229.139.1, 54.198.84.155, 3.213.199.175, 35.169.248.232, 52.45.33.43
* Trying 18.209.97.55:443...
* Connected to httpbin.org (18.209.97.55) port 443
* ALPN: curl offers h2,http/1.1
* (304) (OUT), TLS handshake, Client hello (1):
* CAfile: /etc/ssl/cert.pem
* CApath: none
* (304) (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256 / [blank] / UNDEF
* ALPN: server accepted h2
* Server certificate:
* subject: CN=httpbin.org
* start date: Aug 20 00:00:00 2024 GMT
* expire date: Sep 17 23:59:59 2025 GMT
* subjectAltName: host "httpbin.org" matched cert's "httpbin.org"
* issuer: C=US; O=Amazon; CN=Amazon RSA 2048 M02
* SSL certificate verify ok.
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://httpbin.org/anything
* [HTTP/2] [1] [:method: POST]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: httpbin.org]
* [HTTP/2] [1] [:path: /anything]
* [HTTP/2] [1] [user-agent: curl/8.7.1]
* [HTTP/2] [1] [accept: */*]
* [HTTP/2] [1] [content-type: text/plain]
* [HTTP/2] [1] [content-length: 17]
> POST /anything HTTP/2
> Host: httpbin.org
> User-Agent: curl/8.7.1
> Accept: */*
> Content-type: text/plain
> Content-Length: 17
>
* upload completely sent off: 17 bytes
< HTTP/2 200
< date: Wed, 18 Jun 2025 02:12:54 GMT
< content-type: application/json
< content-length: 425
< server: gunicorn/19.9.0
< access-control-allow-origin: *
< access-control-allow-credentials: true
<
{
"args": {},
"data": "request body here",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Content-Length": "17",
"Content-Type": "text/plain",
"Host": "httpbin.org",
"User-Agent": "curl/8.7.1",
"X-Amzn-Trace-Id": "Root=1-685220a6-1918241b18fd2bf22719d2a1"
},
"json": null,
"method": "POST",
"origin": "157.131.221.67",
"url": "https://httpbin.org/anything"
}
* Connection #0 to host httpbin.org left intact
2. OHTTP Gateway
Cloudflare has written and open-sourced an OHTTP gateway that we can use to demonstrate the full OHTTP request and response cycle. The source code for their gateway is on GitHub at cloudflare/privacy-gateway-server-go.
For this quickstart, we’ll use a copy of this gateway that’s already deployed todemo-gateway.oblivious.network.
3. OHTTP Relay
First, make sure you’ve signed up and have an account. Next, visit the My Relays page, click “Create Relay”, and put in this Gateway URL:
https://demo-gateway.oblivious.network/gatewayClick “Create Relay”, and then copy the Relay URL on the next page. We’ll use that URL to make requests from the client.
4. OHTTP Client requests
The only open-source tool for making OHTTP requests at present is a Rust library, so you’ll need Rust if you don’t already have it. On a Mac, you can brew install rust, or you can use Rustup, the official Rust install tool.
Once you have Rust available, you’ll want to check out martinthomson/ohttp/ and run cargo build to make sure everything is set up.
Next, create the HTTP request that we want the gateway to send to httpbin.org for us:
cat <<EOF | sed 's/$/\r/' > examples/service-request.txt
GET https://httpbin.org/anything HTTP/1.1
Host: httpbin.org
request body here
EOFNow that we have a request ready, we can use the ohttp-client binary to encrypt the request, send that to the Relay, have the Relay pass that encrypted request to the Gateway, which will decrypt it, make the request, and then accept the reponse from the remote server and encrypt it, and return the encrypted response to the Relay, which will return the encrypted response to us, and we will decrypt it and print it out.
cargo run --bin ohttp-client -- <YOUR RELAY URL GOES HERE> -i examples/service-request.txt "$(curl https://demo-gateway.oblivious.network/ohttp-keys -s | xxd -p -c 0)"Full cargo output
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.08s
Running `target/debug/ohttp-client 'https://relay.oblivious.network/demo' -i examples/service-request.txt 04c90000300987307db116805a3f7b5195ed7507a870d2556d309e5803920fc6fe24c5493e1edc9cf99563928685c909ba1215c47b622fb916195bf767e7e52119a75d02290c0ce641af5c6ec1900ddba077a6222a408a76e8d0204c759244592e624b3a24f3cdea9b40630976450108203a5076110dbb5a3c960cc1b2e602c2b9964b3631f5dcc6d0eb48627b990a390848e5084e9bac16586de6d12eef4c56d3a233c9541a702530a8e75ef1740a378786c7f76b76a3bc94d7238ac51e2d37cbd14113beb616a76c2bf074839a52abd4d4811a655c2b12bbb519c1180a74ade4afd94823e5971fe928794f4b5f18e8b9993c61c5176ff9864ee6697c662879806b374f393853f50faad3788824989a8b19adf2caae100b797b06cf9b6337712088588db1c405e77ab2edc94f7915c31dca55b53b85feda3f08a41b1d3aa730804bb178cb16fc5d78379c8aeaa0bdb1c160b23a7af9b79c4b6a03cbc98bdac814ba293a5657a077501e0866aa733f2b091382a486eb882520fc7b9a7c20358002d1141d1797c85fb280738c5a015a0e6633326a3481956a69860582637337a0f22a7cca202d6086b9413d6e6477830612715a6202970d19a62867eb5392ea59964b7984267a464a7d73e7a4963b1e40f92429239823bc2cce054df3127b6daab400f66fe596c056722c63126645e4857ab3099f5abd3576c8f8355b645487dbe5c3ef3429090ccfaa193e9d32bb6cc9cbba3482d7960e2b31464d5b5162ec6bf184a1bdd2ab37f2bc20cb38eddc86dd595b70616e8dd9048f0138ce31ba61a0bc5ab9037e792eb22c5f3dbc48ed76885b230d28c16bc0895a5b8067e3141867c97a4d57b3838c539856b40fd9cc89e63e33149c5a6233a10ba3cdf81b9014832a182b74360216c20b81129b9ff1593f770ee4e3abae635213874eab638a52666867f26a0278b27cc271d07011c1d6cb8e9b4d84e42089e4a64925ab8f29cc6faaced4ab1e8e0a8aecd91849eb498ea6c2ab4668fb17a1dd82467c54b9ae922cba627464a90d772c12fac005e61a365eb4b433055a4fd26966467a5c35036e70c189e65f88605c3ed975e062a0a5db591b562168319d6ae34435d42464d99879c03f31676631189092659c73fb377c23cf2cb3c7b62884e9e50ef8da03f3cc35b70739adc2c13d2517aa79804cf2a2c8e1c218db11cf1214522bc02c3276cbd806d192bb1a719d4c132e14b41ffdf33a814ba68a47835dfb5365e088cc00a2259a31b01415a11c7cb1284e2b2c174ce8c1cd914a34a83149f78ce1ba80bbd6b3eca971317aaa365970901c7f6d2927413357e7a56ea702042b905366e8a2a6f53a92c06150c26b44741c070a01bc90c1bd8246fe0c49c638ccef1575f92accd349a017a4688c354f2ef1c879a883ad8844e249b64164a3e0863ffeeb36c7e31adedc74ec838dda5c7cdf8a38936314374247048bcc44c19afae89f364a0506619fde4000b7ab96149284cd956cd28b57fbd80816b9573e769afb051ad5d5c0080286993c5aea85811fc4a8601265bc1532b6c89afff3922939b73d01a10b05882c6a31090324772c4b83927e35331a13168fd442a9aea06cab5aa95e145fa4d8b2c353b61dfcbc40abb73b178c3bb5aed923325fc8afd67475acd3cc2f0b80c1d1b75cd49860538df33fd2e759b1ade3e4cae8e4f84f9f52c0bb30a07d38c6a12dc7c6c71e0004000100010029800020c81d90101968e527b350ca17003e2389de8711843b458b9afeea990dc2188d64000400010001`
Request: 8000200001000137f84e61bf09d2f5d2486204dd9db3f03c75930d5c4584dc391653a775fe1c5a11ce27ba24ad97611645f24dad3a4ee5879afa54ded57e2db6657a08a17554562ef996b149198497bcd48da71a36c6c1623c34050c7544b5ee8cdea751a87e89df685012b6d30843924817b6e51f4d7b0b56b4920aa62c0c
Response: 97997023a7503e0c25f1a98c7b1a86919c8963e693f932d964b26d93c35463dfaf74f696f788903f75c294b62d656f41f3e159c7197548799b03106a1c428e2dad828a2d0bb0e7b46643152aa2f600cd4f4877f19f1b272d576a856c21897f43d86919fb28130fee92e0a96b8061229031df720710476609676f805ba3e9921e75cde54e028a58e93bd719e7c5bc83cd62356bbbf44fec5efca9e53a25830e39c49bc461d4563174ae18651759da5e8b5a98a587c8f3b7c5f1856497dbf31c1f216c2b56690cebdb9187d4e1f813d9ba90c5f06c84fee612760cca37e7e1315249a37849b7673f1c0ce714a4e39217891830623083474660e8128d313c35598becf420c91c3e2b18417afbe720fdd7ff90743c421464ca26499d3420757ef7b182ecfe4879c2dffefd66ae55ccdc8a71e6df36384504586d2e9aa704173f770c17cb7beb9a7eba50b0fa988ed14ad60c42fd40b930292064fafd98616fe8cfa0a10f1d6ec0ea52ec0de82568e10ee830a8f9a9e0e544a59737e30791f438b6d473cea948302378dd8b2ecb4a1ac78d565ebc75994960c62aadac919497a2cbcdcfaaf28a559eda771b4df872cda133c8586593fb5f3b37c86882fbe71e40c2045be7db624c7cfc898a94c5feecbffd256bc25b88454b21663bc4dfbcd2ae71f1ed0e814d0217893c064d041bfb2abb96afa8fec67a5a9a324a8e0c05ed730483f2806c7dc7a0b6d9ce0b7a3006b356a32f75967aa5150c79e6b69df7f8ba5032981944ebe6c592f8388d08e9df323ef8ff883f1a08ed17cf1b043582d72fcfceece2d91a947974ae92bb82828d717858e70d417fa91b54776c60208bfaf39e3193a62c211e7a7e3067b7eba7b848e7096199
HTTP/1.1 200 Reason
Content-Length: 411
content-length: 411
server: gunicorn/19.9.0
access-control-allow-origin: *
access-control-allow-credentials: true
date: Wed, 18 Jun 2025 02:19:23 GMT
content-type: application/json
{
"args": {},
"data": "request body here\r\n",
"files": {},
"form": {},
"headers": {
"Accept-Encoding": "gzip",
"Content-Length": "19",
"Host": "httpbin.org",
"User-Agent": "Go-http-client/2.0",
"X-Amzn-Trace-Id": "Root=1-6852222b-5bc96af0631db3e20e778fea"
},
"json": null,
"method": "GET",
"origin": "216.246.104.87",
"url": "https://httpbin.org/anything"
}
As you can see in the sample output, the request was made to httpbin.org, and the response was returned to you and printed out. The httpbin.org servers never saw your client information or IP address, and the relay server never saw the contents of your request or the response.
That’s a full OHTTP request!