Skip to content

DNS Security & Privacy

💡
Before you start

You need a Linux machine (or WSL on Windows) and two small tools: dig (from dnsutils on Debian/Ubuntu, bind-utils on Fedora) and dnsmasq, a tiny DNS server. Install both with sudo apt install dnsutils dnsmasq. Check dig is there with dig -v.

The first four steps run a private practice resolver on a high port (5354) that answers only from a file you write. It never touches your system’s real DNS settings and needs no internet. On the machine that produced the output below, the lab ran inside a throwaway network sandbox because the build environment blocks outbound DNS; on your own computer the same commands run directly, and the numbers you see will match.

How DNS Works

The Domain Name System (DNS) translates human-readable domain names (like finkatana.com) into IP addresses (like 78.111.67.116) that computers use to communicate. Every time you visit a website, a DNS query happens first.

The lookup chain works like this:

1
Your device checks its local DNS cache for a stored answer
2
If not cached, the query goes to your configured DNS resolver (usually your ISP's server)
3
The resolver queries root servers, TLD servers, and authoritative servers to find the answer
4
The IP address is returned to your device and cached for future use

Why DNS is a Privacy Risk

Traditional DNS has a critical privacy problem: queries are sent in plain text.

  • Your ISP can see every website you visit via DNS queries
  • Network operators (on public WiFi, corporate networks) can log your DNS traffic
  • Attackers can intercept and modify DNS responses (DNS spoofing/hijacking)
  • Governments can compel ISPs to provide DNS logs of your browsing history
⚠️
Even with HTTPS, DNS leaks your browsing

HTTPS encrypts the content of your connection, but the DNS query revealing which domain you are visiting is sent before the HTTPS connection is established.

Encrypted DNS: DoH and DoT

DNS over HTTPS (DoH) Sends DNS queries over HTTPS (port 443). Hard to block since it uses the same port as regular web traffic.
DNS over TLS (DoT) Sends DNS queries over TLS (port 853). Easier to identify and block, but is a dedicated standard.

Both prevent your ISP and network operators from seeing your DNS queries.

Recommended DNS Providers

Cloudflare (1.1.1.1) Fast, privacy-focused. Audited by KPMG. Supports DoH and DoT.
Quad9 (9.9.9.9) Non-profit, blocks known malicious domains. Swiss-based (strong privacy laws).
Google (8.8.8.8) Reliable and fast, but Google logs some data. Less private than alternatives.

Configuring DNS

On Your Router (Recommended)

Changing DNS on your router protects all devices on the network:

  • Log into your router admin panel
  • Find DNS settings (often under WAN, Internet, or DHCP settings)
  • Replace the ISP DNS with your preferred provider (e.g., 1.1.1.1 and 1.0.0.1)

On Linux

# Temporary (resets on reboot):
sudo resolvectl dns eth0 1.1.1.1 1.0.0.1

# Check current DNS:
resolvectl status

In Firefox (DoH)

Firefox supports DoH natively:

  • Go to Settings > Privacy & Security > scroll to "DNS over HTTPS"
  • Select "Max Protection" and choose Cloudflare or a custom provider

DNS Leak Testing

After changing your DNS, verify it is working by visiting a DNS leak test site. The results should show your chosen DNS provider, not your ISP.

Now Do It Yourself: See DNS — and DNS Attacks — in Five Steps

DNS turns a name you can remember into an address a computer can reach. That lookup is also the single most trusted step in everything you do online, which makes it a favourite thing to attack. You will run your own tiny resolver, watch it answer a lookup, read every part of that answer, see what happens when a name does not exist, and then — the whole point — watch a poisoned resolver hand back an attacker’s address for a name you trust. Every line of output below was produced by running these exact commands.

1
Run a practice resolver and ask it one question

Go: open a terminal in a folder you can write to.

Do: write a one-line lookup table, start dnsmasq answering only from it on port 5354, then ask it for example.test. (If dnsmasq prints a permission error, put sudo in front of it — it still touches nothing but its own port.)

printf '93.184.216.34 example.test\n' > myzone
dnsmasq --no-daemon --no-resolv --no-hosts --addn-hosts=myzone --listen-address=127.0.0.1 --port=5354 --bind-interfaces &
dig @127.0.0.1 -p 5354 example.test A +short

You should see: the address your table gave, and nothing else:

93.184.216.34

That is DNS in one line: you asked a resolver for a name, it returned an address. Stop the server later with kill %1.

If not: failed to bind... Address already in use means port 5354 is taken — change both the --port and the -p to 5355. If dig prints connection refused, the server did not start; scroll up for the dnsmasq error, usually a typo in the long flag line.

2
Read the full answer, not just the address

Go: same terminal, server still running.

Do: drop +short and ask for the answer plus the summary lines.

dig @127.0.0.1 -p 5354 example.test A +noall +answer +stats

You should see: the record in full, then who answered and how long it took:

example.test.		0	IN	A	93.184.216.34
;; Query time: 0 msec
;; SERVER: 127.0.0.1#5354(127.0.0.1) (UDP)

Read it left to right: the name, the TTL (how many seconds this answer may be cached — 0 here means do not cache), the class IN, the type A, and the address. The SERVER: line names which resolver replied — remember that line; step 4 is about trusting it.

If not: a real public resolver returns a large TTL like 3600 instead of 0; that is caching at work and is normal. The shape of the line is the constant.

3
Ask for a name that does not exist

Go: same terminal.

Do: look up a name your table never defined, and read the status line.

dig @127.0.0.1 -p 5354 doesnotexist.test A +noall +comments

You should see: no answer, and a status that is not NOERROR — this practice server replies REFUSED because it has no record and no upstream to ask:

;; ->>HEADER<<- opcode: QUERY, status: REFUSED, id: 4532
;; flags: qr rd ra; QUERY: 1, ANSWER: 0

On the real internet you would instead see NXDOMAIN — “this name does not exist”. Either way the lesson is that a resolver can say no, and your software must handle that, not assume every lookup succeeds.

If not: if you get an address here, you left the wrong name in myzone — only example.test should resolve.

4
Watch a poisoned resolver lie — the whole reason DNS security exists

Go: same terminal. Stop the honest server first: kill %1.

Do: imagine an attacker who controls whichever resolver answers you. They write the same name pointing at their server, start it, and you ask the same question you trusted before.

printf '203.0.113.66 example.test\n' > evilzone
dnsmasq --no-daemon --no-resolv --no-hosts --addn-hosts=evilzone --listen-address=127.0.0.1 --port=5354 --bind-interfaces &
dig @127.0.0.1 -p 5354 example.test A +short

You should see: a completely different address for the name you never changed:

203.0.113.66

Nothing in the question changed — only who answered. That is DNS spoofing and cache poisoning in miniature: whoever controls the reply controls where example.test sends your traffic, and your browser shows the name you typed while connecting to the attacker’s server. This is why the defences matter: DNSSEC signs answers so a forged one is rejected, and DoH/DoT encrypt the query so a machine on the path cannot see it or swap the reply.

If not: if you still get 93.184.216.34, the old server is still running — run kill %1 (or kill %2) until dig shows the new address, or the poisoned server never took the port.

5
Do it for real, then encrypt it

Go: the same terminal, now querying the real internet on your own machine. Stop the practice server first (kill %1).

Do: look up a real domain, then read the SERVER: line to see which resolver you actually use — and that your query left in plaintext.

dig example.com A +noall +answer +comments

You should see: a real answer with a real TTL, and a SERVER: line naming your resolver — often your router (192.168.1.1#53) or your ISP. Port 53 is unencrypted: every domain you resolve is visible to your network and your ISP.

If not: no answer at all usually means you are offline. To fix the privacy problem this step exposes, turn on encrypted DNS: in Firefox, Settings → Privacy & Security → Enable DNS over HTTPS; in Chrome, Settings → Privacy and security → Use secure DNS. After that, your lookups travel over HTTPS and the machines between you and the resolver can no longer read or rewrite them.

🎉
Check yourself before moving on

Without scrolling up: in step 4 the question example.test never changed, yet the address came back different. What changed, and which two defences would have stopped it? Answer: the resolver answering the query changed — a poisoned or attacker-run resolver replied instead of the honest one. DNSSEC would have made the forged answer fail its signature check, and DoH/DoT would have encrypted the query end-to-end so a man in the middle could neither read it nor substitute a reply.

Now do it without the page: point your myzone file at a second name of your own — say 203.0.113.9 shop.test on its own line — restart the practice resolver, and confirm with dig that shop.test resolves while a name you did not list still comes back REFUSED. You have just run an authoritative resolver for your own zone.

Summary

In this tutorial, you learned:

  • How DNS works and the lookup chain
  • Why traditional DNS is a privacy risk
  • Encrypted DNS options: DoH and DoT
  • Trusted DNS providers to use
  • How to configure DNS on your router, Linux, and Firefox
🎉
Your DNS queries are now private!

Switching to an encrypted DNS provider is one of the simplest and most effective privacy improvements you can make.