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
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
Get it from here: https://gist.github.com/bruvv/4b1283fa7902447b3d2ae69481ea8ffa
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
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
(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
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
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
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
ldconfig
apt remove software-properties-common build-essential
apt autoremove
apt autoclean
reboot
Read extra nginx examples: https://github.com/TuxInvader/nginx-dns
Hello,
Thank you for the write up, but I'm running into some issues.
After completing the entire guide, when I navigate to
dns.domain.com
, I'm greeted by the Pi-hole landing page. Is that expected? I ask because, I thought the purpose ofdnsadmin.domain.com
is for accessing the Pi-hole UI? Additionally, when I go todns.domain.com
and click the "Did you mean to go to the admin panel", I get an error stating:Needless to say, when putting
dns.domain.com
into my Android at this point, it gives the common "Couldn't connect" to the private DNS.My second issue is that the certificiate for my
dnsadmin.domain.com
is not valid. When investaging the certificate in the browser, it shows that the certificate is invalid because it's issued fordns.domain.com
and notdnsadmin.domain.com
. I'm curious if that's due to needing to put thednsadmin.domain.com
certificate information in/etc/nginx/streams/dns-over-tls
. The guide mentions putting thedns.domain.com
certification information there.Any ideas would be greatly appreciated.