Type github.com into a browser and the page loads in well under a second.
Before any of it could load, something on this machine had to turn that name into an address, and that lookup went somewhere.
This post follows one question the whole way down: who saw it.
Every claim below is checked against a packet capture taken on the machine being described, read back in termshark, and every command is one you can run yourself.
What happens when you type a domain into a browser
A browser cannot open a connection to a name.
Connections are addressed by IP address, so before it sends a byte to github.com the browser has to turn that name into one.
That lookup is a DNS query, and on this machine it takes the same short path every time.
Two separate questions decide what that middle hop looks like: which resolver answers, and what protects the query on its way there. Confusing the two is the most common mistake about encrypted DNS, and the rest of this post is mostly about keeping them apart.
Plain DNS: one query to the router
dig sends a single DNS query and prints the whole answer.
Given a server with @, it asks that server directly, which makes it the right tool for asking a specific resolver a specific question.
A home router commonly answers DNS itself, from cache or by forwarding to whatever servers an internet service provider (ISP) handed it over Dynamic Host Configuration Protocol (DHCP). Asking it directly shows what a device on this network gets by default.
dig @192.168.100.1 github.com

Two fields carry the whole story.
SERVER names who answered and how: 192.168.100.1#53(192.168.100.1) (UDP), port 53, no encryption of any kind.
ANSWER SECTION carries the address, 20.207.73.82, with a time to live (TTL) of 46, which is how many seconds a resolver may reuse that answer before asking again.
MSG SIZE rcvd: 55 is worth remembering. Fifty-five bytes, sent in the clear.
Three jobs called "resolver"
The word "resolver" is used for three different machines doing three different jobs, and the rest of this post depends on telling them apart.
A stub resolver takes requests from applications on one machine and forwards them onward.
It resolves nothing itself.
On this machine that job belongs to systemd-resolved, listening on 127.0.0.53.
A recursive resolver does the real work: it answers from cache, or walks the DNS hierarchy until it has the answer.
Cloudflare's 1.1.1.1 and 1.0.0.1 are recursive resolvers.
An authoritative server holds the actual records for one zone, and is where a recursive resolver's chain of questions ends.
| Role | What it does | On this machine |
|---|---|---|
| Stub resolver | Forwards a request; resolves nothing itself | systemd-resolved at 127.0.0.53 |
| Recursive resolver | Resolves a name, from cache or by walking the hierarchy | Cloudflare at 1.1.1.1 and 1.0.0.1 |
| Authoritative server | Holds the records for one zone | github.com's own nameservers |
Changing the resolver: the same query, sent to Cloudflare
The usual advice is to stop using the network's own DNS and point at a public resolver instead. So do exactly that, and watch what changes.
dig @1.1.1.1 github.com

Two things changed and one did not.
The resolver changed, and so did the answer: 140.82.121.4 from Cloudflare against 20.207.73.82 from the router.
Both are real addresses for github.com; two resolvers simply had different entries cached at the moment they were asked.
The transport did not change at all.
1.1.1.1#53(1.1.1.1) (UDP), port 53, 55 bytes, exactly as readable on the wire as the first query was.
Key distinction
Changing the DNS resolver changes who receives the query. It does nothing to how that query travels. Those are two separate settings, and only one of them was just changed.
DNS over TLS: the same query inside an encrypted connection
DNS over TLS (DoT) carries an ordinary DNS query inside a Transport Layer Security (TLS) connection instead of a bare User Datagram Protocol (UDP) packet. The query and the answer are still DNS messages, unchanged. What changes is what carries them: TCP on port 853, with TLS in between.
Recent versions of dig speak it directly.
dig +tls -p 853 @1.1.1.1 github.com

SERVER now reads 1.1.1.1#853(1.1.1.1) (TLS).
Same resolver, same question, same kind of answer.
The size is the interesting part.
MSG SIZE rcvd: 468, against 55 for the identical query in plain text, and the reason is one line above it in the output: PADDING: (409 bytes).
An encrypted query still has a length, and a length is something an observer can measure even when the bytes are unreadable, so DoT responses are commonly padded to blunt exactly that.
That padding is a hint about what encryption does and does not cover here, and the rest of this post keeps pulling at it.
The packet stack under each transport
Nothing about DNS itself changed between those two queries. What changed is the stack of protocols underneath the DNS message:
Ethernet
IP
UDP
DNS
Query: github.com (readable)Ethernet
IP
TCP
TLS
DNS
Query: github.com (encrypted)Where the encryption starts, and where it stops
TLS protects the DNS message between two specific endpoints: the client that opened the connection, and the DoT server that answers it. Nothing before the first, nothing after the second.
Three roles, and it is worth being exact about each:
systemd-resolved is the stub resolver and the DoT client at once.
It takes the request from the application and, because DNS over TLS is switched on, opens the encrypted connection itself.
The application never negotiated any of it.
Cloudflare terminates that connection. It decrypts the DNS message and resolves the name, because no resolver, encrypted or otherwise, can answer a question it cannot read.
The ISP and every other network in between carry the packets. They are not participants in the TLS session, and do not hold the keys to it. They also do not encrypt anything on your behalf: the encryption was already there when the packets reached them.
The configuration that produces this
systemd-resolved's own view of its upstream state:
resolvectl status

That behaviour comes from one file:
grep -vE '^#|^$' /etc/systemd/resolved.conf | bat -l ini --style=plain --color=always

DNS= names the upstream resolvers, which is the who.
DNSOverTLS=yes is the how, and it is a separate line for the same reason the two are separate questions: remove it and the same servers are queried over plain UDP.
Applications never talk to any of this directly. They find the stub through one file:
bat -l ini --style=plain --color=always /etc/resolv.conf

nameserver 127.0.0.53 is loopback, reachable from this machine and nowhere else.
An application sends its query there, systemd-resolved answers on that address, and only then decides where the query goes upstream and how it is wrapped.
That indirection is the whole point: every application on the machine points at one fixed local address, and the real resolver, encrypted or not, can be changed underneath them without a single application knowing.
Misconception
"127.0.0.53 is Cloudflare." No. It is a loopback address on this machine where systemd-resolved listens. Cloudflare is upstream of it, and the application never addresses Cloudflare at all.
What a resolver that has never heard of DoT does with the connection
DNSOverTLS=yes says how to reach a resolver. It says nothing about whether the resolver on the other end can be reached that way.
A DNS server that has answered on UDP port 53 for a decade and knows nothing about DNS over TLS is extremely common: a home router handing itself out over Dynamic Host Configuration Protocol (DHCP) is usually exactly that. Here is one, answering plain DNS perfectly well and then being asked for the same name over TLS.
dig +noall +answer @192.0.2.60 example.com # port 53, answered
dig +tls -p 853 @192.0.2.60 example.com # port 853, refused
termshark -r dot-refused-853.pcapng

Every attempt is a TCP SYN to port 853, and every reply is a RST, ACK: a connection reset (RST), sent immediately, because nothing is listening there.
The same server answered a plain query on port 53 a moment earlier.
It is not broken and it is not refusing you; it simply has no DNS over TLS to offer.
A resolver configured with DNSOverTLS=yes meets that with a decision to make, and the decision is the interesting part.
resolvectl query wikipedia.org

That line is systemd-resolved reporting which path an answer came back on, and it is the one to check when you want to know whether a setting is doing anything.
Under yes, a server that cannot do DNS over TLS is not used at all: the resolver moves to one that can, and a name that no configured server can answer encrypted simply fails to resolve.
Under opportunistic, the same refusal is met by falling back to plain text, quietly, with no error anywhere and no line in any output to tell you it happened.
That is the whole difference between the two settings, and it only shows up at the moment something refuses.
The capture below is from a lab rather than from this machine's own network, built on ranges reserved for documentation: 192.0.2.0/24 from RFC 5737, and hardware addresses from the RFC 7042 block. The exchange in it is a real one, run against a real resolver; what it is not is a recording of somebody's house, which is why it can be shown whole.
The query name, in plain sight
dig reports what it did. A packet capture is the independent check on that report.
termshark is Wireshark's dissection engine driven from a terminal, reading the same capture file Wireshark would.
Here is a plain DNS query over UDP, with its DNS layer expanded:
dig @192.0.2.53 example.com
termshark -r dns-only.pcapng

Read the panes from the bottom up.
The hex dump on the right holds e xample.c m in readable text, because that is what is in the packet.
The detail pane above it walks the whole stack, Ethernet, Internet Protocol Version 4, User Datagram Protocol, Dst Port: 53, Domain Name System (query), and every one of those layers dissects cleanly because none of them is encrypted.
Nothing was broken to read this. The capture is a passive recording of bytes that crossed a link in the clear.
The capture below is from a lab rather than from this machine's own network, built on ranges reserved for documentation: 192.0.2.0/24 from RFC 5737, and hardware addresses from the RFC 7042 block. The exchange in it is a real one, run against a real resolver; what it is not is a recording of somebody's house, which is why it can be shown whole.
The same query, inside TLS
Now the same question asked over DoT, from the same lab, with the same tool, expanded to the same depth:
dig +tls -p 853 @192.0.2.53 example.com
termshark -r dot-only.pcapng

Everything structural is still visible.
The TCP handshake, the Client Hello, the Server Hello, the record layer, the size of every frame and the moment it was sent.
termshark even labels the record Application Data Protocol: Domain Name System, which is not decryption: port 853 is registered to DNS over TLS, so the dissector knows what protocol should be inside without being able to see any of it.
The hex pane is the difference. In the previous capture it held a hostname. Here it holds ciphertext, and no amount of dissection turns one into the other.
Misconception
"A capture tool cannot see DNS over TLS traffic." It sees the whole connection, as above: addresses, ports, handshake, timing, sizes. What it cannot do is decode a DNS message that was never sent in the clear.
What a passive observer can still read
Both captures were taken from the same position on the network, on the same interface, minutes apart. Switch between them and the list of what an observer can read barely moves:
- Source addressreadablereadable192.168.100.40, the machine that asked
- Destination addressreadablereadable1.1.1.1, the resolver being asked
- Transport and portreadablereadableUDP 53 in the first case, TCP 853 in the second
- Timing and packet sizesreadablereadablewhen the query was sent, and how large it was
- That this is DNS at allreadablereadableport 853 is registered to DNS over TLS and nothing else
- The name being looked upreadableencryptedgithub.com, in the clear, in the packet's own bytes
- The answer that came backreadableencrypted140.82.121.3, in the clear, in the response
Two rows change. Every other row is identical, and the destination address is the row that does the most work: an observer who knows 1.1.1.1 belongs to Cloudflare learns that this machine uses encrypted DNS, and which company answers its queries, without reading a single name.
DoT was never designed to hide that.
DNS over HTTPS: the same query inside an ordinary web request
DNS over HTTPS (DoH) is the other way to encrypt the same message. It carries the DNS query as the body of an HTTPS request rather than as its own protocol on its own port.
dig +https @1.1.1.1 github.com

SERVER reads 1.1.1.1#443(1.1.1.1) (HTTPS).
That port number is the entire practical difference from DoT.
A DoT connection is identifiable as DNS by its port even when its contents are sealed, which is what the capture above showed. A DoH connection to port 443 sits in the same column as every other HTTPS request the machine makes. Both encrypt the DNS message identically well; they differ in how loudly they announce that DNS is what they are carrying.
| Property | Plain DNS | DNS over TLS | DNS over HTTPS |
|---|---|---|---|
| Encryption | None | TLS | TLS |
| Typical transport | UDP | TCP | HTTPS, commonly HTTP/3 over Quick UDP Internet Connections (QUIC) |
| Typical port | 53 | 853 | 443 |
| Query readable by a passive observer | Yes | No | No |
| Identifiable as DNS on the wire | Yes | Yes, by its port | Not usually |
| Observed here | MSG SIZE 55 | MSG SIZE 468, padded | MSG SIZE 55 |
What DNS over TLS leaves exposed
Encryption in transit answers one question and leaves two others open.
The first is metadata, covered above: addresses, ports, timing and sizes all survive. The padding in the DoT response is a partial answer to the size half of that, and only a partial one.
The second is the resolver itself. Cloudflare decrypts every query this machine sends, because it has to in order to answer. Moving from a router that answers in plain text to a public resolver that answers over TLS does not remove a party who sees the queries; it changes which party that is, and moves the traffic out of reach of everyone in between. Whether that is an improvement depends entirely on who you would rather have the list.
The resolvectl status capture also read DNSSEC=no/unsupported.
Domain Name System Security Extensions (DNSSEC) authenticate that an answer was not altered on its way to you, which is a different property from confidentiality, provided by a different mechanism.
DoT hides the query. It does not vouch for the answer.
Misconception
"I changed my DNS to 1.1.1.1, so my DNS is encrypted." No. Two captures in this post used 1.1.1.1, and one of them was plain UDP on port 53, fully readable.
Misconception
"My ISP cannot see anything when I use DoT." The DNS message is sealed. The destination address, the port, the TLS handshake, the timing and the packet sizes are all still on the wire.
Misconception
"Cloudflare keeps my query encrypted through the rest of DNS." The TLS session runs between this machine and Cloudflare, and terminates there. What Cloudflare does afterwards is ordinary recursive resolution.
The core mental model: which resolver, and how the query travels
Two questions, two settings, and neither one answers the other.
DNS= decides who receives the query.
DNSOverTLS= decides what protects it on the way there.
A resolver can be trustworthy and still be sent questions in plain text; a query can be sealed end to end and still be read in full the moment it arrives at the resolver that has to answer it.
Everything in this post is reproducible in about ten minutes: set the two lines, run dig twice, capture with dumpcap, and read the result back in termshark.
The hex pane will tell you which of the two questions you actually answered.
