{"id":143,"date":"2024-05-03T23:24:02","date_gmt":"2024-05-03T18:24:02","guid":{"rendered":"https:\/\/blog.vicnunes.com\/?p=143"},"modified":"2024-05-06T05:46:14","modified_gmt":"2024-05-06T00:46:14","slug":"dns-over-https-fewer-ads","status":"publish","type":"post","link":"https:\/\/blog.vicnunes.com\/?p=143","title":{"rendered":"DNS over HTTPS &amp; Fewer Ads"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Lab fun with Docker, Pi-Hole, Nginx Proxy Manager, and Cloudflare Tunnels.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">My goal was to create a DNS over HTTPS Docker-compose stack that is portable and easy to deploy.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">With DNS over HTTPS (DoH), DNS queries and responses are encrypted and sent via the HTTP or HTTP\/2 protocols. DoH ensures that attackers cannot forge or alter DNS traffic. DoH uses port 443, which is the standard HTTPS traffic port, to wrap the DNS query in an HTTPS request. DNS queries and responses are camouflaged within other HTTPS traffic since it all comes and goes from the same port.<\/p>\n<cite><a href=\"https:\/\/developers.cloudflare.com\/1.1.1.1\/encryption\/dns-over-https\/\">https:\/\/developers.cloudflare.com\/1.1.1.1\/<\/a><a href=\"https:\/\/developers.cloudflare.com\/1.1.1.1\/encryption\/dns-over-https\/\" target=\"_blank\" rel=\"noreferrer noopener\">encryption<\/a><a href=\"https:\/\/developers.cloudflare.com\/1.1.1.1\/encryption\/dns-over-https\/\">\/dns-over-https\/<\/a><\/cite><\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">In today&#8217;s landscape, original ideas are scarce; I&#8217;ve heavily drawn from the exceptional work of others. Specifically:<\/p>\n\n\n\n<ul class=\"wp-block-list\" id=\"bkmrk-https%3A%2F%2Fmroach.com%2F2\">\n<li>James Turnland of YouTube&nbsp;<a href=\"https:\/\/www.youtube.com\/watch?v=8EpnaccHajo\" target=\"_blank\" rel=\"noreferrer noopener\">Jim&#8217;s Garage<\/a>&nbsp;fame showed me it works. &nbsp;Love your channel, Jim!<\/li>\n\n\n\n<li>Michael Roach&#8217;s post on <a href=\"https:\/\/mroach.com\/2020\/08\/pi-hole-and-cloudflared-with-docker\/\" data-type=\"link\" data-id=\"https:\/\/mroach.com\/2020\/08\/pi-hole-and-cloudflared-with-docker\/\" target=\"_blank\" rel=\"noreferrer noopener\">Pi-hole and cloudflared with Docker<\/a>, is so good that I find myself torn between admiration and the fear of inadvertently plagiarizing, my intention is solely to express sincere flattery.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Bits and Bobs<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Docker host<\/strong> &#8211; in my case an Ubuntu 22.04 server VM running on Proxmox\n<ul class=\"wp-block-list\">\n<li>Ubuntu port 53 binding fix<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Docker Compose<\/strong> for the control<\/li>\n\n\n\n<li><strong>Portainer<\/strong> for the visibility<\/li>\n\n\n\n<li><strong>Docker Macvlan<\/strong> for the networking flexibility<\/li>\n\n\n\n<li><strong>Services<\/strong>\n<ul class=\"wp-block-list\">\n<li><strong>Cloudflared<\/strong>\n<ul class=\"wp-block-list\">\n<li>Tunnel-up<\/li>\n\n\n\n<li>Tunnel-dns (proxy-dns)<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Pi-Hole<\/strong> as a DNS server and network-wide online ad protection.<\/li>\n\n\n\n<li><strong>Nginx Proxy Manager<\/strong> to secure local services with Let&#8217;s Encrypt certificates.<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">Configuration<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">If you choose to run Ubuntu server as I do, it normally blocks Port 53 which is crucial for DNS resolution.  The fix is to apply these commands at the CLI:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo sed -r -i.orig 's\/#?DNSStubListener=yes\/DNSStubListener=no\/g' \/etc\/systemd\/resolved.conf\n\nsudo sh -c 'rm \/etc\/resolv.conf &amp;&amp; ln -s \/run\/systemd\/resolve\/resolv.conf \/etc\/resolv.conf'\n\nsudo systemctl restart systemd-resolved <\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Normally, Docker services are confined to an internal network with specific ports exposed. But with the <a href=\"https:\/\/docs.docker.com\/network\/drivers\/macvlan\/\" data-type=\"link\" data-id=\"https:\/\/docs.docker.com\/network\/drivers\/macvlan\/\" target=\"_blank\" rel=\"noreferrer noopener\">Macvlan network driver<\/a>, one can provide an IP Address to each service. Both worked as expected &#8211; here&#8217;s the Macvlan option.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#---------------------------------------\n# Docker macvlan example\n# Must run this on Docker host prior to \n# deploying the stack\n#---------------------------------------\ndocker network create -d macvlan \\\n   --subnet=192.168.10.0\/24 \\\n   --gateway=192.168.10.1 \\\n   -o parent=eth0 doh_lan<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For portability, I elected to use a <a href=\"https:\/\/docs.docker.com\/compose\/environment-variables\/set-environment-variables\/\" data-type=\"link\" data-id=\"https:\/\/docs.docker.com\/compose\/environment-variables\/set-environment-variables\/\">Docker Compose <\/a><a href=\"https:\/\/docs.docker.com\/compose\/environment-variables\/set-environment-variables\/\" data-type=\"link\" data-id=\"https:\/\/docs.docker.com\/compose\/environment-variables\/set-environment-variables\/\" target=\"_blank\" rel=\"noreferrer noopener\">environment<\/a><a href=\"https:\/\/docs.docker.com\/compose\/environment-variables\/set-environment-variables\/\" data-type=\"link\" data-id=\"https:\/\/docs.docker.com\/compose\/environment-variables\/set-environment-variables\/\"> file<\/a>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>.env<\/strong><\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>\n#---------------------------\n# Docker-compose parameters\n#---------------------------\n# Common\nTZ=America_New_York\nPREFIX=onlan\n\n# Service: Cloudflared (part 1 and 2)\nCF1_ORDER=1 # numbers the services\nC1_NAME=tunnel-up\nTUNNEL_UP_IP:192.168.10.11\n\nCF2_ORDER=2\nC2_NAME=tunnel-dns\nCMD_TUNNEL='tunnel --no-autoupdate run --token &lt;YOUR CLOUDFLARED TOKEN HERE>'\nDNS_TUNNEL_IP=192.168.10.12\n\n# Service: PiHole\nPI_ORDER=3 \nPI_NAME=pihole\nWEB_PORT=80\nWEBPASSWORD=changeme\nDNS1=192.168.10.12#53\nDNS2=no\nPI_IP=192.168.10.13\n\n# Service (npm) nginx proxy manager\nNPM_ORDER=4 \nNPM_NAME=proxy\nNPM_IP=192.168.10.14\n\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>docker-compose.yml<\/strong><\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>\n#-------------------------------------------------------------------------\n# Docker Stack\n#--------------------------------------------------------------------------\n# Services:\n# 1. Cloudflared-tunnel\n# 2. Cloudflared-dns\n# 3. Pi-Hole\n# 4. NPM (Nginx Proxy Manager)\n#--------------------------------------------------------------------------\nversion: '3.9'\nservices:\n# Creates a connection to a Cloudflare Zero-Trust Tunnel\n  cloudflared-tunnel:\n    container_name: ${CF1_ORDER}-${PREFIX}-${C1_NAME}\n    command: ${CMD_TUNNEL}\n    image: 'cloudflare\/cloudflared:latest'\n    restart: unless-stopped\n    networks:\n      doh_lan:\n        ipv4_address: ${TUNNEL_UP_IP}\n\n# Layers on the Cloudflared proxy-dns command\n  cloudflared-dns:\n    container_name: ${CF2_ORDER}-${PREFIX}-${C2_NAME}\n    depends_on:\n      - \"cloudflared-tunnel\"\n    restart: unless-stopped\n    image: cloudflare\/cloudflared\n    depends_on:\n      - \"cloudflared-tunnel\"    \n    command: proxy-dns\n    environment:\n      - \"TUNNEL_DNS_UPSTREAM=https:\/\/1.1.1.1\/dns-query,https:\/\/1.0.0.1\/dns-query,https:\/\/9.9.9.9\/dns-query,https:\/\/149.112.112.9\/dns-query\"\n      - \"TUNNEL_METRICS=0.0.0.0:49312\"\n      - \"TUNNEL_DNS_ADDRESS=0.0.0.0\"\n      - \"TUNNEL_DNS_PORT=53\"\n    sysctls:\n      - net.ipv4.ip_unprivileged_port_start=53\n    networks:\n      doh_lan:\n        ipv4_address: ${DNS_TUNNEL_IP}\n\n  pihole:\n    image: pihole\/pihole\n    container_name: ${PI_ORDER}-${PREFIX}-${PI_NAME}\n    hostname: ${PREFIX}${PI_NAME}\n    restart: unless-stopped\n    depends_on:\n      - \"cloudflared-dns\"\n    environment:\n      - \"TZ=${TZ}\"\n      - \"DNS1=${DNS1}\"\n      - \"DNS2=${DNS2}\"\n      - \"DNSMASQ_LISTENING=all\"\n      - \"WEBPASSWORD=${WEBPASSWORD}\"\n      - \"WEB_PORT=${WEB_PORT}\"\n    volumes:\n      - '\/home\/docker\/pihole\/config:\/etc\/pihole\/'\n      - '\/home\/docker\/pihole\/dnsmasq:\/etc\/dnsmasq.d\/'\n    networks:\n      doh_lan:\n        ipv4_address: ${PI_IP}\n# Nginx Proxy Manager and Certificate Management\n  npm:\n    image: 'jc21\/nginx-proxy-manager:latest'\n    container_name: ${NPM_ORDER}-${PREFIX}-${NPM_NAME}\n    restart: unless-stopped\n    depends_on:\n      - \"pihole\"\n    ports:\n      - '80:80'\n      - '81:81'\n      - '443:443'\n    volumes:\n      - \/home\/docker\/npm\/data:\/data\n      - \/home\/docker\/npm\/letsencrypt:\/etc\/letsencrypt\n    networks:\n      doh_lan:\n        ipv4_address: ${NPM_IP}\n\nnetworks:\n  doh_lan:\n    external:\n      name: doh_lan\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Because of persistent issues in getting the Cloudflared service to consistently establish the tunnel, particularly after a Docker host reboot, I opted to run two instances: one for tunnel establishment (tunnel-up) and the other for executing the dns-proxy command. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Adapting this configuration is easy due to the parameters in the .env file. &nbsp;Notice how each service depends on the one before to control boot order.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Switching to another proxy server like <a href=\"https:\/\/doc.traefik.io\/traefik\/\" data-type=\"link\" data-id=\"https:\/\/doc.traefik.io\/traefik\/\" target=\"_blank\" rel=\"noreferrer noopener\">Traefik<\/a> or <a href=\"https:\/\/caddyserver.com\/\" data-type=\"link\" data-id=\"https:\/\/caddyserver.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">Caddy<\/a> should be straightforward.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Resulting view from Portainer:<\/strong><\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"460\" src=\"https:\/\/blog.vicnunes.com\/wp-content\/uploads\/2024\/05\/CleanShot-2024-05-03-at-12.56.49@2x-1024x460.png\" alt=\"\" class=\"wp-image-159\" srcset=\"https:\/\/blog.vicnunes.com\/wp-content\/uploads\/2024\/05\/CleanShot-2024-05-03-at-12.56.49@2x-1024x460.png 1024w, https:\/\/blog.vicnunes.com\/wp-content\/uploads\/2024\/05\/CleanShot-2024-05-03-at-12.56.49@2x-300x135.png 300w, https:\/\/blog.vicnunes.com\/wp-content\/uploads\/2024\/05\/CleanShot-2024-05-03-at-12.56.49@2x-768x345.png 768w, https:\/\/blog.vicnunes.com\/wp-content\/uploads\/2024\/05\/CleanShot-2024-05-03-at-12.56.49@2x-601x270.png 601w, https:\/\/blog.vicnunes.com\/wp-content\/uploads\/2024\/05\/CleanShot-2024-05-03-at-12.56.49@2x.png 1470w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Beyond being an ad-blocker,&nbsp;<a href=\"https:\/\/pi-hole.net\/\" target=\"_blank\" rel=\"noreferrer noopener\">Pi-hole<\/a>&nbsp;is my favorite DNS server and has been rock-solid here for years.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Hope this helps you as much as it was fun for me. Feel free to comment.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Lab fun with Docker, Pi-Hole, Nginx Proxy Manager, and Cloudflare Tunnels. My goal was to create a DNS over HTTPS Docker-compose stack that is portable and easy to deploy. With DNS over HTTPS (DoH), DNS queries and responses are encrypted and sent via the HTTP or HTTP\/2 protocols. DoH ensures that attackers cannot forge or alter DNS traffic. DoH uses port 443, which is the standard HTTPS traffic port, to wrap the DNS query in an HTTPS request. DNS queries&#8230;<\/p>\n<p class=\"read-more\"><a class=\"btn btn-default\" href=\"https:\/\/blog.vicnunes.com\/?p=143\"> Read More<span class=\"screen-reader-text\">  Read More<\/span><\/a><\/p>\n","protected":false},"author":2,"featured_media":169,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5],"tags":[],"class_list":["post-143","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-lab"],"_links":{"self":[{"href":"https:\/\/blog.vicnunes.com\/index.php?rest_route=\/wp\/v2\/posts\/143","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.vicnunes.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.vicnunes.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.vicnunes.com\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.vicnunes.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=143"}],"version-history":[{"count":25,"href":"https:\/\/blog.vicnunes.com\/index.php?rest_route=\/wp\/v2\/posts\/143\/revisions"}],"predecessor-version":[{"id":175,"href":"https:\/\/blog.vicnunes.com\/index.php?rest_route=\/wp\/v2\/posts\/143\/revisions\/175"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.vicnunes.com\/index.php?rest_route=\/wp\/v2\/media\/169"}],"wp:attachment":[{"href":"https:\/\/blog.vicnunes.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=143"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.vicnunes.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=143"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.vicnunes.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=143"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}