Skip to content

Instantly share code, notes, and snippets.

@alsyundawy
Forked from bruvv/PIHOLE+UNBOUND.md
Created January 29, 2024 23:46
Show Gist options
  • Save alsyundawy/b444831b9eb1bbaba0de0d805dff944a to your computer and use it in GitHub Desktop.
Save alsyundawy/b444831b9eb1bbaba0de0d805dff944a to your computer and use it in GitHub Desktop.
Setup Pihole + Unbound + DNS over TLS on ubuntu 20.02 LTS

Swap

First enable swap just incase

sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
sudo cp /etc/fstab /etc/fstab.bak
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
sudo sysctl vm.swappiness=10
sudo sysctl vm.vfs_cache_pressure=50

Also add it to:

nano /etc/sysctl.conf

vm.swappiness = 10
vm.vfs_cache_pressure = 50

Update / Upgrade

apt-get update
apt-get -y install software-properties-common build-essential dialog rsyslog apt-utils



#sudo LC_ALL=C.UTF-8 add-apt-repository ppa:ondrej/php
#apt-get update

apt-get -y full-upgrade

dpkg-reconfigure tzdata

apt-get install -y curl net-tools make wget php-fpm php-sqlite3 php-zip git man-db nano iptables-persistent nginx dnsutils python3-certbot-nginx libevent-dev libssl-dev libexpat1-dev cron python-pyinotify

firewall

Get it from here: https://gist.github.com/bruvv/4b1283fa7902447b3d2ae69481ea8ffa

Lets setup UNBOUND:

mkdir ~/build-unbound
cd ~/build-unbound
wget http://www.unbound.net/downloads/unbound-latest.tar.gz
tar -xvf unbound*
cd unbound-1.*/

useradd -r unbound

./configure \
        --with-pthreads \
        --with-username=unbound \
        --with-ssl \
        --with-libevent \
        --enable-tfo-server \
        --enable-tfo-client \
        --enable-event-api \
	--enable-subnet

make && make install

ln -s /usr/local/etc/unbound/ /etc/unbound
ldconfig

mkdir /usr/local/etc/unbound/trust
#mkdir -p /etc/unbound/unbound.conf.d
chown unbound /usr/local/etc/unbound/trust

sudo -u unbound unbound-control-setup -d /usr/local/etc/unbound/trust/
sudo -u unbound unbound-anchor -a /usr/local/etc/unbound/trust/root.key

wget -O /etc/unbound/trust/root.hints https://www.internic.net/domain/named.root
cat << EOM > /lib/systemd/system/unbound.service
[Unit]
Description=Unbound DNS server
Documentation=man:unbound(8)
After=network.target
Before=nss-lookup.target
Wants=nss-lookup.target

[Service]
Type=simple
Restart=on-failure
EnvironmentFile=-/etc/default/unbound
ExecStartPre=/usr/local/sbin/unbound-anchor -a /usr/local/etc/unbound/trust/root.key
ExecStart=/usr/local/sbin/unbound -c /usr/local/etc/unbound/unbound.conf -d
ExecReload=/usr/local/sbin/unbound-control reload

[Install]
WantedBy=multi-user.target
EOM

systemctl daemon-reload

cat << EOM > /etc/cron.monthly/update-unbound-hints.sh
#!/bin/bash
wget -q https://www.internic.net/domain/named.root -O /tmp/root.hints
if grep -q ROOT-SERVERS /tmp/root.hints ;then
  mv -f /tmp/root.hints /usr/local/etc/unbound/trust/root.hints ; chmod a+r /usr/local/etc/unbound/trust/root.hints ; chown unbound:unbound /etc/unbound/trust/*
fi
EOM

chmod a+x /etc/cron.monthly/update-unbound-hints.sh
/etc/cron.monthly/update-unbound-hints.sh
rm /usr/local/etc/unbound/unbound.conf
wget -O /etc/unbound/unbound.conf https://gist.githubusercontent.com/bruvv/41577f18732b2bfb9ab18fe0581b588a/raw/7ebcd72f14b132ff90a35efeb06b18c3f04b7289/unbound.conf
systemctl daemon-reload
systemctl unmask unbound
systemctl enable unbound
systemctl restart unbound

test unbound

netstat -lnp | grep unbound

dig pi-hole.net @127.0.0.1 -p 5353
dig sigfail.verteiltesysteme.net @127.0.0.1 -p 5353
dig sigok.verteiltesysteme.net @127.0.0.1 -p 5353

service unbound status

Setup NGINX

We're going to set rate limits just in case the public gain access

cat << EOM > /etc/nginx/conf.d/00-rate-limits.conf
limit_req_zone \$binary_remote_addr zone=doh_limit:10m rate=300r/s;
EOM

Create the config - remember to replace server_name with whatever name you are using

cat << EOM > /etc/nginx/sites-available/dns-over-https
upstream dns-backend {
    server 127.0.0.1:53;
    keepalive 30;
}
server {
        listen 80;
        server_name dns.domain.com;
        root /tmp/NOEXIST;
        location /dns-query {
                limit_req zone=doh_limit burst=50 nodelay;
                proxy_set_header X-Real-IP \$remote_addr;
                proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
                proxy_set_header Host \$http_host;
                proxy_set_header X-NginX-Proxy true;
                proxy_http_version 1.1;
                proxy_set_header Upgrade \$http_upgrade;
                proxy_set_header Connection "";
                proxy_redirect off;
                proxy_set_header X-Forwarded-Proto \$scheme;
                proxy_read_timeout 86400;
                proxy_pass http://dns-backend/dns-query;
        }   
}
EOM

sudo ln -s /etc/nginx/sites-available/dns-over-https /etc/nginx/sites-enabled/dns-over-https
sudo nginx -t
sudo systemctl reload nginx

Lets check if nginx is running correctly: netstat -lnp | grep 80

tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      4890/nginx: master     

Lets setup stapeling

cat << EOM > /etc/nginx/conf.d/00-cert-stapling.conf
ssl_stapling on;
ssl_stapling_verify on;
resolver 127.0.0.1:5353;
EOM

certbot --nginx -d dns.domain.com

cat << EOM > /etc/letsencrypt/options-ssl-nginx.conf
# This file contains important security parameters. If you modify this file
# manually, Certbot will be unable to automatically provide future security
# updates. Instead, Certbot will print and log an error message with a path to
# the up-to-date file that you will need to refer to when manually updating
# this file.
ssl_session_cache shared:le_nginx_SSL:1m;
ssl_session_timeout 1440m;
ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
# Enable modern TLS cipher suites
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
# The order of cipher suites matters
ssl_prefer_server_ciphers on;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
EOM

Verify the next file: nano /etc/nginx/sites-available/dns-over-https

change the next line add http2 listen 443 ssl http2; # managed by Certbot CHANGE THIS

systemctl reload nginx

Setup Pi-Hole

(When prompted, do not install Pi-hole default firewall rules, make a note of the admin password when it's provided)

DNS custom 127.0.0.1#5353 DO NOT INSTALL THE WEBSERVER we use nginx.

curl -sSL https://install.pi-hole.net | bash

Change the password: sudo pihole -a -p CHANGEME

Fix the nginx.conf file:

mv /etc/nginx/nginx.conf /etc/nginx/nginx.bak

cat << EOM >  /etc/nginx/nginx.conf
#user root;
#user  nginx;
user www-data;
worker_processes auto;

#load_module modules/ngx_stream_js_module.so;

error_log  /var/log/nginx/error.log;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    #sendfile    on;
    #tcp_nopush  on;

    keepalive_timeout  65;

    gzip  on;

    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/security.conf;
}
# DNS Stream Services
stream {

  # DNS logging
  log_format  dns   '$remote_addr [$time_local] $protocol "$dns_qname"';
  access_log /var/log/nginx/dns-access.log dns;

  # Include the NJS module
  #js_include /etc/nginx/njs.d/nginx_stream.js;

  # The $dns_qname variable can be populated by preread calls, and can be used for DNS routing
  #js_set $dns_qname dns_get_qname;

  # DNS upstream pool.
  upstream dns {
    zone dns 64k;
    server 127.0.0.1:53;
  }
  upstream dot {
    zone dns 64k;
    server 10.0.0.30:53;
  }
  include /etc/nginx/streams/*.conf;
}
EOM
cat << EOM >  /etc/nginx/security.conf
# security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
EOM
cat << EOM >  /etc/nginx/conf.d/pihole.conf
server {
        root /var/www/html;
	listen 80;
        server_name dnsadmin.domain.com;
        autoindex off;
        index pihole/index.php index.php index.html index.htm;
        access_log /var/log/nginx/pihole.access.log;

        location / {
                expires max;
                try_files $uri $uri/ =404;
        }

	location ~ \.php$ {
		include fastcgi_params;
		fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name;
		fastcgi_pass unix:/run/php/php7.4-fpm.sock;
		fastcgi_param FQDN true;
		auth_basic "Restricted"; # For Basic Auth
		auth_basic_user_file /etc/nginx/.htpasswd; # For Basic Auth
	}

        location /*.js {
                index pihole/index.js;
                auth_basic "Restricted"; #For Basic Auth
                auth_basic_user_file /etc/nginx/.htpasswd;  #For Basic Auth
        }

        location /admin {
                root /var/www/html;
                index index.php index.html index.htm;
                auth_basic "Restricted"; #For Basic Auth
                auth_basic_user_file /etc/nginx/.htpasswd;  #For Basic Auth
        }

        location ~ /\.ht {
                deny all;
        }

        error_page 404 /pihole/index.php;
}
EOM
sudo nginx -t
sudo systemctl reload nginx

Verify that PiHole has the correct ip:

ifconfig|grep broad|awk -F' ' '{print $2}'

Check that that exists in: nano /etc/pihole/setupVars.conf

Lets configure FTL:

nano /etc/pihole/pihole-FTL.conf
PRIVACYLEVEL=0
DBINTERVAL=60
RESOLVE_IPV4=yes
BLOCKINGMODE=NULL
IGNORE_LOCALHOST=yes

Setup security: https://www.htaccesstools.com/htpasswd-generator/ put it in: /etc/nginx/.htpasswd

certbot --nginx -d dnsadmin.domain.com

nano /etc/nginx/conf.d/pihole.conf change the next line add http2 listen 443 ssl http2; # managed by Certbot CHANGE THIS

systemctl restart nginx

mkdir /etc/nginx/streams/

Now get from the DoH NGINX’s site configuration file the path to your HTTPS key and certificate.

cat << EOM > /etc/nginx/streams/dns-over-tls
stream {
	upstream dns-servers {
	  zone dns 64k;
	  server XX.XX.XX.XX:53; #PIHOLE IP can be found with: netstat -lnp | grep 53 DO NOT USE 127.0.0.1 or LOCALHOST
	}
	server {
	  listen 853 ssl; # managed by Certbot
	  proxy_bind $remote_addr transparent;
	  proxy_pass dns-servers;
	  ssl_preread on;


	  ssl_certificate /etc/letsencrypt/live/dns.domain.com/fullchain.pem; # managed by Certbot
	  ssl_certificate_key /etc/letsencrypt/live/dns.domain.com/privkey.pem; # managed by Certbot
	  ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
	  ssl_protocols        TLSv1.2 TLSv1.3;

	  #ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-$
	  ssl_prefer_server_ciphers       on;
	  ssl_handshake_timeout           10s;
	  ssl_session_cache               shared:DoT:10m;
	  ssl_session_timeout             1d;
	  ssl_session_tickets             off;
	}
}
EOM

nano /etc/nginx/nginx.conf

include /etc/nginx/streams/*.conf;

sed -i ‘s/www-data/root/’ /etc/nginx/nginx.conf

usermod -a -G pihole www-data

Make sure you disable dns cache from pihole: nano /etc/dnsmasq.d/01-pihole.conf

service pihole-FTL restart
sudo systemctl restart nginx

setup FAIL2BAN

apt-get install fail2ban
cat << EOM > /etc/fail2ban/filter.d/pihole-dns.conf
 Fail2Ban configuration file
[INCLUDES]
# Read common prefixes. If any customizations available -- read them from
# common.local
before = common.conf
[Definition]
_daemon = dnsmasq
# log example from /var/log/pihole.log
#Feb 26 04:41:28 dnsmasq[1887]: query[A] 21cl93vlx5n9p.aikoaiko.net from 67.21.36.3
#(?:DAY )?MON Day 24hour:Minute:Second(?:\.Microseconds)?(?: Year)?
# This will filter all 'query' requests.
failregex = query\[.*\].* from <HOST>$
	    query\[ANY\].* from <HOST>$
	    query\[.*\] version\.bind from <HOST>$
	    query\[.*\] isc\.org from <HOST>$
	    query\[.*\] .*shadowserver.* from <HOST>$
	    query\[.*\] .*shodan.io from <HOST>$
	    reply <HOST> is no-reverse-dns-configured\.com$
	    reply <HOST> is .*shodan\.io$
	    reply <HOST> is .*arbor-observatory.*$
	    reply <HOST> is .*stretchoid\.com$
	    reply <HOST> is .*openportstats\.com$
	    reply <HOST> is .*hostwindsdns\.com$
	    reply <HOST> is .*poneytelecom\.eu$

ignoreregex =
EOM
cat << EOM > /etc/fail2ban/jail.d/nginx.conf
[nginx-auth]
enabled = true
filter = nginx-auth
action = iptables-multiport[name=NoAuthFailures, port="http,https"]
logpath = /var/log/nginx/*error*.log

[nginx-login]
enabled = false
filter = nginx-login
action = iptables-multiport[name=NoLoginFailures, port="http,https"]
logpath = /var/log/nginx/*access*.log
 
[nginx-badbots]
enabled  = true
filter = apache-badbots
action = iptables-multiport[name=BadBots, port="http,https"]
logpath = /var/log/nginx/*access*.log
maxretry = 1
 
[nginx-proxy]
enabled = true
action = iptables-multiport[name=NoProxy, port="http,https"]
filter = nginx-proxy
logpath = /var/log/nginx/*access*.log
maxretry = 0

[nginx-dos]
enabled  = true
port     = http
filter   = nginx-dos
logpath  = /var/log/nginx/*access*.log
findtime = 120
maxretry = 200
EOM
cat << EOM > /etc/fail2ban/jail.d/pihole-dns.conf
[pihole-dns]
enabled = true
port     = 53
action   = %(banaction)s[name=%(__name__)s-tcp, port="%(port)s", protocol="tcp", chain="%(chain)s", actname=%(banaction)s-tcp]
           %(banaction)s[name=%(__name__)s-udp, port="%(port)s", protocol="udp", chain="%(chain)s", actname=%(banaction)s-udp]
logpath = /var/log/pihole.log
findtime = 60
maxretry = 5
# bantime = 3600
EOM
cat << EOM > /etc/fail2ban/jail.d/ssh.conf
[sshd]
enabled = true
#port = 22
filter = sshd
#logpath = /var/log/auth.log
#maxretry = 3
EOM
systemctl start fail2ban
systemctl enable fail2ban

to check if fail2ban works fail2ban-client status pihole-dns

Test your DNS: https://rootcanary.org/test.html

Secure server

sudo nano /etc/sysctl.conf Change / add this:

# IP Spoofing protection
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1

# Ignore ICMP broadcast requests
net.ipv4.icmp_echo_ignore_broadcasts = 1

# Disable source packet routing
net.ipv4.conf.all.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0 
net.ipv4.conf.default.accept_source_route = 0
net.ipv6.conf.default.accept_source_route = 0

# Ignore send redirects
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0

# Block SYN attacks
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_syn_backlog = 2048
net.ipv4.tcp_synack_retries = 2
net.ipv4.tcp_syn_retries = 5

# Log Martians
net.ipv4.conf.all.log_martians = 1
net.ipv4.icmp_ignore_bogus_error_responses = 1

# Ignore ICMP redirects
net.ipv4.conf.all.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0 
net.ipv6.conf.default.accept_redirects = 0

# Ignore Directed pings
net.ipv4.icmp_echo_ignore_all = 1

Secure SSH

cat << EOM > /etc/ssh/sshd_config.d/secure.conf
Protocol 2
PermitRootLogin no
DebianBanner no
EOM

Persistance bans for fail2ban:

touch /etc/fail2ban/ip.blocklist

nano /etc/fail2ban/jail.conf

Find:

bantime = X

Change it to:

bantime = -1

Edit action file:

nano /etc/fail2ban/action.d/iptables-multiport.conf

Add this to actionstart:

cat /etc/fail2ban/ip.blocklist | while read IP; do iptables -I f2b-<name> 1 -s $IP -j DROP; done

Add this to actionban:

echo <ip> >> /etc/fail2ban/ip.blocklist

Fix resolv

sudo systemctl stop systemd-resolved
sudo nano /etc/systemd/resolved.conf

Add this:

[Resolve]
DNS=127.0.0.1
FallbackDNS=9.9.9.9
#Domains=
#LLMNR=no
#MulticastDNS=no
#DNSSEC=no
#Cache=yes
DNSStubListener=no

And restart:

sudo ln -sf /run/systemd/resolve/resolv.conf /etc/resolv.conf

sudo systemctl restart systemd-resolved

Clean stuff:

ldconfig
apt remove software-properties-common build-essential
apt autoremove
apt autoclean
reboot

Read extra nginx examples: https://github.com/TuxInvader/nginx-dns

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment