Traefik
Configure AdGuard Home, Traefik, Keepalived and Pocket ID for a self-hosted DNS proxy.
References
Section titled “References”Directory structure
Section titled “Directory structure”Directoryroot
- compose.yml (*)
Directorytraefik
Directoryconfig
- traefik.yml (*)
- dynamic_config.yml (*)
Directoryletsencrypt
- acme.json
Directoryadguard
Directoryconf
- AdGuardHome.yaml (*)
Directorywork/
- …
Directorykeepalived
- keepalived.conf
-
Initial setup
Section titled “Initial setup”Make directories
Section titled “Make directories”Terminal window mkdir -p {{DOCKER_PATH_VAR}}/dns-proxy/traefik/{config,letsencrypt} {{DOCKER_PATH_VAR}}/dns-proxy/pocket-id/data {{DOCKER_PATH_VAR}}/dns-proxy/adguard/{conf,work} {{DOCKER_PATH_VAR}}/dns-proxy/keepalived && cd {{DOCKER_PATH_VAR}}/traefikcompose.yml
Section titled “compose.yml”Terminal window nano compose.ymlcompose.yml services:traefik:container_name: traefikimage: traefik:v3.7ports:- "80:80"- "443:443"environment:CF_DNS_API_TOKEN: ${CF_DNS_API_TOKEN}volumes:- /var/run/docker.sock:/var/run/docker.sock:ro- ./config/traefik:/etc/traefik:ro- ./letsencrypt:/letsencryptextra_hosts:- "host.docker.internal:host-gateway"networks:- proxyrestart: unless-stoppedpocket-id:container_name: pocket-idimage: pocketid/pocket-id:v2env_file: .envports:- "1411:1411"volumes:- "./pocket-id/data:/app/data"networks:- proxyrestart: unless-stoppedtraefik-forward-auth:container_name: traefik-forward-authimage: ghcr.io/italypaleale/traefik-forward-auth:4environment:DEFAULT_PROVIDER: ${DEFAULT_PROVIDER}PROVIDERS_OIDC_ISSUER_URL: ${PROVIDERS_OIDC_ISSUER_URL}PROVIDERS_OIDC_CLIENT_ID: ${PROVIDERS_OIDC_CLIENT_ID}PROVIDERS_OIDC_CLIENT_SECRET: ${PROVIDERS_OIDC_CLIENT_SECRET}SECRET: ${SECRET}COOKIE_DOMAIN: ${COOKIE_DOMAIN}AUTH_HOST: ${AUTH_HOST}networks:- proxyrestart: unless-stoppedadguard:container_name: adguardimage: adguard/adguardhome:latestrestart: unless-stoppedvolumes:- ./adguard/work:/opt/adguardhome/work- ./adguard/conf:/opt/adguardhome/confnetwork_mode: hostadguardhome-sync:container_name: adguardhome-syncimage: ghcr.io/bakito/adguardhome-sync:latestcommand: runenvironment:ORIGIN_URL: ${ORIGIN_URL}ORIGIN_USERNAME: ${ORIGIN_USERNAME}ORIGIN_PASSWORD: ${ORIGIN_PASSWORD}REPLICA_URL: ${REPLICA_URL}REPLICA_USERNAME: ${REPLICA_USERNAME}REPLICA_PASSWORD: ${REPLICA_PASSWORD}CRON: "*/10 * * * *"RUN_ON_START: "true"networks:- proxyrestart: unless-stoppedkeepalived:container_name: keepalivedimage: osixia/keepalived:latestnetwork_mode: hostcap_add:- NET_ADMIN- NET_BROADCAST- NET_RAWvolumes:- ./keepalived/keepalived.conf:/etc/keepalived/keepalived.conf:rorestart: unless-stoppednetworks:proxy:name: proxydriver: bridgeexternal: trueCloudflare API token
Section titled “Cloudflare API token”Generate a scoped API token to automate DNS record management and SSL certificate validation.
- Go to Dashboard > My Profile > API Tokens.
- Create Token: Use the
Edit zone DNS template. - Permissions: Select
Zone|DNS|Edit. - Zone Resources: Select
Include|Specific zone|your-domain.tld.
Terminal window nano .env.env # pocket-idAPP_URL=https://auth.{{BASE_SUB_DOMAIN_VAR}}TRUST_PROXY=truePUID=1000PGID=1000# Encryption key (choose one method):# Method 1: Direct key (simple but less secure)# Generate with: openssl rand -base64 32ENCRYPTION_KEY=# Method 2: File-based key (recommended)# Put the base64 key in a file and point to it here.# ENCRYPTION_KEY_FILE=/path/to/encryption_key# traefikCF_DNS_API_TOKEN=# traefik-forward-authDEFAULT_PROVIDER=oidcPROVIDERS_OIDC_ISSUER_URL=https://auth.{{BASE_SUB_DOMAIN_VAR}}PROVIDERS_OIDC_CLIENT_ID=PROVIDERS_OIDC_CLIENT_SECRET=SECRET=# openssl rand -hex 16COOKIE_DOMAIN={{BASE_SUB_DOMAIN_VAR}}AUTH_HOST=auth.{{BASE_SUB_DOMAIN_VAR}}# adguardhome-syncORIGIN_URL=http://{{SERVER_IP_VAR}}:8080ORIGIN_USERNAME=ORIGIN_PASSWORD=REPLICA_URL=http://{{CLIENT_IP_VAR}}:8080REPLICA_USERNAME=REPLICA_PASSWORD= -
Traefik
Section titled “Traefik”traefik.yml
Section titled “traefik.yml”Terminal window nano ./traefik/config/traefik.yml./traefik/config/traefik.yml entryPoints:web:address: ":80"http:redirections:entryPoint:to: websecurescheme: httpswebsecure:address: ":443"transport:respondingTimeouts:readTimeout: "30m"http:tls:certResolver: "cloudflare"providers:docker:exposedByDefault: falsefile:filename: "/etc/traefik/dynamic_config.yml"watch: truecertificatesResolvers:cloudflare:acme:storage: "/letsencrypt/acme.json"# staging, remove for productioncaServer: "https://acme-staging-v02.api.letsencrypt.org/directory"dnsChallenge:provider: "cloudflare"resolvers:- "1.1.1.1:53"- "1.0.0.1:53"api:insecure: falsedashboard: falselog:level: "INFO"format: "common"dynamic_config.yml
Section titled “dynamic_config.yml”Terminal window nano ./traefik/config/dynamic_config.yml./traefik/config/dynamic_config.yml http:middlewares:pocket-id-auth:forwardAuth:address: "http://traefik-forward-auth:4181"authResponseHeaders:- "X-Forwarded-User"- "X-Forwarded-Email"trustForwardHeader: truehsts-header:headers:stsSeconds: 31536000stsIncludeSubdomains: truerouters:router-pocket-id:rule: "Host(`auth.{{BASE_SUB_DOMAIN_VAR}}`)"service: service-pocket-identryPoints: ["websecure"]router-dashboard:rule: "Host(`traefik.{{BASE_SUB_DOMAIN_VAR}}`)"service: api@internalentryPoints: ["websecure"]middlewares: ["pocket-id-auth"]router-adguard:rule: "Host(`adguard.{{BASE_SUB_DOMAIN_VAR}}`)"service: service-adguardentryPoints: ["websecure"]middlewares: ["pocket-id-auth"]router-arcane:rule: "Host(`arcane.{{BASE_SUB_DOMAIN_VAR}}`)"service: service-arcaneentryPoints: ["websecure"]middlewares: ["hsts-header"]router-echo-link:rule: "Host(`echo.{{BASE_SUB_DOMAIN_VAR}}`)"service: service-echo-linkentryPoints: ["websecure"]router-immich:rule: "Host(`immich.{{BASE_SUB_DOMAIN_VAR}}`)"service: service-immichentryPoints: ["websecure"]router-jellyfin:rule: "Host(`jf.{{BASE_SUB_DOMAIN_VAR}}`)"service: service-jellyfinentryPoints: ["websecure"]router-linkding:rule: "Host(`link.{{BASE_SUB_DOMAIN_VAR}}`)"service: service-linkdingentryPoints: ["websecure"]router-dynacat:rule: "Host(`home.{{BASE_SUB_DOMAIN_VAR}}`)"service: service-dynacatentryPoints: ["websecure"]middlewares: ["hsts-header"]services:service-pocket-id:loadBalancer:servers:- url: "http://pocket-id:1411"service-adguard:loadBalancer:servers:- url: "{{SERVER_IP_VAR}}:8080"service-arcane:loadBalancer:servers:- url: "{{SERVER_IP_VAR}}:3552"service-echo-link:loadBalancer:servers:- url: "{{SERVER_IP_VAR}}:3002"service-immich:loadBalancer:servers:- url: "{{SERVER_IP_VAR}}:2283"service-jellyfin:loadBalancer:servers:- url: "{{SERVER_IP_VAR}}:8096"service-linkding:loadBalancer:servers:- url: "{{SERVER_IP_VAR}}:9090"service-dynacat:loadBalancer:servers:- url: "{{SERVER_IP_VAR}}:8282"acme.json
Section titled “acme.json”Terminal window touch ./traefik/letsencrypt/acme.json && chmod 600 ./traefik/letsencrypt/acme.json -
AdGuardHome.yaml
Section titled “AdGuardHome.yaml”Terminal window nano ./adguard/conf/AdGuardHome.yaml./adguard/conf/AdGuardHome.yaml http:pprof:port: 6060enabled: falseaddress: 0.0.0.0:8080session_ttl: 720husers: []auth_attempts: 5block_auth_min: 15http_proxy: ""language: ""theme: autodns:bind_hosts:- 0.0.0.0port: 53anonymize_client_ip: falseratelimit: 20ratelimit_subnet_len_ipv4: 24ratelimit_subnet_len_ipv6: 56ratelimit_whitelist: []refuse_any: trueupstream_dns:- https://dns.quad9.net/dns-query- https://dns.cloudflare.com/dns-query- https://dns.adguard-dns.com/dns-queryupstream_dns_file: ""bootstrap_dns:- 9.9.9.10- 149.112.112.10- 2620:fe::10- 2620:fe::fe:10fallback_dns: []upstream_mode: load_balancefastest_timeout: 1sallowed_clients: []disallowed_clients: []blocked_hosts:- version.bind- id.server- hostname.bindtrusted_proxies:- 127.0.0.0/8- ::1/128cache_enabled: truecache_size: 4194304cache_ttl_min: 0cache_ttl_max: 0cache_optimistic: falsecache_optimistic_answer_ttl: 30scache_optimistic_max_age: 12hbogus_nxdomain: []aaaa_disabled: falseenable_dnssec: trueedns_client_subnet:custom_ip: ""enabled: falseuse_custom: falsemax_goroutines: 300handle_ddr: trueipset: []ipset_file: ""bootstrap_prefer_ipv6: falseupstream_timeout: 10sprivate_networks: []use_private_ptr_resolvers: truelocal_ptr_upstreams: []use_dns64: falsedns64_prefixes: []serve_http3: falseuse_http3_upstreams: falseserve_plain_dns: truehostsfile_enabled: truepending_requests:enabled: truetls:enabled: falseserver_name: ""force_https: falseport_https: 443port_dns_over_tls: 853port_dns_over_quic: 853port_dnscrypt: 0dnscrypt_config_file: ""allow_unencrypted_doh: falsecertificate_chain: ""private_key: ""certificate_path: ""private_key_path: ""strict_sni_check: falsequerylog:dir_path: ""ignored: []interval: 2160hsize_memory: 1000enabled: truefile_enabled: truestatistics:dir_path: ""ignored: []interval: 24henabled: truefilters:- enabled: trueurl: https://adguardteam.github.io/HostlistsRegistry/assets/filter_1.txtname: AdGuard DNS filterid: 1- enabled: trueurl: https://adguardteam.github.io/HostlistsRegistry/assets/filter_2.txtname: AdAway Default Blocklistid: 2- enabled: trueurl: https://adguardteam.github.io/HostlistsRegistry/assets/filter_51.txtname: HaGeZi's Pro++ Blocklistid: 1766894575- enabled: trueurl: https://adguardteam.github.io/HostlistsRegistry/assets/filter_5.txtname: OISD Blocklist Smallid: 1766894576- enabled: trueurl: https://adguardteam.github.io/HostlistsRegistry/assets/filter_27.txtname: OISD Blocklist Bigid: 1766894577- enabled: trueurl: https://adguardteam.github.io/HostlistsRegistry/assets/filter_61.txtname: HaGeZi's Samsung Tracker Blocklistid: 1766894578- enabled: trueurl: https://adguardteam.github.io/HostlistsRegistry/assets/filter_30.txtname: Phishing URL Blocklist (PhishTank and OpenPhish)id: 1769393156- enabled: trueurl: https://adguardteam.github.io/HostlistsRegistry/assets/filter_56.txtname: HaGeZi's The World's Most Abused TLDsid: 1769393157- enabled: trueurl: https://adguardteam.github.io/HostlistsRegistry/assets/filter_44.txtname: HaGeZi's Threat Intelligence Feedsid: 1769393158- enabled: trueurl: https://adguardteam.github.io/HostlistsRegistry/assets/filter_55.txtname: HaGeZi's Badware Hoster Blocklistid: 1769393159- enabled: trueurl: https://adguardteam.github.io/HostlistsRegistry/assets/filter_7.txtname: Perflyst and Dandelion Sprout's Smart-TV Blocklistid: 1773791901- enabled: trueurl: https://adguardteam.github.io/HostlistsRegistry/assets/filter_52.txtname: HaGeZi's Encrypted DNS/VPN/TOR/Proxy Bypassid: 1773791902whitelist_filters: []user_rules:- "@@||tailscale.com^"- "@@||tailscale.io^"- "@@||log.tailscale.io^"- "||*.church^$denyallow=host.church|life.church|online.church,badfilter"- ""dhcp:enabled: falseinterface_name: ""local_domain_name: landhcpv4:gateway_ip: ""subnet_mask: ""range_start: ""range_end: ""lease_duration: 86400icmp_timeout_msec: 1000options: []dhcpv6:range_start: ""lease_duration: 86400ra_slaac_only: falsera_allow_slaac: falsefiltering:blocking_ipv4: ""blocking_ipv6: ""blocked_services:schedule:time_zone: UTCids:- 4chan- 500px- 9gag- aliexpress- amino- betano- betfair- betway- bilibili- blaze- canais_globo- clubhouse- coolapk- crunchyroll- douban- kakaotalk- kik- kook- lazada- line- mail_ru- mercado_libre- odysee- ok- onlyfans- plenty_of_fish- shein- shopee- snapchat- temu- tiktok- tinder- wizz- xiaohongshu- zhihuprotection_disabled_until: nullsafe_search:enabled: truebing: trueduckduckgo: trueecosia: truegoogle: truepixabay: trueyandex: trueyoutube: trueblocking_mode: defaultparental_block_host: family-block.dns.adguard.comsafebrowsing_block_host: standard-block.dns.adguard.comrewrites:- domain: "*.{{BASE_SUB_DOMAIN_VAR}}"answer: "{{VIRTUAL_IP_VAR}}"enabled: truesafe_fs_patterns:- /opt/adguardhome/work/userfilters/*safebrowsing_cache_size: 1048576safesearch_cache_size: 1048576parental_cache_size: 1048576cache_time: 30filters_update_interval: 24blocked_response_ttl: 10filtering_enabled: truerewrites_enabled: trueparental_enabled: truesafebrowsing_enabled: trueprotection_enabled: trueclients:runtime_sources:whois: truearp: truerdns: truedhcp: truehosts: truepersistent: []log:enabled: truefile: ""max_backups: 0max_size: 100max_age: 3compress: falselocal_time: falseverbose: falseos:group: ""user: ""rlimit_nofile: 0schema_version: 32 -
keepalived.conf
Section titled “keepalived.conf”Terminal window nano ./keepalived/keepalived.conf./keepalived/keepalived.conf global_defs {enable_script_securityscript_user root}vrrp_script check_adguard {script "/usr/bin/curl -fsS http://{{SERVER_IP_VAR}}:8080/control/status"interval 2weight 2}vrrp_instance VI_1 {state MASTER # MASTER | BACKUPinterface enp3s0 # eth0 | enp3s0 | wlan0virtual_router_id 51priority 101 # MASTER: 101 | BACKUP: 100advert_int 1unicast_peer {# Add peers as necessary# 192.168.100.212 # peer 1# 192.168.100.213 # peer 2}authentication {auth_type PASSauth_pass password # Change max 8 characters}virtual_ipaddress {{{VIRTUAL_IP_VAR}}/24}track_script {check_adguard}} -
Miscellaneous (optional)
Section titled “Miscellaneous (optional)”Tailscale
Section titled “Tailscale”Setup TailscaleInstall and configure the Tailscale client to establish a secure mesh network and manage node connectivity.Resolve port 53 conflicts
Section titled “Resolve port 53 conflicts”If another service on the host is already using port 53, disable the systemd-resolved stub listener before starting AdGuard Home.
-
Disable the stub listener
Section titled “Disable the stub listener”Terminal window sudo nano /etc/systemd/resolved.confSet
DNSStubListener=no -
Restart systemd-resolved
Section titled “Restart systemd-resolved”Terminal window sudo systemctl restart systemd-resolved
Router DNS
Section titled “Router DNS”Update the Primary DNS, often located under LAN, DHCP or DNS Settings.
- Primary DNS:
{{SERVER_IP_VAR}}or{{VIRTUAL_IP_VAR}}(Your device’s Local LAN IP or virtual IP, not the Tailscale one). - Secondary DNS:
1.1.1.1(Fail-safe so your internet still works if your device is turned off).
-
-
Start services
Section titled “Start services”Create proxy network
Section titled “Create proxy network”Create the external Docker network used by Traefik, Pocket ID and the other proxy services.
Terminal window docker network create proxyadguard
Section titled “adguard”Start AdGuard Home first so its initial web setup can be completed before the remaining services depend on it.
Terminal window docker compose up -d adguardComplete web setup
Section titled “Complete web setup”- Go to http://localhost:3000 or http://{{SERVER_IP_VAR}}:3000
- Admin Web Interface.
- Listen interface: Select
All interfaces. - Port: Change to
8080.
adguardhome-sync
Section titled “adguardhome-sync”Once the origin and replica AdGuard Home instances are configured and reachable, start the sync service.
Terminal window docker compose up -d adguardhome-syncRUN_ON_STARTruns the first synchronization immediately, with subsequent synchronizations running every 10 minutes.pocket-id and traefik
Section titled “pocket-id and traefik”Start Pocket ID and Traefik after the proxy network and AdGuard Home are ready.
Terminal window docker compose up -d pocket-id traefikComplete web setup
Section titled “Complete web setup”- Open https://auth.{{BASE_SUB_DOMAIN_VAR}}/setup in your browser.
- Set up your admin account and register your primary Passkey.
- Go to Administration > OIDC Clients > Add OIDC Client:
- Name:
Traefik Forward Auth - Callback URL:
https://auth.{{BASE_SUB_DOMAIN_VAR}}/_oauth - Check
Public Clientif secret is not enforced, or copy theClient IDandClient Secret.
- Name:
- Paste those credentials into the
PROVIDERS_OIDC_CLIENT_IDandPROVIDERS_OIDC_CLIENT_SECRETenvironment variables in.env.
traefik-forward-auth
Section titled “traefik-forward-auth”Start the authentication middleware after the Pocket ID OIDC client has been configured.
Terminal window docker compose up -d traefik-forward-authkeepalived
Section titled “keepalived”Start keepalived after the DNS services and virtual IP configuration are ready.
Terminal window docker compose up -d keepalived - Go to http://localhost:3000 or http://{{SERVER_IP_VAR}}:3000