Quick Start

Quick Start

This quickstart will walk you through all four parts of an Oblivious HTTP setup:

  1. Running an HTTP service.
  2. Setting up an OHTTP Gateway for that service.
  3. Provisioning an Oblivious Network OHTTP Relay.
  4. 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" -vvv
Full 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 to
demo-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/gateway

Click “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
EOF

Now 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!