Ozy-666
DNS Infrastructure · Independent
Engineering notes - rev. 2026.08

I rebuild DNS infrastructure and measure what changed.

I'm Ozy-666. I build and run DNSDOH.ART - a public, no-logs encrypted DNS resolver on a single carefully-tuned server, where every component in the request path has been rebuilt from source.

It started in 2022 on a free Oracle VPS. Port 53 was open to the internet, and for about a week my server was quietly being used as an amplifier in DNS reflection attacks. I looked at that traffic, decided it wasn't an attack, and did nothing. Oracle deleted the instance - correctly. Almost everything I've built since comes out of that week.

What I run
§1

One server, in the open

DNSDOH.ART serves DoH, DoH3, DoT, DoQ and plain DNS, blocks ads and trackers, keeps no logs, and costs nothing. There's no company behind it and there never will be.

CostNoneno account · no app needed
Logs kept0nothing recorded, ever
TransportsDoH · DoT · DoQ · H3phone · laptop · router
SourceAll of itevery fork is public

It is one server, not an anycast network. Cloudflare and Quad9 are genuinely excellent and I'm not pretending to match their reach - that's not something one person replicates. What a single node can be is stable, fast, and extremely hard to abuse. That's what I optimise for: layers of defence in front of it, from XDP at the network card through nftables to filtering at the application layer.

Use it →
The work
§2

What I changed, and how I checked

Every number below was measured on the production host. Where a claim has a verification method, it's stated - a number without one is just a number.

2.1AdGuardHome-edge
−13k LOC · −10 MB

The engine. I stripped ~13k lines of anything that leaks data or wastes time - DHCP, cloud lookups, client-subnet - rebuilt the request pipeline to allocate nothing on the hot path, and made the serve path lock-free so it holds up under flood. A built-in front cache means most repeat lookups never leave the box.

0-alloc pipeline · lock-free serve path · front-cache +60% · −13k LOC
github.com/Ozy-666/AdGuardHome-edge-spec →
2.2dnsproxy
+101% throughput

How requests travel in and out. Rebuilt the UDP and TCP paths to allocate nothing, made upstream RTT tracking lock-free, pooled connections instead of reopening one per query, and sharded the listener across every core with SO_REUSEPORT. It also caps QUIC streams and DoH request sizes so one client can't exhaust it.

0 allocs/op · lock-free RTT map · SO_REUSEPORT · flood caps
github.com/Ozy-666/dnsproxy →
2.3dnscrypt-proxy
security-audited

Encrypts DNS on its way out to the wider internet. Rebuilt the busy paths to allocate nothing, made server selection lock-free, and stripped everything that phones home - the web UI, auto-updates, remote list downloads and ODoH, about 800 lines of protocol removed purely to cut attack surface. It now rejects forged upstream replies.

0 allocs/op · lock-free WP2 · −455 KB · ODoH stripped
github.com/Ozy-666/dnscrypt-proxy →
2.4urlfilter
248× faster

The matcher that decides what's an ad or a tracker. I changed how it indexes regular-expression rules so they're found by literal extraction instead of scanned one by one - closing a path an attacker could use to slow the resolver with floods of fake subdomains.

Verified: identical results to the original engine across 39,983 real domains plus 130,000 fuzz runs, 0 divergences. The flood benchmark went from 111 µs to 449 ns at 1,000 regex rules, and stays flat as rules are added.

O(1) regex miss path · 0 query-path allocs · 0 divergences
github.com/Ozy-666/urlfilter →
2.5Unbound + BoringSSL
+14% · −27%

The part that proves an answer is genuine. Checking DNSSEC signatures was eating close to half the server's CPU, so I rebuilt Unbound against BoringSSL - keeping a one-command rollback ready, and no Unbound source patched: the customisation is entirely build flags, config and systemd units.

+14% end-to-end DNSSEC throughput, −27% worst-case latency. The mechanism is roughly 30% faster signature verification - ECDSA P-256 +28.8%, RSA-2048 +34.2% against OpenSSL 3.0.16, over 8 paired rounds with the same benchmark compiled against both libraries. DNSSEC crypto was about 44% of Unbound's CPU, so a ~30% gain on 44% of the work predicts ~13% overall; the end-to-end flood measured +14%. The benchmark ships with the repo, so a library bump can be re-checked rather than assumed.

pinned BoringSSL · full DNSSEC validation · Zen 2 tuned · benchmark included
github.com/Ozy-666/unbound-edge →
2.6nginx + BoringSSL
post-quantum · ECH

The front door, answering every encrypted request before anything else sees it. Building on BoringSSL buys three things a stock build cannot do. The name of the site you asked for is encrypted too, with Encrypted Client Hello, instead of travelling in the clear. Traffic recorded today can't be unlocked by a future quantum computer, because the key exchange is already post-quantum. And it speaks HTTP/3, so pages start a round trip sooner.

nginx implements ECH only against the OpenSSL 3.x API, so the repo carries one patch adding the BoringSSL path. The build verifies the PGP signature on every download before it compiles anything, and refuses to proceed on an unpinned signing key.

A DoH3 handshake here started failing about one attempt in twenty, and it was not my build. When the server's ServerHello spans two Initial packets and one of them is lost, nginx never retransmits it: the handshake deadlocks until the client gives up, and nothing is logged. I traced it to the congestion controller rather than the retransmission logic and reported it with a reproduction. nginx opened a fix crediting the report - still open, not merged. Rebuilding their candidate here cleared the case I reported, and showed the deadlock returns once the certificate chain is large enough to fill QUIC's initial congestion window, which post-quantum certificates are big enough to do.

X25519MLKEM768 · Encrypted Client Hello · HTTP/3 + Brotli · PGP-verified builds
github.com/Ozy-666/nginx-edge → nginx/nginx#1616 →
2.7Everything else
the unglamorous half

Redis holds the leak-test state, in memory only - no RDB, no AOF, nothing on disk. The update scripts for nginx, Unbound and Redis all verify checksums or signatures before building, test the new binary against the live config before installing it, and roll back automatically if the service doesn't answer afterwards. A watcher tracks every upstream and tells me when one moves, and only when there's something to actually do.

verified builds · pre-install gates · automatic rollback · upstream watch
Method
§3

How I work

I don't trust myself to notice. The failure that cost me my first server was not a misconfiguration - it was a week of looking at abusive traffic and deciding it was fine. So the things I build now tell me when they break: failures alert, routine events are forced silent so alerts still mean something, and a login prints anything that needs attention. A permanent alert is the same as no alert, so I tune them until they're quiet.

3.1Measured, not guessed

Numbers come from benchmarks on the production host. When I moved BoringSSL forward, three rounds looked like a 2.6% regression - at eight rounds it reversed and turned out to be scheduler noise. Three samples can't separate a real change from a busy machine, so the tool now defaults higher and reports a win count, not one number.

3.2Dead ends written down

When an upstream release doesn't help, that gets recorded with the reason. urlfilter 0.23.4 was reviewed and deliberately not ported: its only functional change is a feature this fork doesn't carry, and taking it would disturb the patch that makes regex matching fast.

3.3Privacy by removal

The safest code is the code that isn't there. Telemetry, auto-update and remote fetches are stripped rather than disabled, because a switch can be flipped back and deleted code cannot.

3.4Always reversible

Every update script backs up what it replaces and restores it automatically if the new build doesn't answer. A change I can't undo in one step isn't finished.

How I got here
§4

From open resolver to hardened one

The short version: I was shut down for running an open resolver, and now I run a public resolver built to survive exactly that.

2022The first server

DNSDOH.ART started on a free Oracle VPS running AdGuard Home, with port 53 open to the internet. It worked for about a year.

2023Deleted

For roughly a week the server was being used as an amplifier in DNS reflection attacks. I saw the traffic, judged it harmless, and did nothing. Oracle removed the instance for running an open DNS resolver. The diagnosis was the failure, not the config.

→2025Trial and error

Read, implement, break something, fix it, guess at a config from someone else's repository. It worked more often than it should have, and I couldn't have told you why.

nowMeasured

The method changed rather than the ambition. Benchmarks gate deploys, updates verify signatures and roll themselves back, and defence is designed instead of bolted on after something goes wrong.

Questions
§5

Things people ask

Why BoringSSL instead of OpenSSL for DNS?

For DNSSEC validation it is measurably faster on this hardware - about 30% on signature verification, which works out to +14% end-to-end throughput because crypto is roughly 44% of the resolver's CPU. It is not a free win: BoringSSL offers no stable ABI, so the library and the daemon have to be rebuilt together, and it has no PKCS#7, so some tooling stays on OpenSSL.

Does stripping code actually make a resolver faster?

Sometimes, but that's not the main reason. Removing ~13k lines of AdGuard Home and ~800 lines of ODoH mostly removes attack surface and things that phone home. The measurable speed came from separate work - allocation-free hot paths, lock-free data structures and a front cache - not from the deletions themselves.

How do you benchmark a DNS resolver honestly?

Separate cache hits from cache misses and score them differently, because mixing them lets one slow authoritative lookup misrank a good resolver. Warm every domain you intend to measure, or you're timing someone else's nameservers. Run paired rounds and report a median with a win count - on a live machine, a single round's noise is larger than most differences worth caring about.

Why one server instead of anycast?

Because anycast isn't something one person runs well, and pretending otherwise would be the dishonest part. A single node can still be fast and very hard to abuse. Reach is the thing I can't match, so I don't claim it.

Is any of this reusable on another host?

The approach is; the scripts mostly aren't. They're compiled for AMD Zen 2 and will crash with an illegal instruction elsewhere, and they overwrite distribution binaries. Every repo says so in its README rather than pretending to be a drop-in installer.

Do you keep logs?

No. Nothing about a lookup is written to disk, and the only state that exists lives in memory for the leak test on the site. There is no account, no telemetry, and no company that would benefit from collecting any of it.

Contact
§6

Get in touch

Open to independent research and consulting work on DNS, TLS and edge infrastructure. Issues and pull requests on any of the repositories are welcome too.

Email: loading…

github.com/Ozy-666 →