Pi-hole in Docker pe un VPS, accesibil doar printr-un tunel WireGuard
Self-hosting

Pi-hole Docker Compose pe VPS: blocare reclame, fara DNS deschis

Reclamele si tracker-ele te urmaresc peste tot: pe laptop la birou, pe telefon pe date mobile, pe wifi-ul de la hotel. Pi-hole e raspunsul clasic: un mic server DNS care raspunde “nu exista nimic aici” ori de cate ori un dispozitiv cere un domeniu de reclame sau de tracking. Majoritatea il ruleaza pe un Raspberry Pi acasa, deci te ajuta doar acasa. Pune-l pe un VPS si te urmeaza oriunde. Exista insa o capcana pe care multe ghiduri o sar: daca publici pur si simplu portul 53 in internet, ai construit un resolver deschis, iar in cateva zile straini vor folosi serverul tau ca sa inunde pe altii cu trafic DNS. In tutorialul asta rulam Pi-hole cu Docker Compose si il facem accesibil doar printr-un tunel WireGuard, de pe dispozitivele tale, ca sa ai blocare de reclame oriunde fara sa devii parte din atacul altcuiva.

Proiectul se gaseste la pi-hole.net , iar imaginea oficiala de container e intretinuta la github.com/pi-hole/docker-pi-hole . Pentru tunel folosim WireGuard , care e inclus direct in kernelul Linux atat pe Debian, cat si pe Ubuntu.

Ce construim si de ce nu deschidem pur si simplu portul 53

Pe scurt, cum functioneaza DNS

De fiecare data cand telefonul deschide un site sau o aplicatie vorbeste cu serverul ei, intai intreaba un server DNS (numit si resolver) sa traduca un nume ca example.com intr-o adresa IP. Intrebarea ajunge pe portul 53. Un port e o “usa” numerotata a unui server: adresa IP le spune pachetelor la ce masina sa mearga, iar portul le spune ce program de pe masina respectiva trebuie sa le primeasca. DNS foloseste portul 53 pe UDP (rapid, fara conexiune) si uneori pe TCP (pentru raspunsuri mari).

Ce face Pi-hole

Pi-hole sta intre dispozitivele tale si serverele DNS reale. Tine o lista de domenii cunoscute de reclame, tracking si malware (baza de date “gravity”). Cand un dispozitiv cere un nume din lista, Pi-hole raspunde 0.0.0.0, deci reclama nu se mai incarca. Pentru restul, trimite intrebarea mai departe catre un resolver upstream, de exemplu Quad9, salveaza raspunsul in cache si il returneaza. Pentru ca blocarea se face la nivel de DNS, functioneaza in toate aplicatiile, nu doar in browser, si ai un dashboard care arata ce a cerut fiecare dispozitiv.

De ce un resolver deschis e o problema reala

Un resolver deschis e un server DNS care raspunde oricui din internet. Scannerele cauta permanent astfel de servere in tot spatiul IPv4, iar un port 53 expus e gasit de obicei in cateva ore. Abuzul se numeste amplificare DNS: atacatorul trimite o interogare mica (in jur de 60 de octeti) cu o adresa sursa falsificata, adresa victimei. Serverul tau trimite constiincios victimei un raspuns mult mai mare, adesea de zeci de ori mai mare. Inmulteste cu mii de interogari pe secunda si VPS-ul tau devine o arma intr-un atac DDoS. Consecintele pentru tine: plangeri de abuz, link de retea saturat, IP-ul ajuns pe liste negre si, posibil, serviciul suspendat.

UDP agraveaza situatia: nu exista handshake, deci serverul nu are cum sa-si dea seama ca adresa sursa e falsa. De aceea “pune o parola” nu e o optiune pentru DNS. Singura solutie reala e ca strainii sa nu poata ajunge deloc la port.

Designul: DNS doar prin tunel

Planul arata asa. WireGuard creeaza pe VPS o interfata de retea privata numita wg0, cu adresa 10.8.0.1. Telefonul si laptopul se conecteaza la WireGuard (singurul lucru expus in internet e portul WireGuard, care ignora orice pachet ce nu e semnat cu o cheie cunoscuta). Docker publica porturile lui Pi-hole doar pe 10.8.0.1, o adresa care exista numai in interiorul tunelului. Pe IP-ul public, nimic nu asculta pe portul 53.

Un detaliu conteaza enorm: Docker ocoleste ufw. Docker isi scrie propriile reguli de firewall pentru porturile publicate, iar acestea sunt evaluate inaintea regulilor ufw, deci ufw deny 53 nu face nimic pentru un container care publica portul 53. In designul nostru, ufw protejeaza doar serviciile host-ului (SSH si WireGuard), iar partea de Docker e protejata de adresa de bind, care e adevarata granita de securitate.

Dispozitivele taleTelefonLaptopWireGuardUDP 51820VPS (Debian 13 / Ubuntu 24.04)ufwpermite 22/tcppermite 51820/udprestul: blocatServer SSH :22WireGuard (wg0)10.8.0.1Docker: ufw nu se aplicaPublicat doar pe10.8.0.1:53 si :80Container Pi-holeDNS + interfata webVolum ./etc-piholesetari, listeDNS upstreamex. Quad9IP public :53nimic nu ascultaScanner internettesteaza portul 53fara raspuns
Configuratia finala: ufw pazeste doar serviciile host-ului (SSH si WireGuard). Porturile Pi-hole sunt publicate de Docker doar pe adresa din tunel, 10.8.0.1, deci pe IP-ul public nimic nu asculta pe portul 53.

De ce ai nevoie

  • Un VPS cu adresa IPv4 publica, cu Debian 13 sau Ubuntu 24.04. Un vCPU si 1 GB de RAM sunt mai mult decat suficiente. Recomandam un VPS KVM: WireGuard are nevoie de un modul de kernel, iar intr-un container LXC neprivilegiat de obicei nu poti crea o interfata WireGuard (si nici nu rulezi Docker comod), decat daca host-ul a fost configurat special pentru asta.
  • Un utilizator obisnuit cu drepturi sudo si autentificare SSH cu cheie. Toate comenzile de mai jos se ruleaza cu acest utilizator.
  • Aplicatia WireGuard pe telefon (Android sau iOS) si pe laptop (Windows, macOS sau Linux).
  • Cam 30-45 de minute.

In tot articolul folosim 203.0.113.10 ca IP public al VPS-ului. E o adresa de documentatie, deci inlocuieste-o cu a ta peste tot unde apare.

Procedura dintr-o privire

Ordinea pasilor nu e intamplatoare. Docker poate publica un port pe 10.8.0.1 doar daca adresa exista deja, deci WireGuard trebuie sa fie pornit inainte de Pi-hole.

Pi-hole pe VPS: cei 7 pasi1234567Pregatire VPSufw, updateDockerEngine, ComposeWireGuardwg0 10.8.0.1Clientitelefon, laptopPi-holecompose upConectareQR, testVerificarefara DNS deschisOrdinea conteaza: wg0 trebuie sa existe inainte ca Docker sa faca bind pe 10.8.0.1
Pasii 1-7 corespund titlurilor numerotate de mai jos: pregatirea VPS-ului, instalarea Docker, configurarea WireGuard, crearea clientilor, pornirea Pi-hole, conectarea dispozitivelor, verificarea ca nu exista resolver deschis.

Pasul 1: Pregatirea VPS-ului si a firewall-ului

Incepem cu un sistem complet actualizat:

sudo apt update
sudo apt full-upgrade -y

Pe Ubuntu, verifici daca e nevoie de reboot cu [ -f /var/run/reboot-required ] && echo "e nevoie de reboot". Pe Debian fisierul acesta nu e creat mereu, deci regula e mai simpla: daca upgrade-ul a instalat un pachet linux-image nou, dai reboot. In ambele cazuri comanda e sudo reboot, apoi te reconectezi.

Urmeaza firewall-ul host-ului. ufw (Uncomplicated Firewall) e o interfata prietenoasa pentru filtrul de pachete din Linux. Vine preinstalat pe Ubuntu, dar nu si pe Debian, asa ca il instalam (pe Ubuntu comanda doar iti spune ca e deja acolo):

sudo apt install -y ufw
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp
sudo ufw allow 51820/udp
sudo ufw enable
sudo ufw status verbose

Permitem SSH (22/tcp) inainte sa activam firewall-ul, ca sa nu ne blocam singuri pe dinafara. Daca serverul SSH asculta pe alt port, permite-l pe acela. Portul 51820/udp e pentru WireGuard. Rezultatul ar trebui sa se termine cu ceva de genul:

To                         Action      From
--                         ------      ----
22/tcp                     ALLOW IN    Anywhere
51820/udp                  ALLOW IN    Anywhere
22/tcp (v6)                ALLOW IN    Anywhere (v6)
51820/udp (v6)             ALLOW IN    Anywhere (v6)

Observa ce lipseste: nicio regula pentru 53 si nici pentru 80. E intentionat. Cum am explicat, ufw oricum nu controleaza porturile publicate de Docker, iar noi nu le vom publica niciodata pe IP-ul public.

La final, sa vedem ce foloseste deja portul 53:

sudo ss -lnup | grep ':53 '

Pe Ubuntu 24.04 vei vedea de obicei systemd-resolved pe 127.0.0.53 si 127.0.0.54. E resolver-ul local al VPS-ului, asculta doar pe loopback si nu intra in conflict cu noi, pentru ca vom lega Pi-hole de 10.8.0.1, nu de toate adresele. Multe ghiduri de Pi-hole iti spun sa-l dezactivezi; cu designul nostru nu e nevoie. Pe Debian 13 rezultatul poate fi gol, ceea ce e la fel de bine.

Pasul 2: Instalarea Docker Engine si Docker Compose

Un container e un proces izolat care vine cu propriul sistem de fisiere si propriile biblioteci, deci Pi-hole ruleaza exact cu dependentele testate de dezvoltatori, fara sa atinga sistemul host. Docker Compose ne lasa sa descriem containerul intr-un mic fisier YAML si sa-l pornim cu o singura comanda. Instalam Docker din repository-ul oficial, folosind fisierul in formatul deb822 actual, /etc/apt/sources.list.d/docker.sources. Variabilele $ID si $VERSION_CODENAME vin din /etc/os-release, deci aceleasi comenzi produc repository-ul corect pe Debian (debian, trixie) si pe Ubuntu (ubuntu, noble):

sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
. /etc/os-release
sudo curl -fsSL "https://download.docker.com/linux/${ID}/gpg" -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
sudo tee /etc/apt/sources.list.d/docker.sources > /dev/null <<EOF
Types: deb
URIs: https://download.docker.com/linux/${ID}
Suites: ${VERSION_CODENAME}
Components: stable
Signed-By: /etc/apt/keyrings/docker.asc
EOF
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Verificam ca totul merge:

sudo docker run --rm hello-world
sudo docker compose version

Ar trebui sa vezi mesajul “Hello from Docker!” si o versiune de Compose care incepe cu v2. In tot tutorialul rulam Docker cu sudo, in loc sa adaugam utilizatorul in grupul docker, pentru ca apartenenta la acel grup inseamna practic acces de root.

Pasul 3: Configurarea serverului WireGuard

WireGuard e un VPN (retea privata virtuala) modern: creeaza un tunel criptat intre dispozitivul tau si VPS, iar in interiorul tunelului ambele parti primesc adrese private. Fiecare parte are o pereche de chei, iar serverul vorbeste doar cu dispozitivele ale caror chei publice le cunoaste. Pachetele de la oricine altcineva sunt ignorate in tacere, deci pentru un scanner portul pare mort.

sudo apt install -y wireguard qrencode
sudo install -d -m 700 /etc/wireguard
sudo sh -c 'umask 077; wg genkey > /etc/wireguard/server.key; wg pubkey < /etc/wireguard/server.key > /etc/wireguard/server.pub'

Prima linie instaleaza uneltele WireGuard plus qrencode, pe care il folosim mai tarziu ca sa afisam un cod QR pentru telefon. Ultima linie genereaza cheia privata a serverului si deriva din ea cheia publica; umask 077 se asigura ca doar root le poate citi. Acum configuratia interfetei:

sudo tee /etc/wireguard/wg0.conf > /dev/null <<EOF
[Interface]
Address = 10.8.0.1/24
ListenPort = 51820
PrivateKey = $(sudo cat /etc/wireguard/server.key)
EOF
sudo chmod 600 /etc/wireguard/wg0.conf
sudo systemctl enable --now wg-quick@wg0
ip -brief address show wg0

Ultima comanda ar trebui sa afiseze ceva de genul wg0 UNKNOWN 10.8.0.1/24. Starea “UNKNOWN” e normala pentru interfetele WireGuard. Daca 10.8.0.0/24 se suprapune cu o retea pe care o folosesti acasa sau la birou, alege alt interval privat (de exemplu 10.66.0.0/24) si modifica-l consecvent in toti pasii.

Acum partea importanta care face totul sa supravietuiasca unui reboot. Docker nu trebuie sa porneasca inainte sa existe wg0, altfel nu poate face bind pe 10.8.0.1 si Pi-hole nu porneste. Un drop-in systemd adauga aceasta ordine serviciului Docker, fara sa modificam unit-ul original:

sudo mkdir -p /etc/systemd/system/docker.service.d
sudo tee /etc/systemd/system/docker.service.d/wait-for-wg0.conf > /dev/null <<'EOF'
[Unit]
Wants=wg-quick@wg0.service
After=wg-quick@wg0.service
EOF
sudo systemctl daemon-reload

Pasul 4: Configurari pentru telefon si laptop

Fiecare dispozitiv are nevoie de propria pereche de chei si de propria adresa in tunel: 10.8.0.2 pentru telefon si 10.8.0.3 pentru laptop. Bucla de mai jos genereaza cheile, scrie un fisier de configurare pentru fiecare dispozitiv si il adauga pe server ca [Peer]. Seteaza intai VPS_IP la IP-ul tau public real, apoi lipeste tot blocul:

VPS_IP="203.0.113.10"   # inlocuieste cu IPv4-ul public al VPS-ului tau
sudo install -d -m 700 /etc/wireguard/clients
n=2
for c in phone laptop; do
  sudo sh -c "umask 077; wg genkey > /etc/wireguard/clients/$c.key; wg pubkey < /etc/wireguard/clients/$c.key > /etc/wireguard/clients/$c.pub"
  sudo sh -c "umask 077; cat > /etc/wireguard/clients/$c.conf" <<EOF
[Interface]
PrivateKey = $(sudo cat /etc/wireguard/clients/$c.key)
Address = 10.8.0.$n/32
DNS = 10.8.0.1

[Peer]
PublicKey = $(sudo cat /etc/wireguard/server.pub)
Endpoint = ${VPS_IP}:51820
AllowedIPs = 10.8.0.1/32
EOF
  sudo tee -a /etc/wireguard/wg0.conf > /dev/null <<EOF

[Peer]
# $c
PublicKey = $(sudo cat /etc/wireguard/clients/$c.pub)
AllowedIPs = 10.8.0.$n/32
EOF
  n=$((n+1))
done
sudo systemctl reload wg-quick@wg0
sudo wg show

sudo wg show ar trebui sa afiseze interfata care asculta pe portul 51820 si doi peer-i cu allowed ips 10.8.0.2/32 si 10.8.0.3/32. Inca nu au “latest handshake”, pentru ca nu s-a conectat niciun dispozitiv.

Doua linii din fisierul clientului merita explicate:

  • DNS = 10.8.0.1 ii spune dispozitivului sa foloseasca Pi-hole ca server DNS cat timp tunelul e activ.
  • AllowedIPs = 10.8.0.1/32 inseamna split tunnel: doar traficul catre 10.8.0.1 (adica interogarile DNS si interfata web Pi-hole) trece prin VPN. Navigarea obisnuita merge direct in internet, deci VPS-ul nu trebuie sa ruteze sau sa faca NAT pentru traficul tau, iar viteza nu e afectata. Reclamele tot sunt blocate, pentru ca blocarea are loc in momentul in care dispozitivul cauta domeniul reclamei.

Am lasat deoparte intentionat PersistentKeepalive: dispozitivul incepe intotdeauna conversatia (trimite o interogare DNS), deci nu are nevoie de pachete keepalive, iar bateria telefonului iti multumeste.

Pentru telefon, afiseaza un cod QR direct in terminal (fa fereastra terminalului suficient de mare):

sudo cat /etc/wireguard/clients/phone.conf | qrencode -t ansiutf8

Pentru laptop, copiaza fisierul in directorul tau home cu proprietarul corect, descarca-l, apoi sterge copia:

sudo install -m 600 -o "$USER" -g "$(id -gn)" /etc/wireguard/clients/laptop.conf ~/laptop.conf
# pe laptop: scp utilizator@203.0.113.10:laptop.conf .
# apoi, inapoi pe VPS:
rm ~/laptop.conf

Generarea cheilor clientilor pe server e comoda pentru un tutorial. Daca preferi ca cheia privata sa nu paraseasca niciodata laptopul, genereaza perechea de chei in aplicatia WireGuard de pe laptop si pune pe server, in blocul [Peer], doar cheia publica.

Pasul 5: Pornirea Pi-hole cu Docker Compose

Tinem totul in /opt/pihole. Intai un fisier .env cu parola pentru interfata web. Compose citeste automat acest fisier si inlocuieste valoarea in YAML, deci parola nu apare in fisierul compose:

sudo mkdir -p /opt/pihole
cd /opt/pihole
sudo sh -c 'umask 077; printf "PIHOLE_PASSWORD=%s\n" "$(openssl rand -base64 24)" > .env'
sudo cat /opt/pihole/.env

Noteaza parola afisata, iti trebuie la login. Acum inima configuratiei pi-hole docker compose:

sudo tee compose.yaml > /dev/null <<'EOF'
services:
  pihole:
    image: pihole/pihole:latest
    container_name: pihole
    restart: unless-stopped
    ports:
      - "10.8.0.1:53:53/udp"
      - "10.8.0.1:53:53/tcp"
      - "10.8.0.1:80:80/tcp"
    environment:
      TZ: "Europe/Bucharest"
      FTLCONF_webserver_api_password: "${PIHOLE_PASSWORD}"
      FTLCONF_dns_listeningMode: "ALL"
      FTLCONF_dns_upstreams: "9.9.9.9;149.112.112.112"
      FTLCONF_ntp_ipv4_active: "false"
      FTLCONF_ntp_ipv6_active: "false"
      FTLCONF_ntp_sync_active: "false"
    volumes:
      - ./etc-pihole:/etc/pihole
EOF

Pe rand:

  • image: Pi-hole nu publica un tag de “versiune majora”; tag-urile sunt release-uri datate (an.luna.patch) plus latest. Folosim latest ca sa primesti mereu build-ul curent de Pi-hole v6 la pull. Compromisul e ca un update poate schimba comportamentul fara avertisment. Daca preferi actualizari previzibile, verifica pagina de release-uri , fixeaza cel mai nou tag datat si schimba-l constient cand vrei sa actualizezi.
  • ports: formatul e adresa-host:port-host:port-container. Prefixul 10.8.0.1: e cel mai important text din tot articolul. Fara el, Docker ar publica portul 53 pe toate adresele VPS-ului, IPv4 si IPv6, si ai avea un resolver deschis pe care ufw nu-l poate opri. Interfata web o publicam pe portul 80 doar in tunel; WireGuard cripteaza deja traficul.
  • FTLCONF_webserver_api_password: seteaza parola de admin din fisierul .env. Setarile trimise ca variabile FTLCONF_ devin read-only in interfata web.
  • FTLCONF_dns_listeningMode: “ALL”: implicit, Pi-hole raspunde doar clientilor din reteaua lui locala. In reteaua bridge a Docker, telefonul tau (10.8.0.2) arata ca un client la distanta, deci Pi-hole l-ar ignora. “ALL” il face sa raspunda oricui ajunge la el, iar asta e sigur aici doar pentru ca adresa de bind limiteaza deja cine poate ajunge.
  • FTLCONF_dns_upstreams: unde trimite Pi-hole intrebarile pe care nu le blocheaza, aici Quad9, separate prin punct si virgula. Poti folosi orice resolver in care ai incredere.
  • FTLCONF_ntp_…: Pi-hole v6 include un server si un client NTP pentru ora. VPS-ul isi sincronizeaza deja ceasul, iar noi nu vrem sa dam containerului capabilitatea suplimentara necesara pentru a seta ora sistemului, deci le dezactivam.
  • volumes: setarile, baza de date gravity si istoricul interogarilor stau pe host in /opt/pihole/etc-pihole, deci supravietuiesc actualizarilor containerului.

Porneste-l si urmareste log-urile un minut:

sudo docker compose up -d
sudo docker compose logs -f pihole

Compose descarca imaginea, creeaza o retea numita pihole_default si porneste containerul. In log-uri vei vedea cum Pi-hole construieste baza gravity din lista implicita de blocare si cum porneste FTL (motorul DNS al lui Pi-hole). Apasa Ctrl+C ca sa iesi din log-uri (containerul ramane pornit), apoi verifica starea:

sudo docker compose ps

Dupa un minut, starea ar trebui sa fie Up ... (healthy), iar coloana de porturi sa arate doar adrese 10.8.0.1, ceva de genul 10.8.0.1:53->53/tcp, 10.8.0.1:53->53/udp, 10.8.0.1:80->80/tcp.

Pasul 6: Conectarea dispozitivelor si testarea blocarii

Telefon: instaleaza aplicatia WireGuard, apasa butonul plus, alege “Scan from QR code”, scaneaza codul din Pasul 4, da un nume tunelului si activeaza-l. Pe iOS poti activa “On-Demand”, ca sa se reconecteze automat. Pe Android, deschide Setari, cauta “DNS privat” (Private DNS) si seteaza-l pe “Dezactivat” sau “Automat”. Daca e setat pe un hostname anume, Android trimite DNS-ul acolo prin TLS si ocoleste complet Pi-hole.

Laptop: pe Windows si macOS, deschide aplicatia WireGuard, alege “Import tunnel(s) from file”, selecteaza laptop.conf si apasa Activate. Pe un desktop Linux cu NetworkManager, cea mai curata varianta e:

sudo nmcli connection import type wireguard file laptop.conf

Acum primul test, de pe laptop, cu tunelul activ:

nslookup example.com 10.8.0.1

Ar trebui sa primesti un raspuns normal, cu adrese IP reale, de la serverul 10.8.0.1. Pe VPS, sudo wg show ar trebui acum sa arate un “latest handshake” pentru dispozitiv.

Deschide http://10.8.0.1/admin in browserul laptopului si autentifica-te cu parola din fisierul .env. Dashboard-ul va arata interogari de la 10.8.0.2 si 10.8.0.3, deci vezi pentru fiecare dispozitiv ce se cauta si ce se blocheaza. Pentru ca Docker redirectioneaza porturile publicate cu o translatare de adrese care pastreaza sursa originala, Pi-hole vede adresele reale din tunel ale dispozitivelor.

Ca sa demonstrezi blocarea intr-un mod sigur, mergi la Domains, adauga example.org ca intrare exacta de tip deny si ruleaza:

nslookup example.org 10.8.0.1

Raspunsul ar trebui sa fie acum 0.0.0.0. Asta e Pi-hole spunand “domeniul asta nu exista pentru tine”. Sterge apoi intrarea de test. De aici incolo, deschide cateva site-uri de stiri pe telefon si urmareste cum creste contorul “Queries Blocked” din dashboard.

Pasul 7: Demonstreaza ca nu ai un resolver deschis

Nu te baza pe configuratie, testeaz-o. Intai, pe VPS, listeaza ce asculta pe portul 53:

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

Ar trebui sa vezi docker-proxy pe 10.8.0.1:53 si, pe Ubuntu, systemd-resolved pe 127.0.0.53 si 127.0.0.54. Nu trebuie sa vezi 0.0.0.0:53, [::]:53 sau *:53 si nimic pe IP-ul public.

Apoi testeaza din exterior. Pe laptop, opreste tunelul WireGuard si interogheaza direct IP-ul public (pe Linux si macOS ai dig, pe Windows foloseste nslookup example.com 203.0.113.10):

dig @203.0.113.10 example.com +time=3 +tries=1

Rezultatul corect e un esec: ;; connection timed out; no servers could be reached sau “connection refused”. Daca primesti un raspuns cu adresa IP, opreste-te aici si verifica din nou sectiunea ports: din fisierul compose.

In al treilea rand, verifica ordinea la pornire. Da reboot VPS-ului cu sudo reboot, asteapta un minut, reconecteaza-te si ruleaza cd /opt/pihole && sudo docker compose ps. Pi-hole ar trebui sa fie pornit si healthy fara sa atingi nimic, datorita drop-in-ului din Pasul 3.

Optional: permite un IP fix de birou fara VPN

Uneori vrei ca o intreaga retea de birou, aflata in spatele unui router cu IP public static, sa foloseasca Pi-hole fara sa instalezi WireGuard pe fiecare calculator. Se poate, dar intelege intai compromisurile: DNS-ul va circula necriptat prin internet, lista de IP-uri permise trebuie intretinuta cand se schimba IP-ul biroului, iar protectia se bazeaza pe reguli de firewall, nu pe adresa de bind. Pentru ca Docker ocoleste ufw, regulile merg in lantul DOCKER-USER, pe care Docker il evalueaza inaintea propriilor reguli.

Afla intai numele interfetei publice de retea (e cuvantul de dupa dev):

ip route show default

Creeaza scriptul de allowlist, inlocuind adresele IP si numele interfetei cu ale tale:

sudo tee /usr/local/sbin/pihole-allowlist.sh > /dev/null <<'EOF'
#!/bin/sh
# IP-uri publice care au voie sa interogheze Pi-hole pe adresa publica
ALLOWED="198.51.100.20"
EXT_IF="eth0"
iptables -N PIHOLE-DNS 2>/dev/null || true
iptables -F PIHOLE-DNS
for ip in $ALLOWED; do
  iptables -A PIHOLE-DNS -s "$ip" -j RETURN
done
iptables -A PIHOLE-DNS -j DROP
for proto in udp tcp; do
  iptables -C DOCKER-USER -i "$EXT_IF" -p "$proto" -m conntrack --ctorigdstport 53 --ctdir ORIGINAL -j PIHOLE-DNS 2>/dev/null || \
  iptables -I DOCKER-USER -i "$EXT_IF" -p "$proto" -m conntrack --ctorigdstport 53 --ctdir ORIGINAL -j PIHOLE-DNS
done
EOF
sudo chmod 755 /usr/local/sbin/pihole-allowlist.sh

Scriptul isi tine regulile intr-un lant dedicat, PIHOLE-DNS, pe care il goleste si il reconstruieste la fiecare rulare, deci daca il rulezi de doua ori nu apar duplicate. Fiecare pachet DNS care vine pe interfata publica sare in acest lant: adresele permise merg mai departe la regulile normale ale Docker, restul sunt aruncate. Regulile iptables dispar la reboot, asa ca un mic unit systemd le reaplica dupa ce porneste Docker, si din nou de fiecare data cand Docker e repornit:

sudo tee /etc/systemd/system/pihole-allowlist.service > /dev/null <<'EOF'
[Unit]
Description=Allowlist pentru Pi-hole DNS pe IP-ul public
After=docker.service
Requires=docker.service
PartOf=docker.service

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/pihole-allowlist.sh
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now pihole-allowlist.service
sudo iptables -L DOCKER-USER -n -v

Abia dupa ce regulile sunt active, adauga legaturile publice in lista ports: din compose.yaml (pastreaza liniile cu 10.8.0.1) si aplica cu sudo docker compose up -d:

      - "203.0.113.10:53:53/udp"
      - "203.0.113.10:53:53/tcp"

Apoi repeta testul din Pasul 7 dintr-o retea care nu e pe lista: trebuie sa dea timeout. Retine ca la boot exista cateva secunde intre momentul in care Docker porneste containerul si momentul in care unit-ul incarca regulile. Daca fereastra asta nu e acceptabila pentru tine, ramai la designul doar cu WireGuard, care nu are o astfel de pauza.

Zi de zi: actualizari, liste de blocare si backup

Actualizarea Pi-hole in Docker inseamna sa descarci o imagine noua si sa recreezi containerul. Nu rula pihole -up in container, acolo nu e suportat:

cd /opt/pihole
sudo docker compose pull
sudo docker compose up -d
sudo docker image prune -f

Liste de blocare: Pi-hole porneste cu o lista implicita rezonabila. Poti adauga altele din Lists in interfata web; dupa adaugare, actualizeaza baza gravity cu sudo docker exec pihole pihole -g. Mai multe liste inseamna mai multa blocare, dar si mai multe rezultate fals pozitive (o pagina de login sau un magazin online care nu mai merge). Cand ceva se strica, Query Log arata ce domeniu a fost blocat si il poti permite cu un click.

Backup: cea mai rapida varianta e Settings > Teleporter din interfata web, care descarca o arhiva cu setarile si listele. Pentru o copie completa la nivel de fisiere, opreste scurt containerul ca bazele de date sa fie consistente:

cd /opt/pihole
sudo docker compose stop
sudo tar czf ~/pihole-backup-$(date +%F).tar.gz -C /opt pihole -C / etc/wireguard
sudo docker compose start
sudo chown "$USER": ~/pihole-backup-*.tar.gz

Arhiva contine atat /opt/pihole (inclusiv fisierul .env), cat si /etc/wireguard cu toate cheile private, deci trateaz-o ca pe un secret. Pentru ca a fost creata cu sudo, apartine lui root; chown o face a ta, ca s-o poti descarca cu scp.

Cat consuma?

Foarte putin. Pi-hole v6 a integrat serverul web in motorul DNS, deci un singur proces face toata treaba. Pe un VPS cu 1 GB, intregul stack lasa libera cea mai mare parte a memoriei:

Consum aproximativ de RAM pe un VPS de 1 GBComponentaSistemul de baza~200 MBDocker Engine~80 MBContainer Pi-hole~70 MBWireGuardsub 5 MBLiber / cache~650 MB0100200300400500600700RAM in MB (aproximativ, variaza cu marimea listelor si imaginea OS)
Consum aproximativ de memorie dupa instalare. Pi-hole creste odata cu numarul de intrari din listele de blocare, dar chiar si cu cateva liste mari ramane mult sub 200 MB. Consumul de CPU e neglijabil pentru cateva dispozitive.

Depanare si greseli frecvente

cannot assign requested address la pornirea Pi-hole. Docker a incercat sa faca bind pe 10.8.0.1 inainte ca adresa sa existe. Verifica ip -brief address show wg0; daca lipseste, ruleaza sudo systemctl start wg-quick@wg0 si apoi sudo docker compose up -d. Daca se intampla dupa fiecare reboot, lipseste drop-in-ul din Pasul 3 sau ai uitat de sudo systemctl daemon-reload.

address already in use pe portul 53. Altceva asculta deja pe exact acea adresa. Cu prefixul 10.8.0.1: asta aproape nu se intampla; e eroarea tipica atunci cand se publica simplu 53:53 pe Ubuntu si apare conflictul cu systemd-resolved. Nu o “repara” stergand prefixul.

Tunelul nu arata niciodata handshake. Verifica daca ufw permite 51820/udp, daca Endpoint din fisierul clientului contine IP-ul public real si daca ai importat fisierul corect pe dispozitivul corect. sudo wg show pe VPS afiseaza cheile publice asteptate; compara-le cu cele din client.

Tunelul e activ, dar interogarile DNS dau timeout. Cel mai des, FTLCONF_dns_listeningMode nu e setat pe ALL, deci Pi-hole ignora clientii din afara retelei containerului. Verifica mesajele din “Pi-hole diagnosis” din interfata web si log-urile containerului.

Inca apar reclame. Verifica daca browserul nu foloseste propria setare de “DNS securizat” (DNS over HTTPS) catre un furnizor public si daca “DNS privat” de pe Android nu e setat pe un hostname. Unele reclame, mai ales pe platformele mari de video, vin de pe aceleasi domenii ca si continutul si nu pot fi blocate deloc prin DNS. Tine minte si ca dispozitivele pastreaza raspunsurile DNS in cache o vreme.

Laptop Linux: resolvconf: command not found. Apare cu wg-quick pe sisteme care folosesc systemd-resolved. Importa tunelul cu nmcli ca in Pasul 6 sau creeaza legatura de compatibilitate cu sudo ln -s /usr/bin/resolvectl /usr/local/bin/resolvconf.

RTNETLINK answers: Operation not supported la pornirea wg0. Kernelul nu poate crea interfete WireGuard, situatie tipica pentru un container LXC neprivilegiat. Foloseste un VPS KVM pentru aceasta configuratie.

Un site nu mai merge. Deschide Query Log, filtreaza dupa IP-ul dispozitivului, gaseste domeniul blocat chiar inainte de eroare si adauga-l in lista de permise.

Note de securitate

  • Adresa de bind e granita ta de securitate. Fiecare linie de port din fisierul compose trebuie sa inceapa cu 10.8.0.1: (sau cu un IP public din allowlist, daca ai urmat constient sectiunea optionala). Verific-o dupa fiecare modificare si reia testul din exterior din Pasul 7.
  • Nu te baza pe ufw pentru porturile Docker. El protejeaza SSH si WireGuard pe host. Porturile publicate de Docker sunt controlate de adresa de bind si, daca ai nevoie, de lantul DOCKER-USER.
  • Pazeste cheile private. Tot ce e in /etc/wireguard si in arhivele de backup poate impersona dispozitivele tale. Daca pierzi un telefon, sterge blocul lui [Peer] din /etc/wireguard/wg0.conf si ruleaza sudo systemctl reload wg-quick@wg0; cheia nu mai functioneaza imediat.
  • Protejeaza interfata web. Foloseste o parola lunga (cea generata e buna) si activeaza autentificarea in doi pasi din setarile Pi-hole. Interfata e accesibila doar prin tunel, dar un strat de protectie in plus nu costa nimic.
  • Ai grija la confidentialitate. Pi-hole inregistreaza fiecare interogare a dispozitivelor tale. E util la depanare, dar e si un jurnal detaliat al navigarii tale. Ajusteaza nivelul de confidentialitate si perioada de pastrare a log-urilor dupa cum te simti confortabil.
  • Tine totul actualizat. Descarca regulat imagini noi de Pi-hole si tine host-ul la zi; pe Debian si Ubuntu, pachetul unattended-upgrades aplica automat actualizarile de securitate.
  • Limitarea de rata ramane activa. Pi-hole limiteaza implicit fiecare client (in jur de 1000 de interogari pe minut). Las-o activa; e o plasa de siguranta utila daca un dispozitiv o ia razna.

Deci, merita un Pi-hole pe VPS?

Daca vrei blocare de reclame si tracker-e pe telefon si pe laptop oriunde te-ai afla, nu doar pe wifi-ul de acasa, un Pi-hole pe VPS e unul dintre cele mai satisfacatoare proiecte mici de self-hosting: un container, un tunel si un dashboard care iti arata cat gunoi incearca dispozitivele tale sa incarce in fiecare zi. Cheia e sa-l faci responsabil: DNS doar prin WireGuard (sau printr-o allowlist explicita), niciodata pe un port 53 public.

Poti rula tot acest stack pe cel mai mic VPS pe care il oferim, gazduit in propriul nostru data center din Romania, deci latenta DNS suplimentara pentru utilizatorii din Romania e de doar cateva milisecunde. Daca preferi ca altcineva sa configureze WireGuard, Pi-hole si regulile de firewall si sa le tina la zi, echipa noastra de administrare servere se poate ocupa de asta pentru tine.

Intrebari frecvente

Trebuie sa deschid portul 53 in ufw?

Nu. DNS-ul ajunge la Pi-hole prin tunelul WireGuard, iar Docker gestioneaza singur portul publicat pe 10.8.0.1. Singurele porturi pe care le deschizi sunt SSH si 51820/udp pentru WireGuard.

O sa-mi incetineasca navigarea?

Aproape deloc. Doar cautarile DNS trec prin tunel, fiecare adauga de la cateva milisecunde pana la cateva zeci, in functie de unde esti, iar atat Pi-hole, cat si dispozitivul tau pastreaza raspunsurile in cache. Descarcarile si video-urile nu trec deloc prin VPS.

Pot trece tot traficul prin VPS?

Da, setand AllowedIPs = 0.0.0.0/0 pe client, dar atunci serverul trebuie sa redirectioneze si sa faca NAT pentru traficul tau, ceea ce cere configurari suplimentare de kernel si firewall. E alt proiect; split tunnel-ul din acest tutorial e tot ce ii trebuie lui Pi-hole.

Blocheaza Pi-hole reclamele de pe YouTube?

De obicei nu. Acele reclame vin de pe aceleasi domenii ca si video-urile, deci blocarea prin DNS nu le poate separa. Pi-hole e excelent pentru retele de reclame, tracker-e si telemetrie, nu pentru orice reclama de pe orice platforma.

Pi-hole v6 e compatibil cu vechile variabile WEBPASSWORD?

Nu. Pi-hole v6 a inlocuit vechile variabile cu schema FTLCONF_ folosita in acest articol. Daca preiei un fisier compose dintr-un ghid mai vechi, traduce acele setari, altfel vor fi ignorate.

Ce VPS imi trebuie?

Cel mai mic e suficient pentru o familie sau o echipa mica: aproximativ 1 GB de RAM si un vCPU duc linistit cateva zeci de dispozitive.

Avem Incredere & Suntem Membri

Suntem membri ai principalelor organizatii de infrastructura internet.

RIPE NCC MANRS PeeringDB RoTLD DSIX SBIX 4IXP LOCIX Euro-IX RIPE NCC MANRS PeeringDB RoTLD DSIX SBIX 4IXP LOCIX Euro-IX