Tutoriais

AdGuard DNS

Tutorial completo de AdGuard Home com Unbound recursivo — DNS sinkhole, split-horizon, DoH/DoT/DoQ, HA com keepalived e troubleshooting.

Tutorial completo para subir um servidor DNS na rede doméstica usando AdGuard Home como sinkhole (bloqueio de ads/trackers) e como resolver de DNS interno (gitea.lan.example apontando pra um IP da LAN), opcionalmente combinado com Unbound para resolução recursiva sem depender de Cloudflare/Google/ISP. Também cobre integração com WireGuard, alta disponibilidade com duas instâncias e troubleshooting.

Audiência: desenvolvedor sênior, ambiente de laboratório, Debian/Docker.


1. Visão geral

1.1. DNS sinkhole vs DNS recursivo

DNS sinkhole e DNS recursivo são camadas complementares, não concorrentes.

  • DNS sinkhole (AdGuard Home, Pi-hole, Blocky): intercepta queries DNS e responde NXDOMAIN ou 0.0.0.0 para domínios em blocklists (ads, trackers, malware). Para domínios permitidos, encaminha a query para um upstream (Cloudflare, Quad9, Google, Unbound). Trabalha no nível da query, não conhece o conteúdo HTTP.
  • DNS recursivo (Unbound, BIND, Knot Resolver, Technitium): resolve nomes do zero, começando nos root servers, descendo pra TLD authoritative, depois pro authoritative do domínio. Não depende de um “upstream resolver”, o que melhora privacidade (a query vai direto pro authoritative, não passa por terceiros) ao custo de mais latência no cold cache.

A arquitetura recomendada combina os dois: AdGuard Home como sinkhole na frente, Unbound como upstream recursivo local em 127.0.0.1:5335.

1.2. Comparação: AdGuard Home vs Pi-hole vs Blocky vs Technitium

CritérioAdGuard HomePi-holeBlockyTechnitium
LinguagemGo (binário único)PHP + dnsmasq/FTLGoC# (.NET)
UI adminModerna, SPAFuncional, antigaSem UI nativa (Prometheus/Grafana)Completa, gerenciamento avançado
DoT / DoH / DoQ nativoSim (server e client)Não nativo (precisa cloudflared/stubby)Sim (client)Sim
DHCP serverSimSimNãoSim
Memória ociosa~50-80 MB~80-120 MB (FTL+lighttpd+php)~30-50 MB~120-180 MB
Blocklists curadasLista enorme pré-curadaAdlists manuaisManualManual
Per-client configSim, granularSim (CLI/admin)Via grupos YAMLSim
Logs de querySim, com filtros ricosSimStdout/PrometheusSim
Authoritative DNSNãoNãoNãoSim (zonas próprias)
DNS recursivo embutidoNãoNãoNãoSim
Curva de aprendizadoBaixaBaixaMédia (YAML)Média-alta

Para um cenário típico de laboratório (dev sênior, quer UI boa e DoT/DoH out-of-the-box, sem ficar tunando dnsmasq), AdGuard Home é o melhor balanço. Pi-hole tem comunidade maior mas mostra a idade. Blocky é ótimo se você vive em YAML/Grafana, mas a falta de UI atrapalha o dia-a-dia. Technitium é o mais completo (faz authoritative e recursivo no mesmo binário), mas é overkill para sinkhole + split-horizon.

1.3. Por que combinar com Unbound

Quando o AdGuard usa Cloudflare/Quad9 como upstream, todo histórico de DNS da casa passa por eles. Mesmo com DoT/DoH (sem ISP vendo no meio), a Cloudflare vê tudo. Unbound local elimina esse intermediário: ele consulta direto os root servers (IANA) e desce pela hierarquia. Vantagens:

  • Privacidade: nenhuma única empresa vê todas as queries.
  • DNSSEC validation local.
  • Cache local muito agressivo.
  • Independência de outages de provedor (Cloudflare/Quad9).

Custo: primeira query a um TLD novo é mais lenta (3-4 hops), mas cache resolve isso em segundos.


2. Arquitetura

flowchart TD
    subgraph LAN["LAN 192.168.1.0/24"]
        L1[Laptop]
        L2[Smart TV]
        L3[IoT / Câmeras]
        L4[Celulares]
    end

    subgraph VPN["WireGuard 10.0.0.0/24"]
        V1[Cliente VPN]
    end

    subgraph Host["Host do laboratório"]
        AGH[AdGuard Home<br/>:53 UDP/TCP<br/>:853 DoT/DoQ<br/>:443 DoH]
        UB[Unbound recursivo<br/>127.0.0.1:5335]
    end

    subgraph Internet["Internet"]
        ROOT[Root servers<br/>a-m.root-servers.net]
        TLD[TLD authoritative<br/>.com .org .lan?]
        AUTH[Domain authoritative<br/>ex: ns1.github.com]
    end

    L1 & L2 & L3 & L4 -->|query :53| AGH
    V1 -->|query via wg0| AGH

    AGH -->|domínio bloqueado| BLOCK[NXDOMAIN / 0.0.0.0]
    AGH -->|domínio permitido| UB
    AGH -->|DNS rewrite lan.example| LOCAL[IP local 192.168.1.x]

    UB -->|recursão| ROOT
    UB -->|delegação| TLD
    UB -->|query final| AUTH

    AUTH -.->|resposta| UB
    UB -.->|resposta+cache| AGH
    AGH -.->|resposta| L1

Fluxo de uma query bloqueada: cliente pergunta doubleclick.net -> AdGuard verifica blocklists -> match -> retorna NXDOMAIN ou 0.0.0.0 direto pro cliente. Sem upstream.

Fluxo de uma query permitida: cliente pergunta github.com -> AdGuard verifica blocklists (não bate) -> consulta cache -> miss -> encaminha pro Unbound em 127.0.0.1:5335 -> Unbound consulta root servers -> TLD .com -> ns1.github.com -> retorna IP -> AdGuard cacheia e devolve.

Fluxo de DNS rewrite (split-horizon): cliente pergunta gitea.lan.example -> AdGuard verifica DNS rewrites -> match -> retorna 192.168.1.50 direto, sem ir pro upstream (que rejeitaria o TLD interno).


3. Decisão de hosting

DNS é critical path. Se o servidor DNS cai, a casa inteira reclama “internet não funciona” (porque navegador, push notifications, smart TV, tudo depende). Trate como infra crítica.

OpçãoPrósContrasRecomendação
Raspberry Pi 4 dedicadoHardware barato, low power (~5W), isoladoCartão SD morre, sem redundânciaBoa para secundária
Mini PC / NUC dedicadoSSD durável, mais CPU, low powerInvestimento inicialExcelente para primária
VM no ProxmoxSnapshot, backup fácil, isoladoDepende do Proxmox estar upBoa se Proxmox tem HA
Container Docker no servidor principal do laboratórioSem hardware extra, fácil de subirCompartilha host, reboot do host derruba DNSAceitável se host é estável
Roteador OpenWRT com AdGuardSem hardware extra, integradoPouca RAM, sem logs históricosBackup only

Recomendação geral: container Docker no servidor principal como primária, e Raspberry Pi 4 com instalação bare-metal como secundária. DHCP do roteador entrega os dois IPs como DNS — clientes failover automaticamente.

Quem quer ir além: VIP com keepalived (VRRP) e config sincronizada via rsync ou git. Mas duas instâncias com DHCP entregando ambas resolve 95% dos cenários sem a complexidade extra.


4. Pré-requisitos

  • IP estático na LAN para o host do AdGuard (reserva DHCP no roteador serve).
  • Porta 53 UDP+TCP livre no host. Em Debian/Ubuntu modernos, systemd-resolved ocupa 53 em 127.0.0.53:
    ss -lnup | grep :53
    ss -lntp | grep :53
    Se aparecer systemd-resolved, desabilite ou reconfigure:
    # Opção 1: desabilitar completamente
    sudo systemctl disable --now systemd-resolved
    sudo rm /etc/resolv.conf
    echo "nameserver 1.1.1.1" | sudo tee /etc/resolv.conf
    
    # Opção 2: manter mas tirar do 53 (edita /etc/systemd/resolved.conf)
    # DNSStubListener=no
    sudo systemctl restart systemd-resolved
  • Portas 80 e 443 livres se for usar DoH (recomendado para clientes externos / VPN).
  • Porta 853 livre se for usar DoT/DoQ.
  • Acesso root/sudo.
  • Firewall liberado: UDP 53, TCP 53, TCP 80, TCP 443, UDP 443 (DoH3), TCP/UDP 853 (DoT/DoQ).

5. Instalação via Docker Compose

Crie a estrutura:

sudo mkdir -p /opt/adguardhome/work /opt/adguardhome/conf
cd /opt/adguardhome

docker-compose.yml:

services:
  adguardhome:
    image: adguard/adguardhome:latest
    container_name: adguardhome
    restart: unless-stopped
    # Use network_mode host se quiser que o AdGuard veja o IP real do cliente LAN
    # (importante para per-client stats e rate limit por IP)
    network_mode: host
    cap_add:
      - NET_ADMIN
    volumes:
      - /opt/adguardhome/work:/opt/adguardhome/work
      - /opt/adguardhome/conf:/opt/adguardhome/conf
    # Em bridge mode, descomente network_mode acima e use ports abaixo:
    # ports:
    #   - "53:53/udp"
    #   - "53:53/tcp"
    #   - "67:67/udp"        # DHCP server (opcional)
    #   - "68:68/udp"        # DHCP server (opcional)
    #   - "80:80/tcp"        # admin + DoH HTTP
    #   - "443:443/tcp"      # admin + DoH HTTPS
    #   - "443:443/udp"      # DoH3
    #   - "853:853/tcp"      # DoT
    #   - "853:853/udp"      # DoQ
    #   - "3000:3000/tcp"    # setup wizard inicial
    #   - "5443:5443/tcp"    # DNSCrypt
    #   - "5443:5443/udp"
    healthcheck:
      test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://127.0.0.1:80/control/status"]
      interval: 30s
      timeout: 5s
      retries: 3

Subir:

docker compose up -d
docker compose logs -f adguardhome

Nota sobre network_mode: host: mais simples, e essencial se você for usar AdGuard como servidor DHCP. Em modo bridge, todas as queries chegam com IP de origem do gateway do Docker, o que estraga per-client stats.


6. Instalação bare-metal Debian (Raspberry Pi ou VM)

Alternativa ao Docker. Útil pra secundária no Raspberry.

# Garantir porta 53 livre (ver passo 4)
sudo systemctl disable --now systemd-resolved

# Instalação automática (cria systemd service)
curl -s -S -L https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh | sh -s -- -v

O script:

  • Baixa o binário pra arquitetura correta (arm64, amd64, etc).
  • Instala em /opt/AdGuardHome/.
  • Cria systemd service AdGuardHome.service.
  • Inicia o serviço.

Verificar:

sudo systemctl status AdGuardHome
sudo ss -lnup | grep :53
sudo ss -lntp | grep :3000

Arquivo de configuração: /opt/AdGuardHome/AdGuardHome.yaml (criado após o setup wizard).

Para desinstalar mais tarde: sudo /opt/AdGuardHome/AdGuardHome -s uninstall.


7. Setup wizard

Abra no navegador http://IP-DO-HOST:3000.

Passos:

  1. Welcome — escolher idioma, next.
  2. Admin Web Interface — interface e porta para o painel admin. Deixe em All interfaces e porta 3000 (ou mude para 80 se quiser administrar via porta padrão; isso libera 3000 e usa 80 para admin + DoH, o que confunde — manter 3000 é mais limpo).
  3. DNS server — interface e porta para queries DNS. All interfaces + porta 53.
  4. Authentication — criar usuário admin e senha (use uma forte; vai pro AdGuardHome.yaml em bcrypt).
  5. Configure devices — instruções pra apontar clientes; pode ignorar.
  6. Finish.

Reentre no painel em http://IP-DO-HOST:3000 (ou na porta que você escolheu) com o usuário criado.


8. Configuração básica

8.1. General Settings

Settings -> General settings:

  • Filters update interval: 24 horas (ou menor se você usa lists muito voláteis).
  • Use AdGuard browsing security web service: ON (anti-phishing).
  • Use AdGuard parental control web service: ON se houver crianças, senão OFF.
  • Enforce safe search: ON para Google/Bing/YouTube se quiser SafeSearch forçado.
  • Block services: lista grande de toggles (Facebook, TikTok, etc) — útil para ambientes específicos.
  • Logs configuration: retenção de query log (24h, 7d, 30d, 90d). 7 dias é um bom default; 90 dias gera GB.
  • Statistics configuration: retenção dos stats agregados. 30 dias.

8.2. DNS Settings

Settings -> DNS settings:

  • Upstream DNS servers: comece com um par bom de DoT (depois trocaremos por Unbound no passo 11):

    tls://dns.quad9.net
    tls://1.1.1.1

    Modos:

    • Load-balancing (default): escolhe servidor menos sobrecarregado.
    • Parallel requests: dispara para todos e usa o mais rápido. Mais tráfego, menor latência.
    • Fastest IP address: testa qual IP do mesmo nome resolve mais rápido.
  • Bootstrap DNS servers: resolve os hostnames dos upstreams (dns.quad9.net, etc). Use IPs puros:

    9.9.9.10
    1.1.1.1
  • Fallback DNS servers: usado quando o upstream principal falha. 8.8.8.8, 8.8.4.4. Opcional mas recomendado.

  • Rate limit: 20 q/s por IP é o default. Para LAN doméstica, 50-100 é mais saudável (smart TVs e Apple TV disparam burst legítimo). Em AdGuardHome.yaml:

    dns:
      ratelimit: 50
      ratelimit_subnet_len_ipv4: 24
      ratelimit_subnet_len_ipv6: 56
  • EDNS Client Subnet: DESABILITADO para privacidade. ECS envia parte do seu IP pro upstream pra CDN escolher edge mais próxima — bom pra latência, ruim pra privacidade.

  • DNSSEC: marcar “Enable DNSSEC” — AdGuard valida e seta flag AD quando upstream retorna assinado.

  • Disable IPv6 resolution: marque se sua rede não tem IPv6 funcional (evita timeouts em queries AAAA).

  • Blocked response TTL: 10s. Tempo que cliente cacheia a resposta bloqueada. Curto para mudanças rápidas em allowlist.

  • Custom blocking IP: pode deixar em branco (NXDOMAIN, default) ou setar 0.0.0.0 para retornar IP nulo. NXDOMAIN é mais limpo.

Salve e teste:

dig @192.168.1.10 github.com
dig @192.168.1.10 doubleclick.net   # deve retornar NXDOMAIN

9. Listas de bloqueio

Filters -> DNS blocklists -> Add blocklist -> Choose from the list ou Add a custom list.

Recomendação curada (não combine tudo, escolha 2-3 ou vai gerar muito falso positivo):

ListaURLFoco
AdGuard DNS filternativaDefault geral, bem balanceado
OISD Bighttps://big.oisd.nl/Anti-ads + trackers, baixo falso positivo
Hagezi Prohttps://raw.githubusercontent.com/hagezi/dns-blocklists/main/adblock/pro.txtPro: ads + trackers + malware
Hagezi Normalhttps://raw.githubusercontent.com/hagezi/dns-blocklists/main/adblock/multi.txtMais agressiva
Steven Black hostshttps://raw.githubusercontent.com/StevenBlack/hosts/master/hostsClássica, ads+malware

Estratégia recomendada: AdGuard DNS filter (nativa) + OISD Big + Hagezi Pro. Cobre ~99% dos ads/trackers sem quebrar serviços legítimos.

Filters -> DNS allowlists: para domínios que sempre devem passar (mesmo se aparecerem em blocklist). Exemplos:

@@||analytics.empresa-cliente.com^

Filters -> Custom filtering rules: regras manuais usando sintaxe AdBlock estendida:

! Comentário
||tracker.exemplo.com^
@@||analytics.cliente.com^
||*.doubleclick.net^$important
||google-analytics.com^$client='192.168.1.100'

$important força aplicação mesmo contra allowlist. $client= aplica só pra IP/cliente específico.

Filters -> Blocked services: toggles prontos por serviço (Facebook, TikTok, Reddit, etc). Bom pra ambientes com restrição parental.

Auto-update: Settings -> General -> Filters update interval (24h padrão).


10. Resolução de DNS interno (split-horizon)

Você quer gitea.lan.example resolver para 192.168.1.50 quando consultado de dentro da LAN, sem vazar a query pra upstream público (que não conhece o TLD interno).

Há duas formas no AdGuard:

10.1. DNS rewrites (preferida, mais limpa)

Filters -> DNS rewrites -> Add DNS rewrite:

DomainAnswer
gitea.lan.example192.168.1.50
proxmox.lan.example192.168.1.10
*.lan.example192.168.1.10 (catch-all opcional)

Wildcard funciona. Para CNAME, coloque o hostname destino em vez de IP.

No YAML:

filtering:
  rewrites:
    - domain: gitea.lan.example
      answer: 192.168.1.50
    - domain: '*.lan.example'
      answer: 192.168.1.10

10.2. Custom filtering rules com $dnsrewrite

Filters -> Custom filtering rules:

||gitea.lan.example^$dnsrewrite=192.168.1.50
||proxmox.lan.example^$dnsrewrite=192.168.1.10

Mesma coisa do método 1, mas as regras ficam misturadas com bloqueios. Prefira o painel de DNS rewrites para visibilidade.

10.3. Conditional forwarding (encaminhar zona para outro DNS)

Se você roda um DNS authoritative interno (Technitium, BIND, ou o próprio dnsmasq do roteador), pode delegar a zona inteira:

Settings -> DNS settings -> Upstream DNS servers:

[/lan.example/]192.168.1.1
[/lan/]192.168.1.1
[/in-addr.arpa/]192.168.1.1
1.1.1.1
1.0.0.1

Sintaxe [/zone/]upstream (dnsmasq-style). Queries para *.lan.example vão pro 192.168.1.1 (roteador), o resto vai pro Cloudflare.

Útil quando o DHCP do roteador registra hostnames automaticamente — em vez de cadastrar cada host à mão no AdGuard, deixa o roteador resolver.

10.4. Cliente identification

Settings -> Client settings -> Add client:

  • Identifier: IP, CIDR, MAC, ou ClientID (para DoH/DoT autenticado).
  • Name: “Laptop Pessoal”, “Smart TV Sala”, “IoT”.
  • Upstreams: pode dar upstream diferente por cliente.
  • Tags: per-client blocklists, safe search, etc.

Permite ver no log “Smart TV Sala bloqueou X queries” em vez de IPs anônimos. Também permite rate limit e logging diferenciado.


11. Unbound como upstream recursivo

Instale no mesmo host (Debian/Ubuntu):

sudo apt update
sudo apt install -y unbound

Crie /etc/unbound/unbound.conf.d/adguard.conf:

server:
    # Interface de escuta - só localhost
    interface: 127.0.0.1
    port: 5335

    # IPv4 only (se sua rede não tem IPv6 funcional)
    do-ip4: yes
    do-udp: yes
    do-tcp: yes
    do-ip6: no
    prefer-ip6: no

    # Root hints (pacote dns-root-data fornece)
    # root-hints: "/var/lib/unbound/root.hints"

    # Trust anchor para DNSSEC (auto-managed pelo pacote)
    auto-trust-anchor-file: "/var/lib/unbound/root.key"

    # Segurança / hardening
    harden-glue: yes
    harden-dnssec-stripped: yes
    harden-below-nxdomain: yes
    harden-referral-path: yes
    use-caps-for-id: no

    # Tamanho de buffer EDNS - 1232 evita fragmentação IP em links
    # com MTU típico de 1500 (https://dnsflagday.net/2020/)
    edns-buffer-size: 1232

    # TTL mínimo e máximo
    cache-min-ttl: 300
    cache-max-ttl: 86400
    cache-max-negative-ttl: 3600

    # Privacidade
    qname-minimisation: yes
    aggressive-nsec: yes
    hide-identity: yes
    hide-version: yes

    # Performance
    num-threads: 1
    msg-cache-size: 64m
    rrset-cache-size: 128m
    so-rcvbuf: 1m

    # Logging mínimo
    verbosity: 0

    # Controle de acesso
    access-control: 127.0.0.0/8 allow
    access-control: 0.0.0.0/0 refuse

    # Reduzir DNS leak: não responder a queries de domínios privados
    # vindas da internet
    private-domain: "lan.example"
    local-zone: "10.in-addr.arpa." nodefault
    local-zone: "168.192.in-addr.arpa." nodefault

Habilitar e iniciar:

sudo systemctl enable --now unbound
sudo systemctl restart unbound

# Validar
dig @127.0.0.1 -p 5335 github.com
dig @127.0.0.1 -p 5335 dnssec.works +dnssec    # deve ter flag "ad"
dig @127.0.0.1 -p 5335 fail01.dnssec.works     # deve retornar SERVFAIL

No AdGuard, Settings -> DNS settings -> Upstream DNS servers, troque tudo por:

127.0.0.1:5335

Bootstrap pode ficar com IPs públicos (9.9.9.10, 1.1.1.1) — só é usado se algum upstream com hostname precisar resolver, o que não é o caso aqui.

Pronto: do AdGuard pra dentro, nenhuma query DNS sai pra Cloudflare/Quad9. Vai direto do Unbound pros root servers.


12. DoH e DoT para clientes externos

Útil para:

  • Cliente Firefox/iOS usando DoH apontando para seu AdGuard.
  • Cliente Android com “Private DNS” (DoT only).
  • Clientes em redes hostis (cafés, hotéis) usando seu AdGuard remotamente.

12.1. Certificado TLS

Você precisa de um certificado válido para o nome (ex: adguard.example.com). Use acme.sh ou certbot com DNS-01 challenge (se o domínio é externo) ou copie um cert existente do nginx.

Exemplo acme.sh com Cloudflare DNS:

acme.sh --issue --dns dns_cf -d adguard.example.com
acme.sh --install-cert -d adguard.example.com \
  --fullchain-file /opt/adguardhome/conf/fullchain.pem \
  --key-file /opt/adguardhome/conf/privkey.pem \
  --reloadcmd "docker restart adguardhome"

12.2. Habilitar no AdGuard

Settings -> Encryption settings:

  • Enable encryption (HTTPS, DoH, DoT, DoQ): ON.
  • Server name: adguard.example.com.
  • HTTPS port: 443.
  • DNS-over-TLS port: 853.
  • DNS-over-QUIC port: 853 (UDP).
  • Redirect HTTP to HTTPS: ON.
  • Certificate: caminho pro fullchain.pem ou colar o PEM.
  • Private key: caminho pro privkey.pem ou colar.

URLs resultantes:

  • DoH: https://adguard.example.com/dns-query
  • DoT: tls://adguard.example.com:853
  • DoQ: quic://adguard.example.com:853

12.3. Configurar clientes

Firefox: about:preferences#privacy -> DNS over HTTPS -> Max Protection -> Custom -> https://adguard.example.com/dns-query.

iOS / macOS: usar Mobileconfig generator ou perfil .mobileconfig com DNSSettings.ServerURL.

Android 9+: Settings -> Network -> Private DNS -> Hostname -> adguard.example.com. (Android só faz DoT, não DoH.)


13. Roteador apontando para o AdGuard

No roteador, configure o servidor DHCP para entregar o IP do AdGuard como DNS primário para todos os clientes da LAN.

OpenWRT (LuCI): Network -> Interfaces -> LAN -> DHCP Server -> Advanced -> DHCP-Options: 6,192.168.1.10,192.168.1.11 (primário e secundário).

UniFi: Settings -> Networks -> LAN -> DHCP -> DNS Server -> Auto -> Manual -> 192.168.1.10.

Após mudar, force renovar lease nos clientes:

# Linux
sudo dhclient -r && sudo dhclient
# Windows
ipconfig /release && ipconfig /renew && ipconfig /flushdns
# macOS
sudo dscacheutil -flushcache && sudo killall -HUP mDNSResponder

13.1. Clientes que ignoram o DHCP

Alguns clientes têm DNS hardcoded e ignoram o DHCP:

  • Smart TVs (Samsung, LG): hardcoded 8.8.8.8 para alguns serviços.
  • Chromecast: hardcoded 8.8.8.8.
  • Android com Private DNS: usa DoT direto pra Cloudflare/Google.
  • Apps que usam DoH embutido (Firefox padrão).

Solução: transparent DNS redirect no firewall do roteador. OpenWRT exemplo:

# Redireciona qualquer query DNS (UDP/TCP 53) saindo da LAN
# para o AdGuard local
iptables -t nat -A PREROUTING -i br-lan -p udp --dport 53 -j DNAT \
  --to-destination 192.168.1.10:53
iptables -t nat -A PREROUTING -i br-lan -p tcp --dport 53 -j DNAT \
  --to-destination 192.168.1.10:53

# Bloqueia DoT (porta 853) saindo da LAN para forçar uso do AdGuard
iptables -A FORWARD -i br-lan -p tcp --dport 853 -j REJECT
iptables -A FORWARD -i br-lan -p udp --dport 853 -j REJECT

DoH é mais difícil de bloquear (vai em HTTPS porta 443), só dá pra bloquear via lista de IPs conhecidos de DoH (Mozilla mantém uma).


14. WireGuard apontando para o AdGuard

No servidor WireGuard, garanta que o AdGuard escuta na interface wg0 também (All interfaces no DNS settings já cobre).

Na configuração do peer cliente:

[Interface]
PrivateKey = ...
Address = 10.0.0.2/32
DNS = 10.0.0.1   # IP do servidor WG, onde o AdGuard responde

[Peer]
PublicKey = ...
Endpoint = vpn.example.com:51820
AllowedIPs = 10.0.0.0/24, 192.168.1.0/24
PersistentKeepalive = 25

Importante:

  • DNS = 10.0.0.1 força o cliente WG a usar o AdGuard via VPN, mesmo em rede pública.
  • No AdGuard, garanta que o IP do peer (10.0.0.2) está na ACL ou em Settings -> DNS settings -> Access settings -> Allowed clients (se você restringiu).
  • No firewall do servidor VPN, permitir input UDP/TCP 53 na interface wg0:
    iptables -A INPUT -i wg0 -p udp --dport 53 -j ACCEPT
    iptables -A INPUT -i wg0 -p tcp --dport 53 -j ACCEPT

Validar do lado do cliente VPN conectado:

dig @10.0.0.1 github.com
dig @10.0.0.1 gitea.lan.example   # resolve split-horizon

15. Alta disponibilidade com 2 instâncias

Topologia simples:

Roteador DHCP -> entrega DNS = 192.168.1.10, 192.168.1.11
                                  ^             ^
                               AdGuard      AdGuard secundário
                               (Docker)     (Raspberry Pi)

Clientes do sistema operacional fazem failover automático: se primário não responde em ~3s, tentam secundário.

15.1. Sincronizar configs

Manualmente, com rsync cron:

# No primário, cron diário:
rsync -av /opt/adguardhome/conf/AdGuardHome.yaml \
  admin@192.168.1.11:/opt/AdGuardHome/AdGuardHome.yaml

ssh admin@192.168.1.11 'sudo systemctl restart AdGuardHome'

Ou versionar o yaml em git (ex: instância Gitea interna):

cd /opt/adguardhome/conf
git init
git remote add origin git@gitea.example.internal:infra/adguard-config.git
git add AdGuardHome.yaml
git commit -m "config snapshot"
git push

Cuidado: o yaml contém o hash bcrypt da senha admin. Tudo bem versionar em repo privado. Se for público, sanitize antes.

15.2. AdGuard Home Sync (ferramenta dedicada)

adguard-sync — daemon que sincroniza configuração de primário para N réplicas via API. Mais robusto que rsync.

services:
  adguardhome-sync:
    image: ghcr.io/bakito/adguardhome-sync:latest
    container_name: adguardhome-sync
    restart: unless-stopped
    volumes:
      - ./adguardhome-sync.yaml:/config/adguardhome-sync.yaml
    command: run --config /config/adguardhome-sync.yaml

15.3. keepalived (VIP)

Se quiser um único IP virtual (VIP) que floats entre primário e secundário:

# /etc/keepalived/keepalived.conf no PRIMÁRIO
vrrp_instance VI_DNS {
    state MASTER
    interface eth0
    virtual_router_id 51
    priority 100
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass changeme
    }
    virtual_ipaddress {
        192.168.1.9/24
    }
}

Secundário com state BACKUP e priority 90. DHCP entrega 192.168.1.9 como DNS, keepalived garante que sempre tem alguém respondendo.

Para um laboratório doméstico, DHCP entregando os dois IPs resolve sem a complexidade do keepalived.


16. Backup

Arquivos a backupear:

  • /opt/adguardhome/conf/AdGuardHome.yaml — toda a configuração (filtros, rewrites, upstreams, encryption, clients, senhas hash).
  • /opt/adguardhome/work/data/ — query log, stats, sessions, filters baixados.
    • data/querylog.json / data/querylog.json.1 — log de queries.
    • data/stats.db — estatísticas agregadas.
    • data/sessions.db — sessões web.
    • data/filters/ — blocklists cacheadas (regerável, opcional no backup).

Cron diário com restic ou tar:

#!/bin/bash
# /opt/adguardhome/backup.sh
DATE=$(date +%Y%m%d)
tar czf /var/backups/adguard-${DATE}.tar.gz \
  -C /opt/adguardhome conf/AdGuardHome.yaml work/data
find /var/backups -name 'adguard-*.tar.gz' -mtime +30 -delete

Restore: parar o serviço, extrair, restart.

Versionar o yaml em git é o backup mais barato e útil para revisão de mudanças.


17. Estatísticas e logs

17.1. Query log

Query log no menu lateral. Filtros:

  • Client: filtrar por IP/cliente nomeado.
  • Domain: regex parcial.
  • Status: Processed, Blocked, Whitelisted, Rewritten.
  • Reason: FilteredBlackList, Rewrite, NotFilteredNotFound, etc.

Útil para auditoria (“por que o Smart TV não conectou no Netflix? aha, blocklist X matou o domain”). Exportável em JSON.

Retenção configurável em Settings -> General -> Logs configuration. 7 dias é suficiente para troubleshooting; 30+ dias gera GB.

17.2. Statistics dashboard

Dashboard (página inicial). Mostra:

  • Queries totais nas últimas 24h / 7d / 30d.
  • % bloqueadas.
  • Top clients por volume.
  • Top queried domains.
  • Top blocked domains.
  • Upstream response time.

Útil para identificar smart devices barulhentos (smart TV consultando 5000 hosts/dia é sinal de telemetria agressiva).

17.3. Métricas externas (Prometheus)

AdGuard Home não expõe /metrics nativo. Use um exporter externo:

services:
  adguard-exporter:
    image: ebrianne/adguard-exporter:latest
    ports: ["9617:9617"]
    environment:
      - ADGUARD_PROTOCOL=http
      - ADGUARD_HOSTNAME=192.168.1.10
      - ADGUARD_USERNAME=admin
      - ADGUARD_PASSWORD=changeme

Plug no Grafana com dashboard pronto (id 13330).


18. Troubleshooting

18.1. Porta 53 já está em uso

Sintoma: AdGuard falha ao subir com erro bind: address already in use.

sudo ss -lnup | grep :53
sudo ss -lntp | grep :53

Quase sempre é systemd-resolved. Solução (escolha uma):

# A) Desabilitar completamente
sudo systemctl disable --now systemd-resolved
sudo rm /etc/resolv.conf
echo "nameserver 127.0.0.1" | sudo tee /etc/resolv.conf

# B) Manter resolved mas tirar do 53
sudo sed -i 's/#DNSStubListener=yes/DNSStubListener=no/' /etc/systemd/resolved.conf
sudo systemctl restart systemd-resolved
sudo rm /etc/resolv.conf
sudo ln -s /run/systemd/resolve/resolv.conf /etc/resolv.conf

Em Docker, garanta network_mode: host ou que a porta 53 está mapeada e livre no host.

18.2. Clientes não veem o AdGuard

Sintomas: queries não aparecem no log, clientes ainda resolvem via Cloudflare/ISP.

Causas possíveis:

  1. DHCP do roteador ainda entrega DNS antigo. Force renovação: ipconfig /release && ipconfig /renew && ipconfig /flushdns (Win), sudo dhclient -r && sudo dhclient (Linux).
  2. Cliente tem DNS configurado manualmente (override do DHCP). Verifique:
    • Win: ipconfig /all
    • Linux: resolvectl status
    • macOS: scutil --dns
  3. Cliente usa DNS hardcoded (Chromecast, smart TV). Aplicar redirect transparente no firewall (ver 13.1).
  4. Cliente usa DoH/DoT externo (Firefox padrão usa Cloudflare DoH). Desabilitar em about:config: network.trr.mode = 5 ou apontar pro seu próprio DoH.

18.3. DoH falhando

Sintomas: Certificate verify failed, SSL handshake error.

  • Certificado expirado: openssl x509 -in /opt/adguardhome/conf/fullchain.pem -noout -dates.
  • SAN errado: certificado emitido pra adguard.example.com mas cliente conecta adguard.lan.example. Reemita com SAN correto.
  • Cadeia incompleta: garanta que está usando fullchain.pem (cert + intermediários), não só cert.pem.
  • Cliente não confia na CA: se for cert self-signed ou de CA interna, importar a CA no trust store do cliente.

18.4. Domínios internos (.lan, .internal, etc) não resolvem

Sintomas: dig gitea.lan.example retorna NXDOMAIN.

Causas:

  1. DNS rewrite não configurado. Veja seção 10.1.
  2. Conditional forwarding pra zona interna não está apontado pro DNS interno correto. Veja 10.3.
  3. Sem rewrite e sem forwarding, AdGuard manda pra upstream público, que rejeita TLDs privados (TLDs .lan, .home, .local, .internal são reservados ou tratados de forma especial — .local colide com mDNS).

Recomendação: não use .local (conflita com mDNS/Bonjour). Use .lan, .internal, ou um subdomínio real sob um domínio que você controla (lab.example.com).

18.5. Upstream timeout

Sintomas: queries demoram 5s+, log mostra upstream exchange timeout.

  • Firewall bloqueando saída: garanta que UDP/TCP 53 saindo está liberado.
  • IPv6 desabilitado no SO mas habilitado no AdGuard: o AdGuard tenta AAAA upstream, que falha. Desabilite IPv6 no AdGuard (Settings -> DNS -> Disable IPv6 resolution).
  • Unbound mal configurado: sudo systemctl status unbound e checar logs. sudo unbound-checkconf para validar sintaxe da config.
  • Root hints faltando: sudo apt install dns-root-data.

18.6. AdGuard cai sob carga

Sintomas: UI fica lenta, queries dropam.

  • Rate limit muito baixo: aumentar (ver 8.2).
  • Query log muito grande: rotacionar mais cedo (Settings -> Logs -> Retention).
  • Filesystem cheio: df -h /opt/adguardhome/work.
  • Container sem memória: ajustar mem_limit no compose.

18.7. Listas não atualizam

Filters -> DNS blocklists -> Check for updates. Se falhar:

  • DNS do próprio host quebrado (AdGuard usa upstream pra resolver os hostnames das blocklists). Configurar bootstrap DNS com IPs puros.
  • HTTPS bloqueado: verificar com curl -v https://big.oisd.nl/.
  • URL da lista mudou (acontece com Hagezi às vezes). Remover e re-adicionar com URL nova.

19. Referências