
When your browser visits github.com, what's the very first thing it does?
Not an HTTP request. Not a TCP handshake. It's a DNS query. The browser sends a UDP packet asking a DNS server "what's the IP of github.com?" — and only after getting the answer does it start the actual connection.
Here's the problem: that DNS query is in plaintext.
Traditional DNS uses UDP port 53 with zero encryption. Every domain you visit — github.com, youtube.com, coolboiledwater.cn — is visible to the coffee shop Wi-Fi admin, your ISP, and any intermediate network device. In some environments, a man-in-the-middle can even tamper with the DNS response and redirect you to a phishing site.
DoH (DNS over HTTPS) and DoT (DNS over TLS) were designed to fix exactly this. This article explains how they work, how they differ, and what you need to watch out for in day-to-day development.
/etc/hosts may be bypassed)
You're at a coffee shop, connected to the public Wi-Fi. You open your browser and type github.com. Your DNS query travels through the Wi-Fi router, the coffee shop's ISP, all the way to a recursive resolver:
Your laptop
│── UDP:53 ──── "What's the IP of github.com?" ──→
│ ↑ Plaintext! Anyone can see this
↓
Coffee shop Wi-Fi router ──→ ISP ──→ DNS recursive resolver (114.114.114.114)The Wi-Fi admin can fire up Wireshark and see every domain you visit. In airports, hotels, and cafes, this is very real.
More dangerous is DNS hijacking: a man-in-the-middle intercepts your DNS query and returns a fake IP. You typed github.com, but your browser might open a pixel-perfect phishing page.
What DoH and DoT do is simple: wrap the DNS query in encryption. The admin can still see "you're talking to some IP," but they can't see what you're asking about.
Capture a normal DNS query packet:
┌──────────────────────────────────────────┐
│ Ethernet Header │
│ Dest MAC: Router MAC │
│ Src MAC: Laptop MAC │
│ Type: 0x0800 (IPv4) │
├──────────────────────────────────────────┤
│ IP Header │
│ Src: 192.168.1.10 │
│ Dest: 114.114.114.114 │
│ Protocol: UDP (17) │
├──────────────────────────────────────────┤
│ UDP Header │
│ Src Port: 54321 │
│ Dest Port: 53 │
├──────────────────────────────────────────┤
│ DNS Query (plaintext!) │
│ "What's the A record for github.com?" │
└──────────────────────────────────────────┘The DNS query sits naked in the UDP datagram. Anyone who can capture packets can read it.
DoT's approach is straightforward — put a TLS layer around DNS. The DNS query is encapsulated inside a TLS connection, with the port changed from 53 to 853:
Normal DNS:
[UDP:53 | DNS query in plaintext]
DoT:
[TCP:853 | TLS Encrypted Tunnel | DNS Query]
↑ ↑
Dedicated DNS content encrypted,
port invisible to outsidersThe connection setup is exactly like HTTPS:
Your laptop DoT Server (1.1.1.1:853)
① TCP 3-way handshake ──────────────────→│
② TLS ClientHello ──────────────────────→│ "I support TLS 1.3, cipher suites..."
←── TLS ServerHello + Certificate ──────│ "Here's my cert, signed by DigiCert"
③ Your laptop validates the cert → valid → generates session key
④ Key exchange complete, TLS tunnel established
⑤ ←──DNS Query──→ all inside the TLS tunnel, encryptedAfter setup, what the IP layer sees:
┌──────────────────────────────────────────┐
│ IP: Src 192.168.1.10 → Dest 1.1.1.1 │
│ TCP Dest Port: 853 │
│ Data: [TLS Application Data] │ ← all gibberish
└──────────────────────────────────────────┘An observer can only tell "you're talking to 1.1.1.1 on port 853," but not what domain you're querying.
DoH goes a step further — DNS queries are placed in the body of HTTP/2 or HTTP/1.1 requests, over the standard HTTPS port 443:
Normal HTTPS request:
POST /api/user HTTP/1.1
Host: example.com
Content-Type: application/json
{"name": "Alice"}
DoH request:
POST /dns-query HTTP/2
Host: 1.1.1.1
Content-Type: application/dns-message
[Binary DNS query]Put DoH and normal web browsing side by side:
Normal browsing: TCP:443 → 1.1.1.1 | [TLS-encrypted HTTP: GET /]
DoH query: TCP:443 → 1.1.1.1 | [TLS-encrypted HTTP: POST /dns-query]
↑ Indistinguishable from the outsideAn observer cannot tell whether you're browsing a website or making a DNS query. This is DoH's elegance — traffic analysis tools can't tell the difference.
An actual captured DoH packet:
┌──────────────────────────────────────────┐
│ IP: Src 192.168.1.10 → Dest 1.1.1.1 │
│ TCP Dest Port: 443 │ ← identical to normal HTTPS
│ Data: [TLS Application Data] │
│ ┌────────────────────────────────────┐ │
│ │ HTTP/2 HEADERS │ │
│ │ :method: POST │ │
│ │ :path: /dns-query │ │
│ │ content-type: application/dns- │ │
│ │ message │ │
│ ├────────────────────────────────────┤ │
│ │ HTTP/2 DATA (encrypted DNS query) │ │
│ └────────────────────────────────────┘ │
└──────────────────────────────────────────┘| DoT (DNS over TLS) | DoH (DNS over HTTPS) | |
|---|---|---|
| RFC | 7858 | 8484 |
| Port | 853 (dedicated) | 443 (shared with HTTPS) |
| Transport | TCP | HTTP/2 (preferred) or HTTP/1.1 |
| Blockable? | Easy — just block port 853 | Hard — blends with HTTPS traffic |
| Layer | OS-level (Android Private DNS) | Browser-level (built into Chrome/Firefox) |
| Visibility | Admins see "this is DNS traffic" | Admins see "this is HTTPS traffic" |
Why did browsers pick DoH?
Because browsers don't want to hand their DNS requests to the operating system's DNS resolver — that means the system could intercept, log, or redirect them. DoH lets the browser directly make HTTPS requests to a DoH server (like https://1.1.1.1/dns-query), completely bypassing the OS and ISP DNS settings.
Why did Android pick DoT?
Android 9+ has a "Private DNS" feature — configure a DoT server address (like 1dot1dot1dot1.cloudflare-dns.com) in system settings, and all apps' DNS queries automatically go through the encrypted channel. DoT is a better fit for the OS level than DoH — no HTTP stack needed, just TCP + TLS.
If you use /etc/hosts during local development to point my-project.test to 127.0.0.1:
127.0.0.1 my-project.testNormally, when the browser resolves my-project.test, it checks the hosts file first and gets 127.0.0.1.
But if you've enabled Chrome's DoH feature, the browser may bypass the system DNS and the hosts file entirely, sending the query directly to a DoH server (like https://dns.google/dns-query). The DoH server doesn't know about my-project.test, returns NXDOMAIN — and your local development domain fails to resolve.
Chrome's decision logic:
Chrome checks if the system has configured a "canary domain"
│
├─ Yes → automatically disable DoH, fall back to system DNS (including hosts)
│
└─ No → enable DoH, bypass system DNSSo if you use DoH in Chrome and also need local dev domains, make sure Chrome detects your "enterprise environment" or manually disable DoH:
chrome://settings/security → Secure DNS → Turn off
Or in Firefox:
Settings → Network Settings → Enable DNS over HTTPS → Turn off
Browser (recommended, zero config):
chrome://settings/security → Secure DNS → Choose Cloudflare or GoogleOperating system:
1dot1dot1dot1.cloudflare-dns.comRouter (OpenWRT):
# Install DoH client
opkg update
opkg install https-dns-proxy
# Configure upstream as Cloudflare
uci set https-dns-proxy.@https-dns-proxy[0].resolver_url='https://1.1.1.1/dns-query'
uci commit https-dns-proxy
/etc/init.d/https-dns-proxy restartAll devices on your home Wi-Fi automatically use DoH — no per-device configuration needed.
Check if your browser is using DoH
Visit in your browser:
https://1.1.1.1/help
Check the Using DNS over HTTPS (DoH) line:
Connected to 1.1.1.1: Yes
Using DNS over HTTPS (DoH): Yes ← If Yes, DoH is enabled
DNS over TLS (DoT): NoCapture plaintext DNS with tcpdump
Disable DoH in your browser first, then capture:
# Capture DNS traffic
sudo tcpdump -i en0 'udp port 53' -n -X
# Visit any domain you haven't visited before
# Watch the capture output — you'll see the domain in plaintext in the X columnCompare packet capture with DoH enabled
After enabling DoH, visit the same domain and observe:
# DNS traffic (UDP 53) should show NO related queries
sudo tcpdump -i en0 'udp port 53' -n
# Only HTTPS traffic to the DoH server (TCP 443)
sudo tcpdump -i en0 'tcp port 443' -nMake a DoH query directly with curl
# Use curl's built-in DoH support
curl --doh-url https://1.1.1.1/dns-query https://github.comDeploy DoH on your router
If you have an OpenWRT router at home, deploy https-dns-proxy or AdGuard Home. All devices on your network will automatically get encrypted DNS — a one-time privacy upgrade for the entire household.
/etc/hosts and local dev domains to stop working.DoH DoT DNS over HTTPS DNS over TLS DNS encryption DNS privacy Private DNS /dns-query 1.1.1.1
DNS encryption solves the problem of "keeping queries private." But the answer DNS gives you — that IP address — once you have it, how do packets get correctly routed to their destination? If you're not yet familiar with routing and forwarding, that's the core of the network layer.
Continue reading:
Also, DoH relies on HTTPS (and thus on TLS). If you want to understand how the TLS encryption handshake works: