DNS over TLS (DOT) for Android 9+

If you guys would like to run your own Pihole with DoT, so you can block ads on your Android, here is sample script you can follow:

Note: Use at your own risk.



# ===============================================================================
sudo su

apt-get update && apt-get upgrade -y && apt-get autoremove -y

# Install necessary packages
apt-get install vim nginx certbot resolvconf python-certbot-nginx python3-certbot-dns-cloudflare dialog php7.2-fpm php7.2-zip php-sqlite3 iptables-persistent -y

# (Optional) Install Amazon EC2 Instant Connect
apt-get install ec2-instance-connect

# ===============================================================================
# Create NGINX server block for pihole web interface
# Replace pi.domain.com with your domain

echo "server {
        listen 80;
        listen [::]:80;
        root /var/www/html;
        server_name pi.domain.com;
        autoindex off;
        index pihole/index.php index.php index.html index.htm;
        location / {
                return 301 \$scheme://\$host/admin;
        }
        location ~ \.php$ {
                include snippets/fastcgi-php.conf;
                fastcgi_pass unix:/run/php/php7.2-fpm.sock;
        }
        location /*.js {
                index pihole/index.js;
        }
        location /admin {
                root /var/www/html;
                index index.php index.html index.htm;
        }
        location ~ /\.ht {
                deny all;
        }
}" >> /etc/nginx/sites-available/pihole
# ===============================================================================
# Create Symbolic link for the site

ln -s /etc/nginx/sites-available/pihole /etc/nginx/sites-enabled/pihole 
# ===============================================================================
# Verify configuration and reload NGINX

systemctl reload nginx
# ===============================================================================
# Cloudflare API credentials for certificate verification

echo "dns_cloudflare_email = XXXXXXXXX@XXXXX.XXXXX
dns_cloudflare_api_key = XXXXXXXXXXXXXXXXXX" >> ./cloudflare.ini
# ===============================================================================
# Change permission for cloudfare credentials file

chmod 400 ./cloudflare.ini
# ===============================================================================
# Generate certificate request using Certbot

certbot -i nginx -m admin@domain.com --dns-cloudflare-credentials ./cloudflare.ini --agree-tos --no-eff-email -d *.domain.com
# ===============================================================================
# Install Pihole Server

curl -sSL https://install.pi-hole.net | bash
# ===============================================================================
# Create NGINX Streams directory

mkdir /etc/nginx/streams/
# ===============================================================================
# Create new server block for DNS-Over-TLS Server Block
# We will be requesting the cert later in the stage

echo "upstream dns-servers {
  server [::1]:53;
  server 127.0.0.1:53;
}
server {
  listen 853 ssl;
  listen [::]:853 ssl;
  ssl_certificate /etc/letsencrypt/live/domain.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/domain.com/privkey.pem;
  ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
  ssl_protocols            TLSv1.2 TLSv1.3;
  ssl_ciphers              HIGH:!aNULL:!MD5;
  ssl_handshake_timeout    10s;
  ssl_session_cache        shared:SSL:20m;
  ssl_session_timeout      4h;
  proxy_ssl off;
  #proxy_bind \$remote_addr transparent;
  proxy_pass dns-servers;
}


" >> /etc/nginx/streams/dns-over-tls
# ===============================================================================
# Enable SSL Stapling

echo "ssl_stapling on;
ssl_stapling_verify on;
resolver [::1] 127.0.0.1;" >> /etc/nginx/conf.d/stapling.conf
# ===============================================================================
# Create new NGINX streams directory

echo "stream {
        include /etc/nginx/streams/*;
}" >> /etc/nginx/nginx.conf
# ===============================================================================
# Enable NXDOMAIN response for blocked domains

echo "BLOCKINGMODE=NXDOMAIN" >> /etc/pihole/pihole-FTL.conf
# ===============================================================================
# Restart NGINX and Pihole Server

service nginx restart
systemctl restart pihole-FTL
# ===============================================================================
# Reset Pihole Web Interface password

pihole -a -p
# ===============================================================================
# Verify if DNS-Over-TLS is working or not

# ===============================================================================
# ===============================================================================
# Enabling NGINX Transparency Proxy
# https://www.nginx.com/blog/ip-transparency-direct-server-return-nginx-plus-transparent-proxy/
# ===============================================================================
# ===============================================================================
# Edit DNS-Over-TLS server block at /etc/nginx/streams/dns-over-tls

# Replace upstream IP with Server's IPv6 and IPv4 Interface IP
  server [XXXX:XXXX::XXXX]:53;
  server X.X.X.X:53;

# Add the NGINX Proxy Bind to the server block
  proxy_bind $remote_addr transparent;
# ===============================================================================
# Run NGINX as Root, instead of www-data

sed -i 's/www-data/root/' /etc/nginx/nginx.conf
# ===============================================================================
# Add IPtables marking rules to tag DNS response packets

ip6tables -t mangle -A OUTPUT -p tcp --sport 53 -j MARK --set-xmark 7
iptables -t mangle -A OUTPUT -p tcp --sport 53 -j MARK --set-xmark 7
# ===============================================================================
# Add IP rules to divert DNS response packets to NGINX

# IPv6
ip -6 rule add fwmark 7 lookup 99
ip -6 route add local ::/0 dev lo table 99

# IPv4
ip rule add fwmark 7 lookup 99
ip route add local 0.0.0.0/0 dev lo table 99
# ===============================================================================
# Restart NGINX service

service nginx restart
# ===============================================================================
# Save IP6tables & IPtables rules

ip6tables-save > /etc/iptables/rules.v6
iptables-save > /etc/iptables/rules.v4
# ===============================================================================
# Ignore localhost queries

echo "IGNORE_LOCALHOST=yes" >> /etc/pihole/pihole-FTL.conf
service pihole-FTL restart
# ===============================================================================
# Verify DNS-Over-TLS is working

# ===============================================================================
# Troubleshooting Commands

netstat -tulpane                              # Connection Tracking
pihole -r                                     # Reset/Reconfigure Pihole Server after IP Change
pihole tail                                   # Pihole logs
ip6tables -t mangle                           # IPtables IPv6 Mangle Table
ip6tables -t mangle                           # IPtables IPv4 Mangle Table
tcpdump -n -i any tcp port 853 -w dump.pcap   # TCPDump command to verify DNS-Over-TLS packets
# ===============================================================================

I recommend running this in your VPC. And, don’t bother on VPNs. VPNs are useless and cause extra battery drain.

Mod Edit: External link removed and script contents added to post.

Fair warning to any users considering running the above script:

It looks ok from a quick glance, but just make sure you understand what it is doing before running it

2 Likes

The script is wrong in some places.

# ===============================================================================
# Enable NXDOMAIN response for blocked domains

echo "BLOCKINGMODE=NXDOMAIN" | sudo tee /etc/pihole/pihole-FTL.conf

That will add an additional line to the config file and that is not correct. If you want to change the mode then you need to sed in changes to the existing mode configuration line. If it doesn’t exist then you add it.

Same as with

echo "IGNORE_LOCALHOST=yes" >> /etc/pihole/pihole-FTL.conf

I had a quick look and if I am correct the response from Pi-hole, source port 53, is not going to the source address provided, but the transparent proxy who send the request.

I find it rather complex as a whole and looking at Unbound it has DoT downstream build in.

I one does not need to know the IP address of the client that makes the request then Stunnel is a easy start. This breaks the new group implementation in Pi-hole 5.0 Beta.

Another good guide:

I loosely followed this when I set one up for access from my phone when out and about. Though I ended up reimplementing it with traefik, I found it really good for helping me understand what I needed to do in order to achieve it.

The implementation by Ben Tasker is not transparent and the one above is, which is much searched for.

But rather complicated. But supporting Pi-hole 5.0 groups.

Thanks. I added a note as well.

I corrected my post. Thanks for the advise.

I have some other corrections in the other thread. The current example above won’t work because of the “\” in this line:

proxy_bind \$remote_addr transparent;

Many thanks for sharing this with us. This will make it possible to provide DoT internally and externally.
DoH is also possible and I have strong feelings how this is pushed to be used. I see it solely as last resort if your ISP provider or your government is hindering or even make it impossible to have some freedom!
First talk to the one blocking your freedom and so can change their minds.

This method supports the Groups feature in the coming version of Pi-hole. This also first and mainly a service for internal clients or yourself, outside your internal network.

Several configurations are possible depending on your situation.
The focuspiont is on DoT (port: 853/TCP) and not on DoH which is using HTTPS (port: 443/TCP).