Skip to content

Instantly share code, notes, and snippets.

@thoroc
Last active May 28, 2025 07:40
Show Gist options
  • Save thoroc/88680fb3bcf7eb144fef656934ff83fd to your computer and use it in GitHub Desktop.
Save thoroc/88680fb3bcf7eb144fef656934ff83fd to your computer and use it in GitHub Desktop.
Self-Hosted DNS Server - Installing AdGuard Home + Unbound

Self-Hosted DNS Server - Installing AdGuard Home + Unbound

Introduction

This guide shows you how to set up a self-hosted local and secure DNS server using:

  • AdGuard Home as main DNS server with ad filter and control panel.
  • Unbound as a recursive DNS resolver, directly querying the internet root servers.
  • Docker Compose for simple and efficient orchestration.
  • Features and Benefits
  • Privacy: all DNS resolutions are done locally, without external providers.
  • Full control: customizable filters via AdGuard.
  • Performance: Local DNS cache speeds up frequent resolutions.
  • Security: native DNSSEC validation with Unbound.

Automated Scripts

Installation

  1. Download the script: setup-dns-stack.sh

  2. Set the setup script to be executable:

    chmod +x ./setup-dns-stack.sh

Uninstallation

  1. Download the script: uninstall-dns-stack.sh

  2. Set the uninstall script to be executable:

    chmod +x ./uninstall-dns-stack.sh

AdGuard Configuration

  1. Access the AdGuard web interface: http://localhost:3000
  2. Go to: Settings > DNS > Upstream DNS Servers
  3. Add the Unbound IP in the format: tcp://<IP_INTERNO_UNBOUND>:53

Example: tcp://172.22.0.2:53

Tests

  • Local test with dig: dig @127.0.0.1 google.com
  • Direct test to Unbound (if you have exposed port 5353): dig @127.0.0.1 -p 5353 google.com

Β Final Considerations

  • Restart the session after running the script to activate the group docker without needing sudo.
  • AdGuard dashboard allows you to track DNS queries and block unwanted domains.
  • Unbound operates with local cache and direct queries to root servers.

source: https://www.reddit.com/r/selfhosted/comments/1kx1sc2/selfhosted_dns_server_installing_adguard_home/

#!/bin/bash
seven
echo "πŸš€ Installing Docker and Docker Compose Plugin..."
# Update and install dependencies
sudo apt update
sudo apt install -y ca-certificates curl gnupg lsb-release apt-transport-https
# Add official Docker key
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
# Add official Docker repository
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# Install Docker and Compose plugin
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
echo "βœ… Docker installed successfully."
# Add current user to docker group
echo "πŸ”§ Adding user to docker group to avoid using sudo..."
sudo usermod -aG docker $USER
echo "⚠️ You must log out and re-enter the session (logout/login) for this change to take effect."
# Disable systemd-resolved if enabled
if systemctl is-active --quiet systemd-resolved; then
echo "πŸ”§ Disabling systemd-resolved..."
sudo systemctl disable systemd-resolved.service
sudo systemctl stop systemd-resolved.service
sudo rm -f /etc/resolv.conf
echo "nameserver 1.1.1.1" | sudo tee /etc/resolv.conf
fi
echo "πŸ“ Creating directory structure..."
mkdir -p dns-stack/adguard/{conf,work}
mkdir -p dns-stack/unbound
echo "πŸ“¦ Downloading root.hints..."
curl -o dns-stack/unbound/root.hints https://www.internic.net/domain/named.root
echo "πŸ“ Creating unbound.conf configuration file..."
cat <<EOF > dns-stack/unbound/unbound.conf
server:
verbosity: 1
interface: 0.0.0.0
port: 53
do-ip4: yes
do-udp: yes
do-tcp: yes
root-hints: "/opt/unbound/etc/unbound/root.hints"
hide-identity: yes
hide-version: yes
harden-glue: yes
harden-dnssec-stripped: yes
use-caps-for-id: yes
edns-buffer-size: 1232
prefetch: yes
cache-min-ttl: 3600
cache-max-ttl: 86400
num-threads: 2
so-rcvbuf: 1m
so-sndbuf: 1m
msg-cache-size: 50m
rrset-cache-size: 100m
qname-minimization: yes
rrset-roundrobin: yes
access-control: 0.0.0.0/0 allow
EOF
echo "🧱 Creating docker-compose.yml..."
cat <<EOF > dns-stack/docker-compose.yml
services:
adguardhome:
image: adguard/adguardhome:latest
container_name: adguardhome
volumes:
- ./adguard/work:/opt/adguardhome/work
- ./adguard/conf:/opt/adguardhome/conf
ports:
- "53:53/tcp"
- "53:53/udp"
- "3000:3000/tcp"
- "80:80/tcp"
- "443:443/tcp"
restart: unless-stopped
depends_on:
- unbound
networks:
- dns_net
unbound:
image: mvance/unbound:latest
container_name: unbound
volumes:
- ./unbound:/opt/unbound/etc/unbound
restart: unless-stopped
networks:
dns_net:
aliases:
- unbound
networks:
dns_net:
driver: bridge
EOF
echo "🐳 Uploading containers..."
cd dns-stack || exit 1
docker compose up -d
echo "πŸ”Ž Getting IP from Unbound..."
UNBOUND_IP=$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' unbound)
echo "βœ… Environment ready!"
echo "πŸ‘‰ Configure AdGuard with upstream DNS:"
echo "tcp://$UNBOUND_IP:53"
#!/bin/bash
echo "🧹 Stopping and removing containers..."
cd dns-stack || exit 1
docker compose down
echo "πŸ—‘οΈ Removing directories and files..."
cd ..
rm -rf dns-stack
echo "❌ Removing Docker and related packages..."
sudo apt purge -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo apt autoremove -y
sudo rm -rf /var/lib/docker /etc/docker
sudo groupdel docker || true
echo "βœ… Uninstallation complete."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment