Preface
I have also written an article about setting up a DNS server before, which was based on bind9 (see:Docker Series: Building a DNS Server Based on Bind9), but that is more suitable for setting up a test environment. Normally, if we only need a local DNS forwarder, bind9 is far too bloated. After searching the internet, I discovered ADguard home. Originally, ADguard home focused on domain protection (ad filtering, blacklist filtering, etc.), so when I first looked for DNS solutions, I thought the authors of those articles were writing nonsense and even looked down on it. To my surprise, upon closer inspection, isn't this exactly the perfect forwarder I wanted? It even provides DOH and DOT, and surprisingly comes with ad filtering as a bonus! At the same time, if your environment happens to have access to a VPN or proxy, you can upgrade from an ad-free DNS to an ad-free, pollution-free DNS. This can solve many problems caused by DNS pollution, such as the issue where Emby's TMDB plugin cannot scrape because "api.themovdb.org" is polluted. Although this can be temporarily resolved by modifying the local hosts file, don't forget that the IP is just one of many CDN addresses and is not permanent. Are you going to periodically resolve and modify the hosts file? (Besides, QNAP NAS resets the hosts file to default on every reboot, requiring an additional startup script to modify the hosts file after rebooting.) With pollution-free DNS, none of these are problems anymore.
Of course, to establish a pollution-free DNS, a VPN or proxy environment is required. Once you have a VPN or proxy environment, there are many other ways to normally access "api.themovdb.org", such as pointing the system where Emby is located directly to the VPN/proxy, or having the Emby system directly use a proxy that can bypass the firewall. Therefore, at that point, pollution-free DNS is just one of the optional methods.
In addition, DNS pollution and DNS hijacking are two different things. Within China, all DNS is polluted; there is no such thing as clean DNS. The so-called ability of DOT and DOH technologies to prevent DNS hijacking is actually only targeted at ISPs. So if you use Alibaba's or Tencent's DOH/DOT, it only means your ISP cannot intercept or modify your DNS requests (for example, when you type a domain wrong but get redirected directly to the ISP's ad page).
Previously, Emby on my QNAP NAS solved the TMDB API pollution issue by directly specifying an HTTP proxy at the system level. However, this caused many outgoing requests unrelated to TMDB to also use the proxy. Although it didn't have much impact (the proxy device has a domain whitelist), it was still annoying. Similarly, routing all outgoing traffic of the QNAP NAS through a VPN or proxy is equally inappropriate, since only Emby needs to use it. Therefore, none of these were perfect solutions. Now, simply pointing the DNS to a pollution-free DNS is enough, and other outgoing traffic remains unaffected. This is the most perfect way, and OCD sufferers can finally breathe a sigh of relief.
Of course, pollution-free DNS can only solve some of the problems caused by DNS pollution. As for more advanced IP-level restrictions, ASN restrictions, or even SNI restrictions, it is powerless, and you still have to rely on a VPN or proxy.
Deploy ADguard home
Creating directories on the host machine that need to be mapped inside the container
mkdir -p /docker/adguardhome/work #存放ADguard home的过滤规则文件
mkdir -p /docker/adguardhome/conf #存放ADguard home的配置文件
mkdir -p /docker/adguardhome/cert #存放ADguard home需要使用的证书文件
Set up ADguard home
The docker run command format for setting up ADguard home is as follows:
docker run --name adguardhome -d --restart=always \
-v /docker/adguardhome/work:/opt/adguardhome/work \
-v /docker/adguardhome/conf:/opt/adguardhome/conf \
-v /docker/adguardhome/cert:/opt/adguardhome/cert \
-p 53:53/tcp -p 53:53/udp \ #对外提供标准dns服务的端口,如果内网有设备需要直接将dns地址指向ADguard,那么宿主机映射端口就需要使用53端口(主要是udp53,tcp53很少,不过最好也留着)
-p 80:80/tcp -p 443:443/tcp -p 443:443/udp \ #80其实没什么用,要使用DOH或者DOQ,需要使用443端口,其中DOH使用tcp 443,DOQ使用udp 443
-p 3000:3000/tcp \ #gui面板页面
-p 853:853/tcp \ #DOT端口
-p 5443:5443/tcp -p 5443:5443/udp \ #DNSCrypt server端口
-d adguard/adguardhome
The above is the full version of the command, but in a practical environment we don't need that many ports. At most, we need TCP/UDP port 53, TCP port 443, and TCP port 3000, so the conventional version of the command is as follows:
docker run --name adguardhome -d --restart=always \
-v /docker/adguardhome/work:/opt/adguardhome/work \
-v /docker/adguardhome/conf:/opt/adguardhome/conf \
-v /docker/adguardhome/cert:/opt/adguardhome/cert \
-p 53:53/tcp -p 53:53/udp \
-p 443:443/tcp \
-p 3000:3000/tcp \
-d adguard/adguardhome
Once the container is running, you can directly usehttp://宿主机ip:3000to access ADguard home.
Initialization
When accessing ADguard for the first time, select "Simplified Chinese" in the language options in the bottom right corner, and then click "Get Started" in the center of the screen:

Note that the ports in the figure below all refer to the ports inside the container. The listening port of the web administration interface must be changed to 3000, and then click "Next" at the bottom of the page:

After setting the username and password, click "Next" at the bottom of the page (why is this in English again?):

This is the setup guide interface, which can also be seen in the GUI panel after initialization is complete. Then directly click "Next" at the bottom of the page:

Initialization successful, click "Open Dashboard" directly:

Click "Login" to officially enter:

Configure ADguard home
DNS Settings
Go to "Settings" - "DNS Settings":

The interface is as follows:

In the image above, you need to modify the "Upstream DNS Servers" according to your actual environment. For example, if it is only for domestic access, fill in domestic public DNS, such as Tencent Cloud's 119.29.29.29, Alibaba Cloud's 223.5.5.5, etc.; if you have a scientific or magic environment and need to build a pollution-free DNS, fill in the IPs of foreign public DNS here, such as Google's 8.8.8.8, Cloudflare's 1.1.1.1, or you can directly fill in the public DOH address, referring to the example in the red box below.

This part in the image above also needs to be modified according to your actual environment. "Fallback DNS Servers" is literally what it means. Since my "Upstream DNS Servers" are all foreign ones, once the scientific or magic connection is interrupted, it's game over, so I filled in a domestic Alibaba Cloud DNS in the "Fallback DNS Servers" just in case.
"Bootstrap DNS Servers" is not mandatory. It is only useful if the "Upstream DNS Servers" use DOH/DOT addresses. Furthermore, if you use domestic public DOH/DOT (such as Tencent Cloud's), it is best to fill in the corresponding DNS address (Tencent Cloud's) here.
After completing the settings, you can click "Test Upstream" in the red box below to test whether the upstream servers can work normally, and then click "Apply" to save this part of the configuration.
There are other options in this section that you can research on your own; keeping the default values will not affect usage:


Encryption Settings
Go to "Settings" - "Encryption settings":

This part is mainly for setting up the certificates required for DOT/DOH/DOQ encryption.
Here, you should first evaluate the method that best suits your actual environment. DOH requires a legitimate tcp 443 port. In a domestic environment, this means having a tcp 443 port that can run HTTPS—in plain English, it needs ICP filing. DOQ requires using udp 443. I haven't actually researched this, so I don't know if cloud host environments have any restrictions on it, but if not, it's a good choice. DOT requires using tcp 853 port, which is an option for cloud hosts without ICP filing and home broadband with public IP addresses. However, both this and DOQ have a drawback: anyone can tell what you are doing at a glance~ After all, udp 443 and tcp 853 traffic is too obvious. Although they don't know the details of what you are doing, what you are doing is clear at a glance. Since I have a domain name that is ICP-filed and have a "good citizen certificate" to legally use port 443, I chose DOH. Moreover, compared to DOT and DOQ, DOH has a massive advantage, which I will introduce later.
This part enables encryption and sets which specific encryption method to use. I only need DOH, so I only left HTTPS port 443. You can choose according to your actual situation:

Then, set up the certificates:

I will dedicate another article to the specific application of certificates in the future. It is nothing more than getting them for free from Let's Encrypt, Tencent Cloud, or Alibaba Cloud. The most convenient is ohttps (remember that the 20 free certificates applied for from Let's Encrypt and Tencent Cloud belong to the TrustAsia family, sharing a quota of up to 20 hostnames; I don't know about Alibaba, but probably the same. For detailed steps on applying for and deploying certificates with ohttps, please refer to the article:Home Data Center Series: One-Stop SSL Certificate Management Tool OHTTPS Tutorial). Assuming everyone already has a ready-made certificate and private key, there are 2 ways to upload them here:
"Set certificate path" and "Set private key file":
Remember the -v parameter in the previous docker run command:
-v /docker/adguardhome/cert:/opt/adguardhome/certOn the host machine
/docker/adguardhome/certThe directory is used to directly place certificate files and private key files, corresponding to the inside of the container/opt/adguardhome/certdirectory, so you only need to copy xxx.crt and xxx.key directly to the host machine's/docker/adguardhome/certdirectory, and then in ADguard's "Set certificate path" and "Set private key file", use ’/opt/adguardhome/cert/xxx.crt' and/opt/adguardhome/cert/xxx.keyrespectively to specify the certificate path and then save."Paste certificate content" and "Paste private key content":
This method allows you to directly copy and paste the certificate content and private key content into ADguard. With this method, the certificate and private key content are directly stored in the AdGuardHome.yaml file under the conf directory.
Both of the above methods are fine. However, if you use the "Paste" method, you will need to manually update the certificate content regularly. If you use the "Set" method, it is possible to update the certificate regularly using external methods, which I will explain in detail in another article later.
DNS Client Settings (Optional)
In this section, you can add clients and specify a specific identifier for each client:


This identifier can be called in the previous "DNS settings" section:

It can achieve allowing only a certain client to access or not allowing a certain client to access.
For example, if I want to restrict access to only myself, I can add a client and specify an identifier:

Then, in "DNS settings", allow only this user to access:

Then, use it when specifying the DOH address:
https://example.org/dns-query/222-333-444-555-666as the address, which means appending it to the regularhttps://example.org/dns-queryfollowed by the client's identifier.
There are also corresponding specification methods for using DOT and DOQ, but they require a wildcard certificate. For details, you can refer to the official documentation:

Filter Settings
"Filters" - "DNS blocklists":

Click "Add blocklist" at the bottom of the image above:

There are many lists in the image above. I just selected 2 starting with CHN. You can check them as you like, but it is recommended not to select too many, and then click save in the bottom right corner.
"Filters" - "DNS rewrites":

This is actually custom domain name resolution:

"Filters" - "Blocked services":

Here you can quickly block domain name resolution for some common networks, and you can also set a blocking schedule:

You can research the details yourself; I have no need for this.
Domestic and Overseas DNS Split Routing (Optional)
If you set up a pollution-free DNS, you will face a problem: when using this DNS, accessing some domestic websites might actually cause issues. At this point, it involves a problem of DNS splitting between domestic and international traffic. ADguard supports domestic and international DNS splitting, but it requires a whitelist corresponding to domestic domains and domestic DNS. The GitHub project "mouyase/ChinaListForAdGuardHome" provides a whitelist specifically for ADguard home, part of which is as follows:

The whitelist download address is as follows:ChinaWhiteList.txt, you can download it yourself and update it regularly.
After downloading ChinaWhiteList.txt, it can be placed in any directory on the host machine that is mounted inside the container, such as the/docker/adguardhome/confdirectory, and then use a text editor to edit theAdGuardHome.yamlfile in the same directory. In its upstream_dns_file option (which is empty by default), add the following content:/opt/adguardhome/conf/ChinaWhiteList.txt, as follows:

Save and restart the container.
Expose ADguard home services externally
For me, I have 2 ways of using ADguard home:
As a pollution-free DNS in the local area network
This is mainly for machines that need to use Emby scraping. Of course, my home broadband has a public IP, and port 53 is usable and can be exposed to the internet. However, because it is a dynamic IP that changes every few days, it doesn't make much sense.
Providing DOH service to the outside world
This is the most useful one. Why did I choose DOH among DOQ, DOT, and DOH? Because DOH is based on HTTPS, which has a huge advantage: you can use a CDN (of course, if it is a domestic CDN, you need a registered domain name)! Although my home broadband egress has a public IP, it does not have port 443. However, Tencent Cloud's CDN allows custom origin ports, so I can use Tencent Cloud CDN to point to a non-443 port on my home broadband egress router, and then map it through the router to ultimately point to port 443 of ADguard home. In the end, what is provided to the outside is port 443 accelerated by Tencent's CDN, and crucially, it can also hide the origin IP address!
In addition, regarding the choice between DOT and DOQ: if it is home broadband with a public IP address, use DOT; if it is a cloud host, you can use either DOT or DOQ. You can research the details yourself—it's just a matter of domain name resolution and port mapping (or port forwarding/allowing on cloud hosts). However, using DOT or DOQ will expose your host's IP address, so be sure to take security precautions.
Because I didn't want to open a separate port mapping for ADguard home on the router and preferred to directly use a unified external publishing port, it is a bit more complex: (HTTPS to) CDN (Cloudflare or Tencent Cloud CDN) -> (via public HTTPS to) home data center egress main reverse proxy -> (HTTPS to) ADguard home. Certificates need to be deployed at all 3 locations (ADguard must have a valid certificate, the other 2 locations can be anything). At the same time, my DOH domain is also connected to Cloudflare via custom hostnames, so I'm not afraid of crawlers and DDoS. The benefits are huge.
A simple way to determine if the DOH configured in ADguard home is successful is to directly access the DOH address in a browser. If it returns "Bad Request", it is successful:

Of course, this success does not guarantee whether it can pass Chrome's validation (for example, my .com domain hosted through Cloudflare):

While the DOH of my other Tencent .cc domain can be accessed via browser and passes Chrome's validation:


It's truly an unsolved mystery.
Note: The reason why Cloudflare fails Chrome's validation has been found. There are 2 points to note:
Cloudflare's WAF challenged the query request sent by Chrome as an abnormal request. You need to configure a skip action in the WAF rules based on conditions such as the access host, URI path (/dns-query), and UA agent.
When using tunnel to create Public hostnames, if you want to point directly to ADguard's HTTPS port, you need to configure the TLS Origin Server Name. This is because ADguard can only use valid certificates (so it is definitely not a self-signed certificate of a Cloudflare-managed domain name). When Cloudflare accesses ADguard's HTTPS port via HTTPS protocol through the tunnel, the domain name contained in the retrieved certificate does not match the one it manages, making it impossible to establish a connection. Therefore, you need to use the Origin Server Name to specify the domain name in the ADguard certificate. This is really amazing; I never knew what this option was for before, but now I do, as shown in the figure:

Of course, if you do not point directly to ADguard's HTTPS port but access it through a reverse proxy instead, this step is not required.
Client configuration for DOH/DOT servers
This is also one of the reasons why I chose DOH. In terms of the convenience of current client settings, DOH is the simplest. For example, Windows 11 already has built-in support:

And the Chrome browser is also convenient:


You can fill in DOH, DOT, or DOQ addresses in the red box in the image above.
Note that, as mentioned earlier, after entering the DOH, DOT, or DOQ address in Chrome, Chrome will validate the legitimacy of the address. If the validation fails, the following error will be displayed:

If the validation is successful, there will be no prompt. Taking the Tencent Cloud DOH as an example:

So you can tell here whether your self-built DOH and other addresses are valid (it is also possible that they are valid but the validation packets are intercepted).
Many Android clients can directly specify the DOT address, while iOS and macOS require installation via configuration profiles. For the generation of specific configuration profiles, please refer to the following URL:https://dns.notjakob.com/tool.html。
Afterword
Writing this article was exhausting. I had to sort through quite a few blind spots in my knowledge, which took a lot of my time, but it was worth it. I won't share my DOH address, as it's a bit of a taboo.
Requesting a detailed setup tutorial! I'm totally confused. I have an unregistered domain name and a Tencent Cloud domestic server, and the domain name is hosted on Cloudflare.
Are you confused about how to publish the domain name at the end? I set it up directly using my home broadband and used Cloudflare's tunnel to publish the domain name. Except for the last step of publishing the domain name, the previous processes are all in the article. Previously, I used Tencent's CDN plus a registered domain name to publish it, but later I thought about it and decided to just use the domain name on Cloudflare directly. It's all the same.
Could you write a tutorial on using NPM (Nginx Proxy Manager) for reverse proxy? 🥹
Just search for "npm" in the search box in the top right corner and it will show up~~