DoH/DoT DNS Server
How to create your own DoH/DoT DNS Server running on Docker.
Download and install Docker
sudo apt-get update
for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin docker-compose
Create Let’s Encrypt Certificate
sudo apt-get update
sudo apt-get -y install certbot
sudo certbot certonly --no-eff-emai --agree-tos --email <your email> --standalone --non-interactive --domain <your domain>
cp /etc/letsencrypt/live/<your domain>/privkey.pem .
cp /etc/letsencrypt/live/<your domain>/fullchain.pem .
Unbound Configuration
vim unbound.conf
Inside, paste the following script:
server:
username: "unbound"
chroot: "/etc/unbound"
directory: /etc/unbound/
pidfile: "/var/run/unbound.pid"
logfile: "/var/log/unbound.log"
log-local-actions: yes
log-queries: yes
log-replies: yes
log-servfail: yes
verbosity: 1
log-queries: yes
num-threads: 4
so-rcvbuf: 2m
verbosity: 2
interface: 0.0.0.0@443
do-ip4: yes
do-tcp: yes
do-ip6: yes
access-control: 0.0.0.0/0 allow
tls-service-key: "/etc/unbound/privkey.pem"
tls-service-pem: "/etc/unbound/fullchain.pem"
minimal-responses: yes
cache-min-ttl: 0
cache-max-ttl: 86400
hide-identity: yes
hide-version: yes
hide-trustanchor: yes
minimal-responses: yes
prefetch: yes
prefetch-key: yes
qname-minimisation: yes
incoming-num-tcp: 4096
ratelimit: 1000
num-queries-per-thread: 4096
rrset-roundrobin: yes
use-caps-for-id: yes
aggressive-nsec: yes
edns-buffer-size: 1472
harden-glue: yes
harden-short-bufsize: yes
harden-large-queries: yes
harden-algo-downgrade: yes
harden-dnssec-stripped: yes
harden-below-nxdomain: yes
harden-referral-path: yes
do-not-query-localhost: no
statistics-cumulative: yes
extended-statistics: yes
val-clean-additional: yes
ip-dscp: 18
private-address: 10.0.0.0/8
private-address: 172.16.0.0/12
private-address: 192.168.0.0/16
private-address: fd00::/8
private-address: 169.254.0.0/16
private-address: fe80::/10
private-address: 127.0.0.0/8
private-address: ::1/128
private-address: ::ffff:0:0/96
forward-zone:
name: "."
forward-tls-upstream: yes
forward-no-cache: no
forward-addr: 8.8.8.8@853
forward-addr: 8.8.4.4@853
forward-addr: 1.1.1.1@853
forward-addr: 1.0.0.1@853
forward-addr: 9.9.9.9@853
remote-control:
control-enable: no
CMD Script
vim unbound.sh
#!/bin/bash
/usr/sbin/unbound -d -c /etc/unbound/unbound.conf
Dockerfile
vim Dockerfile
Inside, paste the following script:
FROM ubuntu:24.04
ENV DEBIAN_FRONTEND=noninteractive
ENV UNBOUND_VERSION=1.22.0
ENV UNBOUND_SHA256=c5dd1bdef5d5685b2cedb749158dd152c52d44f65529a34ac15cd88d4b1b3d43
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN set -x \
&& apt update \
&& apt -y upgrade \
&& apt -y install build-essential net-tools libnghttp2-dev libssl-dev libexpat-dev libsodium-dev libevent-dev openssl wget knot-dnsutils \
&& groupadd -g 88 unbound \
&& useradd -c "Unbound DNS resolver" -d /var/lib/unbound -u 88 -g unbound -s /bin/false unbound \
&& wget http://www.unbound.net/downloads/unbound-${UNBOUND_VERSION}.tar.gz \
&& echo "${UNBOUND_SHA256} unbound-${UNBOUND_VERSION}".tar.gz | sha256sum --check \
&& tar -xvf unbound* \
&& cd unbound-1.*/ \
&& ./configure --with-libnghttp2 --with-libevent --enable-dnscrypt --prefix=/usr --sysconfdir=/etc --disable-static --with-pidfile=/run/unbound.pid \
&& make \
&& make install \
&& mv -v /usr/sbin/unbound-host /usr/bin/ \
&& unbound-anchor /etc/unbound/root.key ; true\
&& unbound-control-setup \
&& unbound-checkconf \
&& wget https://www.internic.net/domain/named.root -qO- | tee /etc/unbound/root.hints \
&& rm -rf /unbound-${UNBOUND_VERSION}.tar.gz \
&& rm -rf unbound-* \
&& rm -rf /var/lib/apt/lists/* \
&& apt-get clean
COPY ./unbound.conf /etc/unbound/unbound.conf
COPY ./privkey.pem /etc/unbound/privkey.pem
COPY ./fullchain.pem /etc/unbound/fullchain.pem
COPY ./unbound.sh /unbound.sh
WORKDIR /etc/unbound/
EXPOSE 443
HEALTHCHECK CMD netstat -an | grep 443 > /dev/null; if [ 0 != $? ]; then exit 1; fi;
CMD ["/unbound.sh"]
Build Image
docker build -t unbound-tls:1.22.0 .
Docker Compose
vim docker-compose.yml
Inside, paste the following script:
services:
unbound-tls:
image: unbound-tls:1.22.0
container_name: unbound-tls
restart: always
ports:
- "443:443"
Run image
docker compose up -d