Open-source project
paulmillr/encrypted-dns avatar
paulmillr/encrypted-dns

encrypted-dns: a profile table you install, and a README the build overwrites

DNS over HTTPS config profiles for iOS & macOS

4,930 stars439 forksJavaScriptUnlicense

At a glance

What is it?
encrypted-dns is a set of DNS over HTTPS and DNS over TLS configuration profiles for Apple devices, one file per resolver, each offered signed and unsigned. It is not a client and not a VPN: it is generated .mobileconfig profiles plus a build script, and reading its package.json explains more about how it works than the install steps do.
Who is it for?
encrypted-dns suits someone who wants to point an iPhone or Mac at a resolver over HTTPS or TLS by installing one file, and who can decide for themselves whether a filtering resolver is acceptable, since the Censorship column is the only place that choice is written down. It does not suit a team that needs a pinned, versioned resolver list, because the repository has no releases and the provider table is rebuilt from JSON.
Can I use it commercially?
Yes. Unlicense is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 112 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • paulmillr/encrypted-dns4,930 ★ · +107
Star history
Repository2026-09-232026-11-14
paulmillr/encrypted-dns4.8k4.9k

107 Stars in the last 52 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

Safari only, because other browsers never ask to install

The usage section is short enough to memorise, and one of its instructions is the reason many first attempts fail. On an iPhone or iPad you open the profile file with Safari, tap Allow, and then walk to System Settings => General => VPN, DNS & Device Management, select the downloaded profile and tap Install.

The parenthetical in step one is the load bearing part: other browsers just download the file and will not ask for installation. A profile opened from a chat message, a mail attachment or a file manager therefore lands in your downloads with nothing installed, and the DNS setting you were expecting never appears. The same trap exists on macOS in a different shape, described next.

This is not an app. There is no binary to launch, no background process, and no toggle in Settings that the project adds. What you install is a configuration profile, which is why every provider row in the table is a `.mobileconfig` link rather than a download of software.

NAME.mobileconfig, not NAME.mobileconfig.txt, and the Profiles pane

The Mac path has its own trap, stated as step one: make sure the downloaded file has the proper extension, NAME.mobileconfig rather than NAME.mobileconfig.txt. A text editor or a download manager that appends `.txt` turns a profile into a file macOS will not offer to install, and the fix is renaming it before you start.

From there the route runs through the Apple menu, System Settings, Privacy and Security in the sidebar, then Profiles on the right. The file warns that you may need to scroll down to find it, and that you may be asked for your password or other information during installation. In the Downloaded section you double-click the profile, review its contents, then click Continue, Install or Enroll.

One behaviour is worth knowing before you install a second profile over the first: if an earlier version of a profile is already installed on your Mac, the settings in the updated version replace the previous ones.

The Censorship column is about what the resolver tells you, not about blocking

Before the provider table there is a one line definition, and it is the most useful sentence in the file. Censorship, also known as filtering, means the profile will not send true information about the `hostname=IP` relation for some hosts.

That is a stronger statement than saying ads are blocked. A filtered resolver answers with an address that is not the host's real one, so anything that resolves a name and then compares the result against an expected address sees a mismatch. The rows make the distinction concrete. AdGuard DNS Family Protection is marked Yes and blocks `Default` plus malware and adult content. AdGuard DNS Non-filtering is marked No. BlahDNS CDN Unfiltered is No, while BlahDNS CDN Filtered, BlahDNS Germany and BlahDNS Singapore are all Yes and described as blocking ads, tracking and malware. Aliyun Public DNS is marked No, and Alekberg Encrypted DNS is marked No and described as Independent.

The Notes column is the second filter to read. Several rows say which company operates the resolver, such as 360 Digital Security Group, AdGuard Software Ltd. or Alibaba Cloud Ltd., while the BlahDNS and Alekberg rows say Independent. Region is a third column, running from the Netherlands and Germany to Singapore and Canada.

The README is a build output, in three languages

The most surprising thing in this repository is in package.json, and it is the clean step:

json
"build:clean": "rm -f signed/*.mobileconfig profiles/*.mobileconfig README*.md",
"build": "npm run build:clean && node src/scripts/build.ts",
"sign": "node src/scripts/sign.ts",
"new": "node src/scripts/new.ts"

`build:clean` deletes three things: the signed profiles, the unsigned profiles, and every `README*.md` at the top level. So the file you are reading, `README.md`, plus `README.cmn-CN.md` and `README.cmn-TW.md`, are generated by `node src/scripts/build.ts` and then rewritten on the next build.

That explains the shape of the provider table. Each row links to reference style labels such as the signed HTTPS install and the unsigned HTTPS install, which is what a generated link collection looks like, and it explains why three languages of the same table exist with no translation tooling in the dependencies. Editing README.md by hand is work the next build discards. The source of truth is the JSON the build reads, and `format` shows where that lives: prettier is pointed at `src/*.json` and `src/scripts/*.ts`.

Two dependency pins and a signing step with no key at the top level

Three runtime dependencies carry the whole build, and all three are pinned to exact versions:

json
"@noble/hashes": "2.2.0",
"micro-key-producer": "0.8.6",
"prettier": "3.6.2"

Prettier sits in `dependencies` rather than in a development section, and the `format` script runs it over `src/*.json` and `src/scripts/*.ts`.

What the top level of the tree does not contain is a key. The entries are `.github/`, `.gitignore`, `.prettierrc.json`, `LICENSE`, the three READMEs, `package-lock.json`, `package.json`, `profiles/`, `signed/` and `src/`. No signing certificate is committed at that level, which is consistent with a repository that publishes signed output into `signed/` while letting anyone reproduce the unsigned files in `profiles/`. The tests point the same way, since one of the two test files is named `sign-single.test.ts`, which covers signing one profile rather than a batch.

The test command needs a Node that can strip types itself

The tooling is TypeScript, run without a compile step:

json
"test": "node --experimental-strip-types --test src/scripts/new.test.ts src/scripts/sign-single.test.ts",
"format": "prettier --write \"src/*.json\" src/scripts/*.ts"

`node --experimental-strip-types` means the scripts under `src/scripts/` are handed to Node directly and the types are removed at run time. The practical consequence is a floor on your Node version, and a flag that is marked experimental. A contributor on an older Node gets a failure that has nothing to do with their change.

The two test files also tell you where the workflows are. `new.test.ts` covers the script that adds a provider, `sign-single.test.ts` covers signing. Both sit next to the scripts they exercise in `src/scripts/`, and the README's contributing section is the place it points you when you want to add a provider or edit an existing one.

No releases, and the provider set can change under your feet

This repository has no GitHub releases at all, and the last push to the default branch was on 2026-07-25. There is therefore no tag to pin, no changelog to read, and no version to record in your own notes about which resolver your device was pointed at on a given day.

What fills that gap is the article the README points to, at paulmillr.com/posts/encrypted-dns/, which is also the project homepage. The repository itself is deliberately thin: configuration profiles, the scripts that generate and sign them, and the generated tables in three languages. The licence is the Unlicense, so there is no notice requirement attaching to the profile files you redistribute.

One practical warning for anyone automating this. Because the profiles are files rather than a running service, nothing on the device tells you which resolver answered the last lookup. If you need to prove that encrypted DNS is in effect, that check lives outside this repository, and the only thing it can confirm from here is that a profile is installed.

Editorial conclusion

encrypted-dns suits someone who wants to point an iPhone or Mac at a resolver over HTTPS or TLS by installing one file, and who can decide for themselves whether a filtering resolver is acceptable, since the Censorship column is the only place that choice is written down. It does not suit a team that needs a pinned, versioned resolver list, because the repository has no releases and the provider table is rebuilt from JSON. Pick a row, install the signed link through Safari, and then confirm which directory the file landed in before you assume the resolver is in use.

Frequently asked questions

What is encrypted DNS?

In this repository, encrypted DNS is a set of configuration profiles for DNS over HTTPS and DNS over TLS that you install on an iPhone, iPad or Mac. Each profile is a `.mobileconfig` file pointing the system at one resolver, and the project publishes both HTTPS and TLS variants where a provider supports them.

How do I use encrypted DNS on an iPhone?

Open the `.mobileconfig` file with Safari, tap Allow, then go to System Settings => General => VPN, DNS & Device Management, select the downloaded profile and tap Install. The README warns that other browsers only download the file and will not ask for installation.

How do I set up encrypted DNS on a Mac?

Check the extension first: it must be NAME.mobileconfig and not NAME.mobileconfig.txt. Then open the Apple menu, go to System Settings, choose Privacy and Security in the sidebar and Profiles on the right, double-click the profile under Downloaded, and click Continue, Install or Enroll.

How do I enable encrypted DNS from this profile collection?

You install a profile rather than switching on a setting: pick a provider row, download its `.mobileconfig` link, and follow the Safari and System Settings steps. Installing an updated version of a profile on a Mac replaces the settings of the earlier one.

What is an encrypted DNS server in this list?

Each row in the provider table is one resolver, with its region, a Censorship value, notes on who operates it, and two install links, one signed and one unsigned. Censorship, also called filtering, means the profile will not send true information about the `hostname=IP` relation for some hosts.

Official sources

  1. Issues
  2. License: Unlicense
  3. paulmillr/encrypted-dns on GitHub
  4. Project website
  5. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/paulmillr-encrypted-dns.svg)](https://hysenlabs.com/projects/paulmillr-encrypted-dns)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/paulmillr-encrypted-dns.svg)](https://hysenlabs.com/projects/paulmillr-encrypted-dns)

Was this useful?