Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork2.2k
The RethinkDNS resolver that deploys to Cloudflare Workers, Deno Deploy, Fastly, and Fly.io
License
serverless-dns/serverless-dns
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
serverless-dns is a Pi-Hole esquecontent-blocking, serverless, stub DNS-over-HTTPS (DoH) and DNS-over-TLS (DoT) resolver. Runs out-of-the-box onCloudflare Workers,Deno Deploy,Fastly Compute@Edge, andFly.io. Free tiers of all these services should be enough to cover 10 to 20 devices worth of DNS traffic per month.
RethinkDNS runsserverless-dns in production at these endpoints:
| Cloud platform | Server locations | Protocol | Domain | Usage |
|---|---|---|---|---|
| ⛅ Cloudflare Workers | 280+ (ping) | DoH | sky.rethinkdns.com | configure |
| 🦕 Deno Deploy | 30+ (ping) | DoH | private beta | |
| ⏱️ Fastly Compute@Edge | 80+ (ping) | DoH | private beta | |
| 🪂 Fly.io | 30+ (ping) | DoH and DoT | max.rethinkdns.com | configure |
Server-side processing takes from 0 milliseconds (ms) to 2ms (median), and end-to-end latency (varies across regions and networks) is between 10ms to 30ms (median).
TheRethink DNS resolver on Fly.io is sponsored byFLOSS/fund and FOSS United.
Cloudflare Workers is the easiest platform to setupserverless-dns:
For step-by-step instructions, refer:
| Platform | Difficulty | Runtime | Doc |
|---|---|---|---|
| ⛅ Cloudflare | Easy | v8Isolates | Hosting on Cloudflare Workers |
| 🦕 Deno.com | Moderate | DenoIsolates | Hosting on Deno.com |
| ⏱️ Fastly Compute@Edge | Easy | Fastly JS | Hosting on Fastly Compute@Edge |
| 🪂 Fly.io | Hard | NodeMicroVM | Hosting on Fly.io |
To setup blocklists, visithttps://<my-domain>.tld/configure from your browser (it should load something similar toRethinkDNS'configure page).
For help or assistance, feel free toopen an issue orsubmit a patch.
Code:
# navigate to work dircd /my/work/dir# clone this repositorygit clone https://github.com/serverless-dns/serverless-dns.git# navigate to serverless-dnscd ./serverless-dns
Node:
# install node v22+ via nvm, if required# https://github.com/nvm-sh/nvm#installing-and-updatingwget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh| bashnvm install --lts# download dependenciesnpm i# (optional) update dependenciesnpm update# run serverless-dns on node./run n# run a clinicjs.org profiler./run n [cpu|fn|mem]
Deno:
# install deno.land v2+# https://github.com/denoland/deno/#installcurl -fsSL https://deno.land/install.sh| sh# run serverless-dns on deno./run d
Fastly:
# install node v22+ via nvm, if required# install the Fastly CLI# https://developer.fastly.com/learning/tools/cli# run serverless-dns on Fastly Compute@Edge./run f
Wrangler:
# install Cloudflare Workers (cli) aka Wrangler# https://developers.cloudflare.com/workers/cli-wrangler/install-updatenpm i wrangler --save-dev# run serverless-dns on Cloudflare Workers (cli)# Make sure to setup Wrangler first:# https://developers.cloudflare.com/workers/cli-wrangler/authentication./run w# profile wrangler with Chrome DevTools# blog.cloudflare.com/profiling-your-workers-with-wrangler
Commits on this repository enforces the Google JavaScript style guide (ref:.eslintrc.cjs).A gitpre-commit hook that runs linter (eslint) and formatter (prettier) on.js files. Usegit commit --no-verifyto bypass this hook.
Pull requests are also checked for code style violations and fixed automatically where possible.
Configureenv.js if you need to tweak the defaults.For Cloudflare Workers, setup env vars inwrangler.toml, instead.For Fastly Compute@Edge, setup env vars infastly.toml, instead.
- The request/response flow: client <->
src/server-[node|workers|deno]<->doh.js<->plugin.js - The
plugin.jsflow:user-op.js->cache-resolver.js->cc.js->resolver.js
serverless-dns supports authentication with analpha-numeric bearer token for both DoH and DoT. For a token,msg-key (secret), append the output ofhex(hmac-sha256(msg-key|domain.tld), msg) toACCESS_KEYS env var in csv format. Note:msg is currently fixed tosdns-public-auth-info.
- DoH: place the
msg-keyat the end of the blockstamp, like so:1:4AIggAABEGAgAA:<msg-key>(here,1is the version,1:4AIggAABEGAgAAis the blockstamp,<msg-key>is the auth secret, and:is the delimiter). - DoT: place the
msg-keyat the end of the SNI (domain-name) containing the blockstamp:1-4abcbaaaaeigaiaa-<msg-key>(here1is the version,4abcbaaaaeigaiaais the blockstamp,<msg-key>is the auth secret, and-is the delimeter).
If the intention is to use auth with DoT too, keepmsg-key shorter (8 to 24 chars), since subdomains may only be 63 chars long in total.
You can generate the access keys for your fork frommax.rethinkdns.com, like so:
msgkey="ShortAlphanumericSecret"domain="my-serverless-dns-domain.tld"curl'https://max.rethinkdns.com/genaccesskey?key='"$msgkey"'&dom='"$domain"# output# {"accesskey":["my-serverless-dns-domain.tld|deadbeefd3adb33fa2bb33fd3eadf084beef3b152beefdead49bbb2b33fdead83d3adbeefdeadb33f"],"context":"sdns-public-auth-info"}
serverless-dns also supports TLS PSK ciphersuites when env varTLS_PSK is set to hex or base64 of randomly generated 64 bytes. Works only on cloud deployments that terminate their own TLS (like on Fly.io).
The server-hint sent to the TLS 1.2 clients is fixed to888811119999.
Static PSK: TLS 1.2 clients must set client-hint (id) as hex string from790bb453...ffae2452. The static pre-shared key is then derived fromhkdf-sha256(key, id) wherekey is itselfhkdf-sha256(seed, sha512(ctx), salt):
seedis env varTLS_PSKconverted to bytes from base64 or hex.ctxisUTF-8 encoding of stringpskkeyfixedderivationcontext.saltis fixed from44f402e7...91a6e3ceconverted to bytes.idis the static client-hint from above (790bb453...ffae2452) converted to bytes.
Dynamic PSK: For TLS 1.2 clients, to use a dynamically generated PSK identity and key (derived from env varTLS_PSK), invoke<my-domain.tld>/gentlspsk. The returned credentials are valid as long asTLS_PSK is unchanged:
{// 64 hex chars; id is to be used as-is as the psk client identity."id":"43dc2df4...6d332545",// 128 hex chars; convert to 64-length byte array to use as psk shared secret."psk":"ebc9ab07...03629dd4"}
TLSearly data (0-RTT) for TLS 1.3 (via TLS PSK) is not supported by Node.(why?)
serverless-dns can be setup to upload logs via CloudflareLogpush.
- Setup aLogpush job:
CF_ACCOUNT_ID=<hex-cloudflare-account-id>CF_API_KEY=<api-key-with-logs-edit-permission-at-account-level>R2_BUCKET=<r2-bucket-name>R2_ACCESS_KEY=<r2-access-key-for-the-bucket>R2_SECRET_KEY=<r2-secret-key-with-read-write-permissions># optional, setup a filter such that only logs form this worker ends up being pushed; but if you# do not need a filter on Worker name (script-name), edit the "filter" field below accordingly.SCRIPT_NAME=<name-of-the-worker-as-in-wrangler-toml># for more options, ref: developers.cloudflare.com/logs/get-started/api-configuration# Logpush API with cURL: developers.cloudflare.com/logs/tutorials/examples/example-logpush-curl# Available Logpull fields: developers.cloudflare.com/logs/reference/log-fields/account/workers_trace_eventscurl -s -X POST"https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/logpush/jobs" \ -H"Authorization: Bearer${CF_API_KEY}" \ -H'Content-Type: application/json' \ -d'{ "name": "dns-logpush", "logpull_options": "fields=EventTimestampMs,Outcome,Logs,ScriptName×tamps=rfc3339", "destination_conf": "r2://'"$R2_BUCKET"'/{DATE}?access-key-id='"${R2_ACCESS_KEY}"'&secret-access-key='"${R2_SECRET_KEY}"'&account-id='"{$CF_ACCOUNT_ID}"', "dataset": "workers_trace_events", "filter": "{\"where\":{\"and\":[{\"key\":\"ScriptName\",\"operator\":\"contains\",\"value\":\"'"${SCRIPT_NAME}"'\"},{\"key\":\"Outcome\",\"operator\":\"eq\",\"value\":\"ok\"}]}}", "enabled": true, "frequency": "low" }'
- Set
wrangler.tomlpropertylogpush = true, which enablesLogpush. - (Optional) env var
LOG_LEVEL = "logpush", which raises the log-level such that onlyrequest and error logs are emitted. - (Optional) Set env var
LOGPUSH_SRC = "csv,of,subdomains", which makeslog-pusher.jsemitrequest logs only if Workershostnamecontains one of the subdomains.
Logs published to R2 can be retrieved either usingR2 Workers, theR2 API, or theLogpush API.
Workers Analytics, if enabled, is pushed against a log-key,lid, which if unspecified is set to hostname of the serverless deployment with periods,., replaced with underscores,_. Auth must be setup when querying for Analytics via the API which returns a json; ex:https://max.rethinkdns.com/1:<optional-stamp>:<msg-key>/analytics?t=<time-interval-in-mins>&f=<field-name>. Possiblefields areip (client ip),qname (dns query name),region (resolver region),qtype (dns query type),dom (top-level domains),ansip (dns answer ips), andcc (ans ip country codes).
Log capture and analytics isn't yet implemented for Fly and Deno Deploy.
Deno Deploy (cloud) and Deno (the runtime) do not expose the same API surface (for example, Deno Deploy onlysupports HTTP/S server-listeners; whereas, Deno suports raw TCP/UDP/TLS in addition to plain HTTP and HTTP/S).
Except on Node,serverless-dns uses DoH upstreams defined by env vars,CF_DNS_RESOLVER_URL /CF_DNS_RESOLVER_URL_2.On Node, the default DNS upstream is1.1.1.2 (ref) or the recursive DNS resolver atfdaa::3 when running on Fly.io.
The entrypoints for Node and Deno aresrc/server-node.js,src/server-deno.ts respectively,and both listen for TCP-over-TLS, HTTP/S connections; whereas, the entrypoint for Cloudflare Workers, which only listens over HTTP (cli) orover HTTP/S (prod), issrc/server-workers.js; and for Fastly itssrc/server-fastly.js.
Local (non-prod) setups on Node,key (private) andcert (public chain) files, by default, are read frompaths defined in env vars,TLS_KEY_PATH andTLS_CRT_PATH.
Whilst for prod setup on Node (on Fly.io), eitherTLS_OFFLOAD must be set totrue orkey andcertmust bebase64 encoded in env varTLS_CERTKEY (ref), like so:
# EITHER: offload tls to fly.io and set tls_offload to trueTLS_OFFLOAD="true"# OR: base64 representation of both key (private) and cert (public chain)TLS_CERTKEY="KEY=b64_key_content\nCRT=b64_cert_content"# OPTIONALLY: use TLS with PSK ciphers (also permits domain fronting)TLS_PSK="hex-or-base64(cryptographically-secure-random-64bytes)"# OPTIONALLY: set TLS_ALLOW_ANY_SNI to true to permit domain frontingTLS_ALLOW_ANY_SNI="true"
For Deno,key andcert files are read from paths defined in env vars,TLS_KEY_PATH andTLS_CRT_PATH (ref).
Process bringup is different for each of these runtimes: For Node,src/core/node/config.js governs thebringup;while for Deno, it issrc/core/deno/config.ts, and for Workers it issrc/core/workers/config.js.src/system.js pub-sub co-ordinates thebringup phase among various modules.
On Node and Deno, in-process DNS caching is backed by@serverless-dns/lfu-cache; Cloudflare Workers is backed by bothCache Web API andin-process lfu caches. To disable caching altogether on all three platfroms, set env var,PROFILE_DNS_RESOLVES=true.
Cloudflare Workers, and Deno Deploy are ephemeral, as in, the "process" that serves client requests is not long-lived,and in fact, two back-to-back requests may be served by two differentisolates ("processes"). Fastly Compute@Edge is the also ephemeral but does not use isolates, instead Fastly creates and destroys awasmtime sandbox for each request. Resolver on Fly.io, running Node, is backed bypersistent VMs and is hence longer-lived,like traditional "serverfull" environments.
For Deno Deploy, the code-base is bundled up in a single javascript file withdeno bundle and then handed offto Deno.com.
Cloudflare Workers build-time and runtime configurations are defined inwrangler.toml.Webpack5 bundles the files in an ESM module which is then uploaded to Cloudflare byWrangler.
Fastly Compute@Edge build-time and runtime configurations are defined infastly.toml.Webpack5 bundles the files in an ESM module which is then compiled to WASM bynpx js-compute-runtimeand subsequently packaged and published to Fastly Compute@Edge with theFastly CLI.
For Fly.io, which runs Node, the runtime directives are defined infly.toml (used bydev andlive deployment-types),while deploy directives are innode.Dockerfile.flyctl accordingly setsupserverless-dns on Fly.io's infrastructure.
# build and deploy for cloudflare workers.devnpm run build# usually, env-name is prodnpx wrangler publish [-e<env-name>]# bundle, build, and deploy for fastly compute@edge# developer.fastly.com/reference/cli/compute/publishfastly compute publish# build and deploy to fly.ionpm run build:flyflyctl deploy --dockerfile node.Dockerfile --config<fly.toml> [-a<app-name>] [--image-label<some-uniq-label>]
For deploys offloading TLS termination to Fly.io (B1 deployment-type), the runtime directives are instead defined infly.tls.toml, which sets up HTTP2 Cleartext and HTTP/1.1 on port443, and DNS over TCP on port853.
Ref:github/workflows.
200+ blocklists are compressed in aSuccinct Radix Trie (based on Steve Hanov's impl) with modificationsto speed up string search (lookup) at the expense of "succintness". The blocklists are versionedwith unix timestamp (defined insrc/basicconfig.json downloaded bypre.sh), which is generated once every week, but we'd like to generate 'em daily / hourly,if possiblesee), and hosted on Cloudflare R2 (env var:CF_BLOCKLIST_URL).
serverless-dns downloadsblocklist filesrequired to setup the radix-trie during runtime bring-up or, downloads themlazily,when serving a DNS request.
serverless-dns compiles around ~17M entries (as of Nov 2025) from around 200+ blocklists. These are defined in theserverless-dns/blocklists repository.
About
The RethinkDNS resolver that deploys to Cloudflare Workers, Deno Deploy, Fastly, and Fly.io
Topics
Resources
License
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Sponsor this project
Uh oh!
There was an error while loading.Please reload this page.
Packages0
Uh oh!
There was an error while loading.Please reload this page.
Contributors14
Uh oh!
There was an error while loading.Please reload this page.
