MCPcopy Index your code
hub / github.com/DNSCrypt/doh-server

github.com/DNSCrypt/doh-server @0.9.16

Chat with this repo
repository ↗ · DeepWiki ↗ · release 0.9.16 ↗ · + Follow
87 symbols 200 edges 13 files 4 documented · 5%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

DoH server (and ODoH - Oblivious DoH server)

License: MIT Rust Crates.io

A fast and secure DoH (DNS-over-HTTPS) and ODoH (Oblivious DoH) server.

doh-proxy is written in Rust, and has been battle-tested in production since February 2018. It doesn't do DNS resolution on its own, but can sit in front of any DNS resolver in order to augment it with DoH support.

Table of Contents

Features

  • DNS-over-HTTPS (DoH) - Encrypts DNS queries using HTTPS
  • JSON API Support - Compatible with Google DNS-over-HTTPS JSON API format
  • Oblivious DoH (ODoH) - Provides additional privacy by hiding client IP addresses
  • EDNS Client Subnet - Forward client IP information to upstream resolvers for geo-optimized responses
  • High Performance - Built with Rust and Tokio for excellent performance
  • Flexible Deployment - Can run standalone with built-in TLS or behind a reverse proxy
  • Production Ready - Battle-tested in production environments since 2018
  • Multiple IP Support - Supports multiple external IP addresses for load balancing
  • Automatic Certificate Reloading - No downtime when updating TLS certificates
  • Configurable Caching - TTL management with configurable min/max values

Installation

Option 1: precompiled binaries for Linux

Precompiled tarballs and Debian packages for Linux/x86_64 can be downloaded here.

Option 2: from source code

This requires the rust compiler to be installed.

  • With built-in support for HTTPS (default):
cargo install doh-proxy
  • Without built-in support for HTTPS:
cargo install doh-proxy --no-default-features

Quick Start

Basic Usage

# Simple setup with a local DNS resolver
doh-proxy -H 'doh.example.com' -u 127.0.0.1:53

# With a specific public IP address
doh-proxy -H 'doh.example.com' -u 127.0.0.1:53 -g 203.0.113.1

# With built-in TLS support
doh-proxy -H 'doh.example.com' -u 127.0.0.1:53 -i /path/to/cert.pem -I /path/to/key.pem

Complete Usage Reference

USAGE:
    doh-proxy [FLAGS] [OPTIONS]

FLAGS:
    -O, --allow-odoh-post      Allow POST queries over ODoH even if they have been disabed for DoH
    -K, --disable-keepalive    Disable keepalive
    -P, --disable-post         Disable POST queries
    -h, --help                 Prints help information
    -V, --version              Prints version information

OPTIONS:
    -E, --err-ttl <err_ttl>                          TTL for errors, in seconds [default: 2]
    -H, --hostname <hostname>                        Host name (not IP address) DoH clients will use to connect
    -l, --listen-address <listen_address>            Address to listen to [default: 127.0.0.1:3000]
    -b, --local-bind-address <local_bind_address>    Address to connect from
    -c, --max-clients <max_clients>                  Maximum number of simultaneous clients [default: 512]
    -C, --max-concurrent <max_concurrent>            Maximum number of concurrent requests per client [default: 16]
    -X, --max-ttl <max_ttl>                          Maximum TTL, in seconds [default: 604800]
    -T, --min-ttl <min_ttl>                          Minimum TTL, in seconds [default: 10]
    -p, --path <path>                                URI path [default: /dns-query]
    -g, --public-address <public_address>            External IP address(es) DoH clients will connect to (can be specified multiple times)
    -j, --public-port <public_port>                  External port DoH clients will connect to, if not 443
    -u, --server-address <server_address>            Address to connect to [default: 9.9.9.9:53]
    -t, --timeout <timeout>                          Timeout, in seconds [default: 10]
    -I, --tls-cert-key-path <tls_cert_key_path>
            Path to the PEM-encoded secret keys (only required for built-in TLS)

    -i, --tls-cert-path <tls_cert_path>
            Path to the PEM/PKCS#8-encoded certificates (only required for built-in TLS)

    --enable-ecs                              Enable EDNS Client Subnet
    --ecs-prefix-v4 <ecs_prefix_v4>         IPv4 prefix length for EDNS Client Subnet [default: 24]
    --ecs-prefix-v6 <ecs_prefix_v6>         IPv6 prefix length for EDNS Client Subnet [default: 56]

Example Configurations

Basic setup with custom DNS resolver:

doh-proxy -H 'doh.example.com' -u 8.8.8.8:53 -g 203.0.113.1

Multiple IP addresses for load balancing:

doh-proxy -H 'doh.example.com' -u 127.0.0.1:53 -g 203.0.113.1 -g 203.0.113.2 -g 2001:db8::1

This generates separate DNS stamps for each IP address, allowing clients to connect via any of them.

Production setup with TLS and custom limits:

doh-proxy -H 'doh.example.com' \
          -u 127.0.0.1:53 \
          -l 0.0.0.0:443 \
          -i /etc/letsencrypt/live/doh.example.com/fullchain.pem \
          -I /etc/letsencrypt/live/doh.example.com/privkey.pem \
          -c 1000 \
          -C 32

Behind a reverse proxy (nginx/Caddy):

doh-proxy -H 'doh.example.com' -u 127.0.0.1:53 -l 127.0.0.1:3000

Deployment Architectures

Behind a Reverse Proxy (Recommended)

The recommended deployment is behind a TLS termination proxy such as nginx, Caddy, HAProxy, or a CDN. This allows: - Sharing port 443 with existing web services - Leveraging existing TLS certificate management - Using HTTP/2 and HTTP/3 features from the proxy - Better DDoS protection and rate limiting

Example with nginx:

server {
    listen 443 ssl http2;
    server_name doh.example.com;

    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;

    location /dns-query {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Example with Caddy:

doh.example.com {
    reverse_proxy /dns-query localhost:3000
}

Standalone with Built-in TLS

For simpler deployments or when running on separate infrastructure:

doh-proxy -H 'doh.example.com' \
          -u 127.0.0.1:53 \
          -l 0.0.0.0:443 \
          -i /path/to/fullchain.pem \
          -I /path/to/privkey.pem

Certificate Requirements: - Certificates and keys must be in PEM/PKCS#8 format - Can be stored in the same file or separately - Automatically reloaded when changed (no restart needed)

If using ECDSA certificates that start with -----BEGIN EC PRIVATE KEY-----, convert to PKCS#8:

openssl pkcs8 -topk8 -nocrypt -in example.key -out example.pkcs8.pem

Using Let's Encrypt with acme.sh:

# Install acme.sh
curl https://get.acme.sh | sh

# Get certificates
acme.sh --issue -d doh.example.com --webroot /var/www/html

# Run doh-proxy with Let's Encrypt certificates
doh-proxy -H 'doh.example.com' \
          -u 127.0.0.1:53 \
          -i ~/.acme.sh/doh.example.com/fullchain.cer \
          -I ~/.acme.sh/doh.example.com/doh.example.com.key

Note: Once HTTPS is enabled, HTTP connections will not be accepted. A sample self-signed certificate localhost.pem is available for testing.

Integration Examples

With Encrypted DNS Server

Encrypted DNS Server can handle both DNSCrypt and DoH on the same port:

# In encrypted-dns-server.toml
[tls]
upstream_addr = "127.0.0.1:3000"

This provides: - Support for both DNSCrypt and DoH protocols - Built-in DNS caching - Server-side filtering - Connection reuse and DDoS protection

With nginx

location /dns-query {
    proxy_pass http://127.0.0.1:3000;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_http_version 1.1;
    proxy_set_header Connection "";
}

With HAProxy

backend doh_backend
    mode http
    server doh1 127.0.0.1:3000 check

JSON API

The server supports Google's DNS-over-HTTPS JSON API format, making it compatible with applications that use this format.

Usage

Send GET requests to /dns-query with Accept: application/dns-json header:

# Query A records
curl -H "Accept: application/dns-json" \
  "http://localhost:3000/dns-query?name=example.com&type=1"

# Query with multiple parameters
curl -H "Accept: application/dns-json" \
  "http://localhost:3000/dns-query?name=example.com&type=28&cd=1&do=1"

Supported Parameters

  • name - Domain name to query (required)
  • type - DNS record type (default: 1 for A records)
  • cd - Disable DNSSEC validation (0 or 1)
  • do - Request DNSSEC data (0 or 1)
  • edns_client_subnet - Client subnet for EDNS

Response Format

{
  "Status": 0,
  "TC": false,
  "RD": true,
  "RA": true,
  "AD": false,
  "CD": false,
  "Question": [{
    "name": "example.com",
    "type": 1
  }],
  "Answer": [{
    "name": "example.com",
    "type": 1,
    "TTL": 300,
    "data": "93.184.216.34"
  }]
}

EDNS Client Subnet (ECS)

Overview

EDNS Client Subnet (ECS) is a DNS extension that allows the DoH proxy to forward client IP information to upstream DNS resolvers. This enables geo-optimized DNS responses by allowing authoritative nameservers to return results based on the client's actual location rather than the proxy's location.

Configuration Options

Enable ECS support with the following command-line options:

  • --enable-ecs - Enable EDNS Client Subnet functionality
  • --ecs-prefix-v4 <length> - IPv4 prefix length (default: 24)
  • --ecs-prefix-v6 <length> - IPv6 prefix length (default: 56)

How It Works

  1. Client IP Extraction: The proxy extracts the client's IP address from:
  2. X-Forwarded-For header (takes the first IP if multiple are present)
  3. X-Real-IP header
  4. Direct connection IP (if no headers are present)

  5. IP Truncation: For privacy, client IPs are truncated to the configured prefix length:

  6. IPv4: Default /24 (e.g., 192.168.1.100 → 192.168.1.0/24)
  7. IPv6: Default /56 (e.g., 2001:db8::1 → 2001:db8::/56)

  8. DNS Query Enhancement: The truncated client subnet is added to outgoing DNS queries using the EDNS0 Client Subnet option (RFC 7871).

  9. Geo-Optimized Responses: Upstream resolvers and authoritative nameservers can use this information to return geographically appropriate results.

Examples

Basic ECS setup:

doh-proxy -H 'doh.example.com' -u 8.8.8.8:53 --enable-ecs

Custom prefix lengths for more privacy:

doh-proxy -H 'doh.example.com' -u 8.8.8.8:53 \
          --enable-ecs \
          --ecs-prefix-v4 16 \
          --ecs-prefix-v6 48

Behind nginx with ECS enabled:

server {
    listen 443 ssl http2;
    server_name doh.example.com;

    location /dns-query {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

Testing ECS functionality:

# With X-Forwarded-For header
curl -H "X-Forwarded-For: 1.2.3.4" \
     -H "Accept: application/dns-json" \
     "https://doh.example.com/dns-query?name=example.com&type=1"

# The DNS query sent to upstream will include ECS: 1.2.0.0/24

Privacy Considerations

  • IP Truncation: Client IPs are never sent in full. The default settings provide a good balance between geolocation accuracy and privacy.
  • Opt-in Only: ECS is disabled by default and must be explicitly enabled.
  • Header Trust: Only trust X-Forwarded-For and X-Real-IP headers from known reverse proxies.

Core symbols most depended-on inside this repo

http_error
called by 21
src/libdoh/src/lib.rs
exit_with_error
called by 13
src/config.rs
skip_name
called by 7
src/libdoh/src/dns.rs
arcount
called by 6
src/libdoh/src/dns.rs
as_str
called by 5
src/libdoh/src/lib.rs
parse_name
called by 5
src/libdoh/src/dns_json.rs
ancount
called by 5
src/libdoh/src/dns.rs
build_ecs_option
called by 4
src/libdoh/src/edns_ecs.rs

Shape

Method 38
Function 35
Class 12
Enum 2

Languages

Rust100%

Modules by API surface

src/libdoh/src/lib.rs31 symbols
src/libdoh/src/dns.rs16 symbols
src/libdoh/src/odoh.rs9 symbols
src/libdoh/src/dns_json.rs8 symbols
src/libdoh/src/edns_ecs.rs7 symbols
src/libdoh/src/globals.rs5 symbols
src/libdoh/src/tls.rs3 symbols
src/libdoh/src/errors.rs3 symbols
src/utils.rs2 symbols
src/config.rs2 symbols
src/main.rs1 symbols

For agents

$ claude mcp add doh-server \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact