One of the greatest inventions of the past century is the internet. It has let everyone in the world become connected in a way that we never thought possible previously. But this great invention also has many dark sides: Ads. Most sites on the internet claim to be “free”, but in reality, they are making money off of their users using ads placed throughout the site. Not only Ads exists on these sites. Many sites contain trackers that collect everything you do their sites, which is then sold to the highest bidder. As scary as it is to think about, there is something we can do to protect ourselves. Many of these ads or tracker use a DNS server to look up the IP of their servers in order to server these ads or trackers.
There are a few services called “DNS sink-holes” that act as an intermediary between the device requesting the DNS and the actual DNS server. When your PC or other device requests the IP address of a domain, it asks the “sink-hole” and that either blocks the request or lets it continue to the DNS server. We can set blacklists for blocked domains to common ad and tracker services to prevent them from responding. This effectively blocks all of those ads and trackers from our devices. Cool huh?
One of the best “sink-holes” to use is a service called Adguard. It is a free to use service with “sink-hole” functionality as well as many more cool features. As with most of my other Home-Lab services, I will be using docker to install the service, as well as securing the service behind the Traefik reverse proxy.
To install Adguard, I will use the official Adguard docker image in a docker compose file. This file is the same one I use for all my services, so some of the items may not apply to everyone, so I will try and explain the parts. Below is the docker compose code:
adguard: container_name: adguardhome restart: unless-stopped image: adguard/adguardhome hostname: adguard security_opt: - no-new-privileges:true volumes: - $DOCKERDIR/adguard/conf:/opt/adguardhome/conf - $DOCKERDIR/adguard/work:/opt/adguardhome/work networks: - t2_proxy ports: - "53:53/tcp" # Ports for DNS server - "53:53/udp" environment: - PUID=$PUID - PGID=$PGID
- security_opt: This is an optional setting to prevent the service from trying to elevate its privileges.
- volumes: This links our local folders to the docker folders. The $DOCKERDIR is an environment variable of the location of docker-compose file.
- ports: These are the ports we want to expose to the host IP. Port 53 is the DNS server port. You may need more depending on services needed.
- If you do not use a reverse proxy, such as Traefik, then you will also need to add ports 3000 and 80 to the ports list.
If you use Traefik, such as I do, then you can add the following labels you connect it to that:
labels: - "traefik.enable=true" ## HTTP Routers - "traefik.http.routers.adguard-rtr.entrypoints=https" - "traefik.http.routers.adguard-rtr.rule=Host(`dns.$DOMAINNAME`)" - "traefik.http.routers.adguard-rtr.tls=true" ## Middlewares - "traefik.http.routers.adguard-rtr.middlewares=chain-oauth@file" ## HTTP Services - "traefik.http.routers.adguard-rtr.service=adguard-svc" - "traefik.http.services.adguard-svc.loadbalancer.server.port=80"
This sets the DNS service behind dns.domainnam.ca. For extra security I hid the access behind Google OAuth so that only accounts I want can access it. The middlewares section will depend on how the rest of your system is setup.
One thing to not for the reverse proxy setup is the server port needs to be set to port 3000 for the setup of the service, then after that can be changed back to port 80.
Potential Issue: One issue that I ran into is my Ubuntu server was running an internal DNS service, so port 53 was already in use. To fix, I following the steps in the this link to disable the DNS server.
With the port set to 3000 at the start, go to either the IP of the host, port 3000, or to the dns.hostname.ca if using Traefik. you should see the setup page if everything was successfull.
Follow the setup steps and create a admin account, making sure to use a strong password of course! Once initial setup is done, you can change the compose file to use port 80. Going to the page now should bring a login page. Use the credentials you made earlier. We now have Ad Guard Home fully installed! I will leave the settings for now, maybe I’ll cover them in another post. Finally, you can follow the setup guide to connect you devices to ad guard home.
With Adguard working, we now have our devices protected from many ads and trackers, but our internet usage is still being tracked by the upstream DNS servers themselves. The thing with Adguard is, it doesn’t actually know the addresses of any of the requests, so it has to ask another server to do that lookup for us. These upstream servers collect all of these requests. To avoid this, we can install our own upstream DNS server that knows the IP address, so our requests aren’t collected by company’s like Google or Cloudflare. That’s where Unbound DNS comes in. Unbound talks to the root name servers on the internet that allow us to cache these addresses locally. It’s a bit of an advance topic, so I recommend reading the Unbound site to better understand the difference between the DNS servers out there.
We will be using a premade docker container in docker compose, same as Adguard. First we need to create a folder and some files for Unbound to use:
mkdir unbound touch unbound/a-records.conf touch unbound/forward-records.conf touch unbound/srv-records.conf
Next we need to define the docker container in our compose file:
unbound: container_name: unboundDNS restart: unless-stopped image: mvance/unbound:latest hostname: unboundDNS security_opt: - no-new-privileges:true volumes: - $DOCKERDIR/appdata/unbound:/opt/unbound/etc/unbound/ networks: t2_proxy: ipv4_address: 172.18.0.24
Things to note with the compose code:
- The network is the same as the rest of my containers, but I define a static IP address for it. This IP is local only to my docker containers, and therefor not accessible outside the server. This allows Adguard to connect, but not individual devices.
- I recommend adding a static IP for this container, but please not the IP will depend on the docker network sub-domain.
- The volume is where we pass in the files and folder we created above. Mine is just using environment variables to define the absolute path.
Now we can run the compose file. I recommend viewing the syslogs of the container to make sure it is working, as there is no GUI to indicate if things are working.
Finally we can link the Unbound server to Adguard. In Adguard, go to Settings->DNS Settings. In the upstream servers section at the top, replace the default server with the IP of the Unbound server we set before.
Before saving, I recommend trying the “Test Upstream” button to ensure Adguard can communicate with Unbound. If all is good, save the new config.
So there we have it, we set up a personal DNS server with a DNS sink-hole for Ad and Tracker blocking. Now when a device queries Adguard, it will check if the domain is allowed or not. If it is not allowed, it stops the check there and the Ad or Tracker is stopped in its tracks. If the domain is allowed, it will ask Unbound for the IP address of the domain. If unbound already knows it, it will respond immediately. If Unbound does not know, it will find out using the Root DNS servers directly instead of third party ones like Google or Cloudflare.