We will install, configure and autoupdate of dnscrypt-proxy, which will be set as our only resolver in the system. Configure special instance of Chrome to connect to captive portals - which are unsafe and impossible to access through dnscrypt-proxy. It will be also helpfull when we fail to configure dnscrypt-proxy properly.

Firefox has an option to use ESNI1 but only if DOH2 is enabled as well in Firefox, we would like a system wide solution, that’s why we will setup local doh server using dnscrypt-proxy. We will also install locally trusted certificates to make local doh server https available.

Captive portal

We are going to install separate instance of Google Chrome used only for logging into captive portals.

First we need to export paths to bash/zsh (~/.bashrc or ~/.zshrc).

#go runtime
export GOPATH=$HOME/go
export PATH=$PATH:/usr/local/go/bin:$GOPATH/bin

Then follow the instructions

We should have binary file in ~/go/bin/captive-browser and we are gonna use a captive portal whenever we are connecting to the airport paywall network.

Extra (create a .desktop file)

We will create a .desktop file to easily run captive-browser from GNOME search bar

captive-browser.desktop content:

[Desktop Entry]
Name=Captive Browser
GenericName=Non Safe Web Browser

We will put the file inside $HOME/.local/share/applications/


When we search for the file in GNOME, there’s a chance that we will not find Captive Browser. In that case we will have to give a full hard path to captive-browser for ex. /home/yourhomedir/go/bin/captive-browser From what I found it’s all because XDG_DATA_DIRS, but even when I added path export XDG_DATA_DIRS=$XDG_DATA_DIRS:$HOME/go/bin it was finding it GNOME but could run it. Somehow when I symlinked ln -s $HOME/go/bin/captive-browser $HOME/bin it works even though $HOME/bin is not in $XDG_DATA_DIRS. Suuper weird.

Local certificates for localhost - mkcert is a simple tool for making locally-trusted development certificates. It requires no configuration. It is used to connect to our localhost by https

go get -u

Now we should have mkcert executable in ~/go/bin


cd ~/go/bin
./mkcert -install -key-file localhost-key.pem -cert-file localhost.pem localhost

We just installed our certificates. We should have generated two files localhost-key.pem and localhost.pem in ~/go/bin. We are going to need them later.

Side-note: to uninstall

./mkcert -uninstall -key-file localhost-key.pem -cert-file localhost.pem localhost


Version in the ubuntu repository is old and there are some configuration gimmicks. That’s why I recommend to install it from the source

Get and install dnscrypt-proxy

Download latest release from github. dnscrypt-proxy-linux_x86_64-*.tar.gz This is the one most people want.


tar xvzf ./dnscrypt-proxy-linux_x86_64-*.tar.gz
cd ./dnscrypt-proxy-linux_x86_64-*/linux-x86_64
sudo mkdir /usr/local/dnscrypt-proxy
sudo mv ./dnscrypt-proxy /usr/local/dnscrypt-proxy
sudo ln -s /usr/local/dnscrypt-proxy/dnscrypt-proxy /usr/local/bin
# We are going to delete self signed certficate
# because we are going to use two certs that
# we had generated before which doesn't rise any error in browsers
sudo rm ./localhost.pem
sudo mkdir /etc/dnscrypt-proxy
sudo cp * /etc/dnscrypt-proxy

Now it’s time to move our previously generated ceritficates.

cd ~/go/bin
mv ./localhost.pem /etc/dnscrypt-proxy
mv ./localhost-key.pem /etc/dnscrypt-proxy

Configure dnscrypt-proxy

Here is mine /etc/dnscrypt-proxy/dnscrypt-proxy.toml config file - link to gist

Check especially the line that starts with server_names - those are the servers we want to connect, we can leave that empty, dnscrypt will find the fastest server.

From documentation:

## If this line is commented, all registered servers matching the require_* filters
## will be used.

We can choose which protocol and what server we want to connect here:

Switch off current DNS resolver

From now on we can have no internet connection, until we setup properly dnscrypt-proxy

Check what is listening on port 53 ss -lp 'sport = :domain' or command sudo netstat -lnptu

Now we are going to disable system resolver:

systemctl stop systemd-resolved
systemctl disable systemd-resolved

Remove dnsmasq - it is no longer needed because all functions are included in dnscrypt-proxy. sudo apt remove --purge dnsmasq

To disable dnsmasq for NetworkManager, make the /etc/NetworkManager/NetworkManager.conf mine looks like this:

# Stops overwriting /etc/resolv.conf by NetworkManager
# because we use dnscrypt-proxy
# Need to comment this line because of use dnscrypt-proxy


# Random mac address
# should be already default

Setting up /etc/resolv.conf4

Now the most problematic issue. Because we want to make sure that no matter which connection we are going to use, WLAN (different networks) or LAN we always want to have dnscrypt-proxy working. The trouble is that many system apps/daemons are trying to modify /etc/resolv.conf

For example:

In short, it’s overcomplicated. For now, we disabled systemd-resolved, and NetworkManager so they are out of the game. There are many ways for this solution, and personally I’m not completly satisfied from any options below.

Option 1 (preffered)

Remove resolvconf and lock /etc/resolv.conf from modyfing (by anything that is trying to alter it) by setting a file atrribute

apt-get remove resolvconf
cp /etc/resolv.conf /etc/resolv.conf.backup
rm -f /etc/resolv.conf

And create a new /etc/resolv.conf.override file with the following content. That will be our helper file for other scripts

# Created for dnscrypt-proxy
# This is a content of /etc/resolv.conf.override
options edns0

Then, let’s just copy it on right place

cp /etc/resolv.conf.override /etc/resolv.conf
Extra step

If any other program, service, daemon, you name it, changes /etc/resolv.conf. Let’s set a sticky bit Immutable on file by command:

chattr +i /etc/resolv.conf

Side note: To unlock it:

chattr -i /etc/resolv.conf

Option 2

We will use resolvconf and put a trigger script for NetworkManager to use our configuration

Firstly we need to make sure that resolvconf is installed.

Now we are going to create file /etc/resolv.conf.override

# Created for dnscrypt-proxy
# This is a content of /etc/resolv.conf.override
options edns0

then we are going to create a script /etc/NetworkManager/dispatcher.d/20-resolv-conf-override for NetworkManager with following content

cp -f /etc/resolv.conf.override /run/resolvconf/resolv.conf

and then we’re going to create a symlink for it to execute

cd /etc/NetworkManager/dispatcher.d 
ln -s /etc/NetworkManager/dispatcher.d/20-resolv-conf-override pre-up.d/

From now, whenever we are connecting through NetworkManger, script is going to override /etc/resolv.conf with our own configuration using resolvconf

Option 3 - Always use systemd-resolved (

Rest of article doesn’t comply with Option 3

Modify file /etc/systemd/resolved.conf In section [Resolve] add:


then we are going to symlink our systemd-resolved. This is mode 1 in systemd-resolved documentation

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

Setup dnscrypt-proxy

Check if everything is working

cd /etc/dnscrypt-proxy
dnscrypt-proxy -resolve

Check if DNS are resolved

$ dig | grep SERVER

If we have this, we are fine to go further

Install dnscrypt-proxy as service (daemon)

Run the command

cd /etc/dnscrypt-proxy
sudo dnscrypt-proxy -service install

To uninstall

sudo dnscrypt-proxy -service uninstall

Now that it’s installed, it can be started:

cd /etc/dnscrypt-proxy
dnscrypt-proxy -service start


Setup Firefox TTR

Firefox has an option to use ESNI1 bot only if built-in Firefox DOH2 is also switched on. That’s why we need to make local DOH server. Let’s instruct Firefox to use our local DOH server.


To edit hidden options in Firefox, type about:config in address bar.

Learn more about TRR preferences, here also., true
network.trr.early-AAAA, true
network.trr.mode, 3
network.trr.wait-for-A-and-AAAA, false
network.trr.wait-for-portal, false

We are going to disable the captive portal too. I don’t know why, but just disabling captive portal still connects to https://https// because of that we will change those 3 options:

captivedetect.canonicalURL, ""
network.captive-portal-service.enabled, false
network.connectivity-service.enabled, false

Restart browser and check if everything is correct. In Firefox address bar type about:networking Go to tab: DNS. Check TRR values. They all need to be set to true.

Final check

Check if ESNI is working - last three checks should be green.

Check DNS leaks

Automatic updates of dnscrypt-proxy

Following automatic update guide

Inside folder /usr/local/dnscrypt-proxy create a file named with following content - see gist

Then execute

cd /usr/local/dnscrypt-proxy
sudo chmod +x ./
sudo ln -s /usr/local/dnscrypt-proxy/ /usr/local/bin/dnscrypt-proxy-update

Check if everything is working


Now we are going to add this to cron to run it regularly.

sudo crontab -e

add following line

0 */12 * * * /usr/local/dnscrypt-proxy/

crontab guru says our script is going to be executed “At minute 0 past every 12th hour.”

Fast way to toggle dnscrypt off/on

Only for Option 1 install of dnscrypt-proxy

toggle off dnscrypt-proxy - promiscous_dns

Inside ~/bin we will create a file promiscous_dns with following content


text="# Created by ~/bin/promiscous_dns"
cp -f /var/run/NetworkManager/resolv.conf /etc/resolv.conf
sed -i "1i$text" /etc/resolv.conf

To toggle on dnscrypt-proxy - secure_dns

Inside ~/bin we will create a file secure_dns with following content


text="# Created by ~/bin/secure_dns"
cp -f /etc/resolv.conf.override /etc/resolv.conf
sed -i "1i$text" /etc/resolv.conf


If VPN is being used, should we use dnscrypt-proxy or not? I think we should disable it. The question is what’s really happening when we connect through VPN? Are our queries sent by dnscrypt-proxy or VPN?

While we have our /etc/resolv.conf hardcoded, and NetworkManger is instructed not to modify resolv.conf, we should replace our DNS for VPN ones, after connecting to VPN with command sudo ~/bin/promiscous_dns or do it automatically by adding a script to NetworkManager

Running a script after connection to VPN

Script will automatically change for VPN DNS after connecting:

Let’s create a script in /etc/NetworkManager/dispatcher.d' named 99-vpn-resolve`


text='# Modified by /etc/NetworkManager/dispatcher.d/99-vpn-resolve'

if [ "$2" = "vpn-up" ]; then
        cp -f /var/run/NetworkManager/resolv.conf  /etc/resolv.conf
        # Adding first line
        sed -i "1i$text" /etc/resolv.conf

if [ "$2" = "vpn-down" ]; then
        cp -f /etc/resolv.conf.override /etc/resolv.conf
        # Adding first line
        sed -i "1i$text" /etc/resolv.conf

Last thing, we have to make it executable chmod +x /etc/NetworkManager/dispatcher.d/99-vpn-resolve

It should work from now on. We can check it by running VPN and test by

Extra: user.js and reload of Firefox with proper settings

Because Firefox doesn’t care about /etc/resolv.conf while we use ESNI and DOH in Firefox it’s easy to forget that Firefox still uses own settings. When we connect to VPN, we will switch off dnscrypt-proxy and restart Firefox. Let’s create two files in your Firefox profile:

Content of

// Preferences for TTR switched ON
user_pref("", true);
// boostrapAddress no longer necessary
// user_pref("network.trr.bootstrapAddress", "");
user_pref("network.trr.custom_uri", "");
user_pref("network.trr.early-AAAA", true);
user_pref("network.trr.mode", 3);
user_pref("network.trr.uri", "");
user_pref("network.trr.wait-for-A-and-AAAA", false);
user_pref("network.trr.wait-for-portal", false);
// Disable captive portal
user_pref("network.captive-portal-service.enabled", false);
user_pref("captivedetect.canonicalURL", "");
user_pref("network.connectivity-service.enabled", false);

Content of user.js.unsecure:

// We just need to reset this option
network.trr.mode, 0

Content of our modified /etc/NetworkManager/dispatcher.d/99-vpn-resolve: Just modify path to Firefox profile


text='# Modified by /etc/NetworkManager/dispatcher.d/99-vpn-resolve'

if [ "$2" = "vpn-up" ]; then
	cp -f /var/run/NetworkManager/resolv.conf  /etc/resolv.conf
	# Adding first line
	sed -i "1i$text" /etc/resolv.conf
	# Stop dnscrypt-proxy
	dnscrypt-proxy -service stop
	# Reset settings and close Firefox
	cp "$firefoxpath"user.js.unsecure "$firefoxpath"user.js
	pgrep firefox
	if [ $? -eq 0 ]; then
		pkill -SIGTERM firefox
	# TODO: how to run it again?

if [ "$2" = "vpn-down" ]; then
	cp -f /etc/resolv.conf.override /etc/resolv.conf
	# Adding first line
	sed -i "1i$text" /etc/resolv.conf
	# Start dnscrypt-proxy
	dnscrypt-proxy -config /etc/dnscrypt-proxy -service stop
	# Reset settings and close Firefox
	pkill firefox
	cp "$firefoxpath" "$firefoxpath"user.js
	pgrep firefox
	if [ $? -eq 0 ]; then
		pkill -SIGTERM firefox
	# TODO: how to run it again?


  • Check by wireshark that all DNS queries are encrypted - basically it’s true, because if we stop dnscrypt-proxy daemon we cannot resolve any address, but it’s worth checking anyway, for working ESNI for example

Further reading:

  1. ESNI - Encrypted Server Name Indication - in future is going to be replaced by ECH, Firefox and cloudflare implements v1 of internet draft more: on cloudflare and firefox, check if you encrypt SNI  2

  2. DoH - DNS over HTTPS, one of the solutions to encrypt DNS, other is DoT (DNS over TLS) which is internet standards but it’s easier to block by internet providers,  2

  3. dnscrypt-proxy there are few installation guides, ex. for Linux and for Ubuntu, for Ubuntu I still recommend to follow general linux guide. 

  4. resolv.conf configuration - if something still overwrites yours resolv.conf check this article