Type something to search...
Why Does DNS Use Port 53, and When Does It Switch from UDP to TCP?

Why Does DNS Use Port 53, and When Does It Switch from UDP to TCP?

A firewall rule that allows "DNS" on UDP port 53 and nothing else looks perfectly reasonable — and it will work for months. Then one day DNSSEC-signed lookups for certain domains start failing, a secondary nameserver stops receiving zone updates, or a large TXT record mysteriously can't be resolved. The cause is almost always the same: DNS doesn't only run over UDP. It also needs TCP on port 53, and blocking it breaks things in ways that are easy to misdiagnose as common DNS errors with other causes.

This article explains why DNS lives on port 53, why it prefers UDP, the specific situations where it switches to TCP, how to watch that switch happen with dig and a few lines of Python, and how to configure firewalls so you don't break it. Encrypted transports like DoH and DoT use different ports, which are covered briefly at the end.

Why Port 53?

Port 53 is DNS's well-known port, registered with IANA under the service name domain for both UDP and TCP. You can see the assignment on almost any Unix-like system:

grep -E "^domain\s" /etc/services
domain           53/udp     # Domain Name Server
domain           53/tcp     # Domain Name Server

The number itself has no deep technical meaning. Ports below 1024 were assigned to core services as they were defined in the early internet — 23 for Telnet, 25 for SMTP, 53 for the Domain Name System when it was specified in the early 1980s. RFC 1035, the 1987 specification that still defines the DNS wire format, states that servers listen on port 53 for both UDP and TCP.

What matters is that the port is fixed and well known. A resolver anywhere in the world has to reach any authoritative server without prior negotiation, so every DNS server listens on the same port. Clients, by contrast, send from a random high-numbered source port, which turns out to be important for security.

Why DNS Prefers UDP

Most DNS exchanges are one small question and one small answer, often under 100 bytes each. UDP is a perfect fit:

  • No handshake. A UDP query is a single packet out and a single packet back. TCP needs a three-way handshake before any data moves, adding a full round trip to every lookup — and a recursive resolver may perform several lookups to answer one query.
  • No connection state. An authoritative server handling hundreds of thousands of queries per second doesn't have to track open connections, which keeps memory use low and throughput high.
  • Simple retry logic. If a UDP packet is lost, the client just asks again after a short timeout, often to a different server.

The cost is that UDP offers no delivery guarantee, no ordering, and no protection against large messages being fragmented or dropped. DNS works around those limits by keeping messages small and falling back to TCP when they aren't.

The 512-Byte Limit and EDNS

The original DNS specification limited UDP messages to 512 bytes. That was a safe size that any host on the early internet was guaranteed to accept without fragmentation problems. Anything larger had to go over TCP.

Modern DNS routinely needs more: DNSSEC signatures, IPv6 glue, long TXT records for SPF and domain verification, and large NS sets all push responses past 512 bytes. EDNS (Extension Mechanisms for DNS) lets a client advertise that it can accept larger UDP responses. Today most clients and resolvers advertise 1232 bytes — a value chosen to avoid IP fragmentation on virtually all paths. The EDNS post covers the mechanism in detail; for this article, the point is that EDNS raises the ceiling but doesn't remove it.

When DNS Switches to TCP

There are four common situations where DNS uses TCP on port 53.

1. The response is truncated

When an answer won't fit within the client's advertised UDP size, the server sends what it can and sets the TC (truncated) bit in the header. That's a signal to the client: "this is incomplete, ask me again over TCP." The client opens a TCP connection to port 53, repeats the query, and gets the full response.

You can watch this with dig. The root zone's DNSKEY set with signatures is larger than 512 bytes, so limiting the buffer forces truncation. First, tell dig to ignore the TC bit so you can see the truncated response:

dig +ignore +bufsize=512 +dnssec +norecurse . DNSKEY @a.root-servers.net
;; flags: qr aa tc; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; MSG SIZE  rcvd: 303

The tc flag is set and only one of the DNSKEY records made it into a 303-byte reply. Now drop +ignore and let dig behave like a normal client:

dig +bufsize=512 +dnssec +norecurse . DNSKEY @a.root-servers.net
;; Truncated, retrying in TCP mode.
;; flags: qr aa; QUERY: 1, ANSWER: 5, AUTHORITY: 0, ADDITIONAL: 1
;; MSG SIZE  rcvd: 1414

dig saw the TC bit, retried over TCP, and received the complete 1414-byte answer with all the keys and signatures. If TCP port 53 were blocked between you and the server, that retry would time out and the lookup would fail.

2. Zone transfers

Copying a whole zone from a primary to a secondary server — AXFR for a full transfer, IXFR for incremental — always uses TCP. Zones can be megabytes in size and need reliable, ordered delivery. The zone transfer post explains AXFR and IXFR; from a networking point of view, the key fact is that a secondary nameserver needs outbound TCP 53 to its primary, and the primary needs to accept it.

dig +tcp AXFR example.com @ns1.example.com

This requests a full transfer over TCP. Most servers refuse AXFR from unauthorized addresses, which is the correct default.

3. The client chooses TCP

Clients may use TCP for any query. Some resolvers switch to TCP after repeated UDP timeouts, some do it when they've seen that a particular server truncates often, and some prefer TCP as a defense against spoofing. You can force it with dig +tcp:

dig +tcp example.com A @1.1.1.1

4. Encrypted and persistent connections

DNS over TLS runs over TCP (on port 853), and DNS over HTTPS runs over HTTPS (port 443) on TCP or QUIC. These aren't port 53, but they share the same stream-based framing. Persistent TCP connections let a client send many queries without repeating the handshake, which narrows the performance gap with UDP.

TCP Support Is Mandatory, Not Optional

For years, some operators treated TCP as an optional extra for zone transfers only. That's no longer acceptable. RFC 7766 (2016) states that all general-purpose DNS implementations must support both UDP and TCP, and that TCP is a full peer to UDP rather than a last resort. RFC 9210 (2022) goes further and makes TCP support an operational requirement for internet-facing DNS services.

The practical upshot: if you run a DNS server, it must answer on TCP 53. If you run a firewall in front of DNS clients or servers, it must allow TCP 53 in the same directions it allows UDP 53.

What the Packets Look Like

Over UDP, one DNS message fills one datagram. Over TCP, each message is prefixed with a two-byte length field so the receiver knows where it ends in the byte stream. That's the only framing difference — the message itself is identical.

This Python script, using only the standard library, sends the same query both ways and reports whether the response was truncated:

import random
import socket
import struct


def build_query(name: str, qtype: int = 1) -> tuple[int, bytes]:
    """Build a minimal DNS query packet (QTYPE 1 = A, class IN)."""
    query_id = random.randint(0, 0xFFFF)
    flags = 0x0100  # RD bit: ask for recursion
    header = struct.pack("!HHHHHH", query_id, flags, 1, 0, 0, 0)
    labels = [label for label in name.split(".") if label]  # "." -> no labels
    qname = b"".join(bytes([len(l)]) + l.encode("ascii") for l in labels) + b"\x00"
    return query_id, header + qname + struct.pack("!HH", qtype, 1)


def parse_header(data: bytes) -> dict:
    query_id, flags, qd, an, ns, ar = struct.unpack("!HHHHHH", data[:12])
    return {
        "id": query_id,
        "truncated": bool(flags & 0x0200),
        "rcode": flags & 0x000F,
        "answers": an,
        "size": len(data),
    }


def query_udp(name: str, server: str, qtype: int = 1) -> dict:
    query_id, packet = build_query(name, qtype)
    with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
        sock.settimeout(3)
        sock.sendto(packet, (server, 53))
        data, _ = sock.recvfrom(65535)
    result = parse_header(data)
    result["id_matches"] = result["id"] == query_id
    return result


def query_tcp(name: str, server: str, qtype: int = 1) -> dict:
    query_id, packet = build_query(name, qtype)
    with socket.create_connection((server, 53), timeout=3) as sock:
        # Over TCP, every DNS message is prefixed with a 2-byte length
        sock.sendall(struct.pack("!H", len(packet)) + packet)
        length = struct.unpack("!H", sock.recv(2))[0]
        data = b""
        while len(data) < length:
            data += sock.recv(length - len(data))
    result = parse_header(data)
    result["id_matches"] = result["id"] == query_id
    return result


print("UDP:", query_udp("example.com", "1.1.1.1"))
print("TCP:", query_tcp("example.com", "1.1.1.1"))
# 48 = DNSKEY. Without EDNS, the UDP reply is capped at 512 bytes.
print("UDP DNSKEY:", query_udp(".", "198.41.0.4", qtype=48))
print("TCP DNSKEY:", query_tcp(".", "198.41.0.4", qtype=48))
UDP: {'id': 26377, 'truncated': False, 'rcode': 0, 'answers': 2, 'size': 61, 'id_matches': True}
TCP: {'id': 5130, 'truncated': False, 'rcode': 0, 'answers': 2, 'size': 61, 'id_matches': True}
UDP DNSKEY: {'id': 60982, 'truncated': True, 'rcode': 0, 'answers': 1, 'size': 292, 'id_matches': True}
TCP DNSKEY: {'id': 51874, 'truncated': False, 'rcode': 0, 'answers': 4, 'size': 1117, 'id_matches': True}

The script builds a raw query without EDNS, so the server assumes the classic 512-byte UDP limit. The small A record fits either way. The root DNSKEY query comes back truncated over UDP with a single record, and complete over TCP with all four. The ID check matters: a client must discard any response whose ID doesn't match its query.

Source Ports and Spoofing

The destination port is always 53, but the source port a resolver uses should be random. In 2008, Dan Kaminsky showed that resolvers using a fixed or predictable source port could have their caches poisoned in seconds, because an attacker only had to guess the 16-bit query ID. Randomizing the source port adds roughly another 16 bits of entropy, making blind spoofing impractical. Every modern resolver does this by default.

TCP is inherently harder to spoof, because an off-path attacker can't complete the handshake without seeing the server's sequence numbers. That's one reason some resolvers fall back to TCP when they detect suspicious UDP responses. For the wider picture, see DNS spoofing and cache poisoning.

UDP's lack of a handshake is also what makes DNS useful for reflection attacks: an attacker sends small queries with a forged source address, and the server sends much larger answers to the victim. DNS amplification attacks exploit exactly this, and it's one more reason resolvers should not be open to the internet.

Configuring Firewalls Correctly

For a server that answers DNS (authoritative or resolver), allow inbound UDP and TCP on port 53. With ufw:

sudo ufw allow 53/udp
sudo ufw allow 53/tcp

With nftables, as part of an existing inet filter table:

sudo nft add rule inet filter input udp dport 53 accept
sudo nft add rule inet filter input tcp dport 53 accept

On AWS, security groups need both protocols too:

aws ec2 authorize-security-group-ingress --group-id sg-0123456789abcdef0 \
  --ip-permissions \
  'IpProtocol=udp,FromPort=53,ToPort=53,IpRanges=[{CidrIp=0.0.0.0/0}]' \
  'IpProtocol=tcp,FromPort=53,ToPort=53,IpRanges=[{CidrIp=0.0.0.0/0}]'

Each of these opens port 53 for both transports. On a recursive resolver, replace "anywhere" with your own client networks so you aren't running an open resolver.

For outbound traffic from clients and resolvers, the same rule applies in reverse: allow UDP and TCP to destination port 53. Also watch for middleboxes that drop UDP fragments or DNS responses larger than 512 bytes, which cause intermittent failures that look like timeouts rather than errors.

Checking what's listening

To confirm a server is listening on both transports:

sudo ss -lunp 'sport = :53'   # UDP listeners
sudo ss -ltnp 'sport = :53'   # TCP listeners

And to see real traffic switch from UDP to TCP while you run the dig commands above:

sudo tcpdump -ni any port 53

You'll see the UDP query and truncated reply, followed by a TCP handshake and the full answer on the same port.

Ports for Encrypted DNS

Classic DNS on port 53 is unencrypted, so anyone on the path can see and potentially tamper with queries. Encrypted alternatives use different ports:

ProtocolPortTransport
Classic DNS53UDP, falling back to TCP
DNS over TLS (DoT)853TCP with TLS
DNS over QUIC (DoQ)853UDP with QUIC
DNS over HTTPS (DoH)443HTTPS over TCP or QUIC

DoT's dedicated port makes it easy to identify and block, while DoH blends in with ordinary web traffic. The posts on DNS over HTTPS and DNS over TLS cover those protocols in depth. Communication between recursive resolvers and authoritative servers still overwhelmingly uses port 53.


DNS Port 53 FAQ

Both. DNS uses UDP on port 53 for most queries because it's faster, and TCP on port 53 for truncated responses, zone transfers, and whenever a client chooses it. All implementations are required to support both.

Port 53 was assigned to the Domain Name System as its well-known port when DNS was defined in the 1980s. A fixed port lets any resolver reach any DNS server without prior coordination.

The TC (truncated) bit is a header flag that a server sets when the full response won't fit in a UDP message. It tells the client to repeat the query over TCP to get the complete answer.

No. Blocking TCP 53 breaks large responses, DNSSEC-signed answers, and zone transfers. RFC 7766 and RFC 9210 require TCP support for DNS servers and clients.

Classic DNS limits UDP responses to 512 bytes. With EDNS, clients advertise larger sizes; 1232 bytes is the widely used default that avoids IP fragmentation.

Zone transfers (AXFR and IXFR) use TCP on port 53, because zones can be large and need reliable, ordered delivery.

DNS over HTTPS uses port 443, the same as regular HTTPS traffic. DNS over TLS and DNS over QUIC use port 853.

Add the +tcp option, for example dig +tcp example.com @1.1.1.1. Without it, dig uses UDP and only switches to TCP if the response is truncated.

Conclusion

DNS uses port 53 because it was assigned that well-known port at the start, and it stays there because every resolver and server on the internet depends on it being predictable. UDP handles the vast majority of queries, since a single small packet each way is the fastest possible exchange. When responses outgrow the advertised UDP size, when zones need transferring, or when a client simply prefers it, DNS switches to TCP on the same port.

The single most useful takeaway is operational: always allow both UDP and TCP on port 53 wherever DNS traffic needs to flow. Use dig +ignore, dig +tcp, and tcpdump to see exactly which transport is in use, and remember that a lookup that fails only for large or DNSSEC-signed answers is almost always a blocked TCP fallback.

Here are some useful references for going deeper on DNS transport and port 53:

  1. RFC 1035: Domain Names - Implementation and Specification — defines the DNS message format, the 512-byte UDP limit, and the use of port 53.
  2. RFC 7766: DNS Transport over TCP - Implementation Requirements — makes TCP support mandatory for DNS implementations.
  3. RFC 9210: DNS Transport over TCP - Operational Requirements — requires internet-facing DNS services to support TCP.
  4. IANA: Service Name and Transport Protocol Port Number Registry — the official registry listing port 53 for the domain service.
  5. Cloudflare Learning Center: What is DNS? — a general introduction to how DNS queries work.
Tags :
Share :

Related Posts

What Is the Difference Between Authoritative and Recursive DNS Servers?

What Is the Difference Between Authoritative and Recursive DNS Servers?

When someone says "the DNS server," they could mean two completely different machines doing two completely different jobs. One kind of server holds t

Continue Reading
Can DNS settings affect website speed?

Can DNS settings affect website speed?

Yes, DNS settings can significantly affect the speed at which a website loads for its users. DNS, or Domain Name System, is often likened to the inte

Continue Reading
Can You Use a CNAME Record on the Root Domain?

Can You Use a CNAME Record on the Root Domain?

It is one of the most common DNS questions there is. Your hosting platform says "add a CNAME pointing to myapp.example-cdn.net," it works perfectly

Continue Reading