Short answer: DNS uses port 53, over both UDP and TCP. Ordinary lookups go over UDP; TCP carries answers too large for a UDP packet, zone transfers between name servers, and every retry after a truncated reply. A DNS server needs both open, and the standards have required TCP support since 2016. Encrypted DNS uses other ports: 853 for DNS over TLS, 443 for DNS over HTTPS.
| Port | Transport | What runs there |
|---|---|---|
| 53 | UDP | Ordinary queries and answers, client to resolver and resolver to name server |
| 53 | TCP | Large answers, retries after truncation, zone transfers (AXFR/IXFR) |
| 853 | TCP | DNS over TLS (DoT, RFC 7858) |
| 853 | UDP | DNS over QUIC (DoQ, RFC 9250) |
| 443 | TCP (UDP with HTTP/3) | DNS over HTTPS (DoH, RFC 8484) |
| 5353 | UDP | Multicast DNS on the local network (.local names) |
| 5355 | UDP + TCP | LLMNR, Windows name resolution on the local network |
Port 53 is the server side. The client sends from a random high source port: on Linux usually one between 32768 and 60999, on Windows one from 49152 upwards. That is why a firewall rule for a DNS server matches the destination port 53, and why the replies of a stateful firewall need no rule of their own.
Is port 53 TCP or UDP?
Both, for different jobs. UDP is the default because a question and its answer each fit in one packet, with no handshake. The original limit was 512 bytes. EDNS (RFC 6891) lets a client announce a bigger buffer, and since DNS Flag Day 2020 most software offers 1232 bytes, which avoids IP fragmentation. When the answer still does not fit, the server sends what fits with the tc (truncated) flag set, and the client asks again over TCP. You can watch it happen by asking for Google’s TXT records with a small buffer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 14290
;; flags: qr tc rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1+ignore stops dig at the truncated UDP answer: tc set, no records. Without it, dig does what every resolver does:
;; Truncated, retrying in TCP mode.
;; flags: qr rd ra; QUERY: 1, ANSWER: 17, AUTHORITY: 0, ADDITIONAL: 1
;; SERVER: 8.8.8.8#53(8.8.8.8) (TCP)
;; MSG SIZE rcvd: 1187Zone transfers (AXFR) run over TCP only. RFC 7766 made TCP support mandatory for every DNS implementation, and RFC 9210 (BCP 235) tells operators that they must allow DNS over both transports. A firewall that opens only UDP 53 therefore breaks DNS in a nasty way: most lookups work, and the ones with large answers fail, often DNSSEC-signed names and domains with long TXT records.
How to test port 53 with dig
dig comes with the bind9-dnsutils package on Debian and Ubuntu. Ask a specific server with @; replace YOUR_SERVER_IP with the address of the server you are testing:
dig @YOUR_SERVER_IP example.com AThis is a healthy answer from a public resolver, Cloudflare’s 1.1.1.1:
; <<>> DiG 9.18.49-1~deb12u2-Debian <<>> @1.1.1.1 example.com A
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 5765
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;example.com. IN A
;; ANSWER SECTION:
example.com. 34 IN A 172.66.147.243
example.com. 34 IN A 104.20.23.154
;; Query time: 3 msec
;; SERVER: 1.1.1.1#53(1.1.1.1) (UDP)
;; WHEN: Sun Oct 11 14:25:57 UTC 2026
;; MSG SIZE rcvd: 72status: NOERRORand an ANSWER section: the server answered and the name exists.ra(recursion available): this server looks up names it does not host. That is right for a resolver and wrong for a server that is only meant to serve your zones.ad: the resolver validated the answer with DNSSEC.SERVER: … (UDP)names the transport. Repeat with+tcpto test TCP 53.
dig +tcp @YOUR_SERVER_IP example.com AAn authoritative server answers differently. Here is one of the name servers for example.com, asked without recursion and over TCP:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 54360
;; flags: qr aa; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
;; ANSWER SECTION:
example.com. 300 IN A 104.20.23.154
example.com. 300 IN A 172.66.147.243
;; SERVER: 108.162.192.162#53(hera.ns.cloudflare.com) (TCP)aa means authoritative: this is the source, not a cached copy, which is why the TTL is the full 300 seconds. Ask the same server for a name it does not host and it refuses, which is exactly what an authoritative-only server should do:
;; ->>HEADER<<- opcode: QUERY, status: REFUSED, id: 44351
;; flags: qr rd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not availableA timeout instead (communications error … timed out) means nothing answered at all: the service is down, listens on another address, or a firewall drops the packets. On Windows, Resolve-DnsName -Name example.com -Server YOUR_SERVER_IP -TcpOnly tests TCP, and nslookup example.com YOUR_SERVER_IP tests UDP; the nslookup guide explains its output.
From outside, the open port checker tells you whether TCP 53 is reachable from our Amsterdam server; it cannot test UDP, because a silent UDP port looks the same as a filtered one. To see what the world gets for your domain, the DNS lookup shows the records with their TTLs and the DNS propagation checker asks your domain’s own name server next to twelve public resolvers.
Port 53 already in use: systemd-resolved
You install Pi-hole, AdGuard Home, dnsmasq, Unbound or a DNS container on Ubuntu, and it will not start: the log says bind: address already in use or Address already in use for port 53. Find the owner:
sudo ss -lntup 'sport = :53'Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
udp UNCONN 0 0 127.0.0.54:53 0.0.0.0:* users:(("systemd-resolve",pid=612,fd=20))
udp UNCONN 0 0 127.0.0.53%lo:53 0.0.0.0:* users:(("systemd-resolve",pid=612,fd=18))
tcp LISTEN 0 4096 127.0.0.53%lo:53 0.0.0.0:* users:(("systemd-resolve",pid=612,fd=19))
tcp LISTEN 0 4096 127.0.0.54:53 0.0.0.0:* users:(("systemd-resolve",pid=612,fd=21))That is the stub listener of systemd-resolved, the local resolver Ubuntu runs by default (Ubuntu 22.04 and 20.04 show only 127.0.0.53; the second address came with systemd 250). /etc/resolv.conf points programs at 127.0.0.53, and resolved forwards their questions upstream. It holds port 53 only on those loopback addresses, but a server that binds the wildcard address 0.0.0.0:53 collides with it. We tested this: with 127.0.0.53:53 taken, a TCP bind to 0.0.0.0:53 fails, while a bind to 127.0.0.1:53 or to the server’s public address succeeds. A default Debian install does not include systemd-resolved, though some images add it. If ss names another process, such as dnsmasq from libvirt or a docker-proxy, deal with that one instead. The general method is in Address already in use.
There are two clean fixes.
Fix 1: bind your DNS server to specific addresses and leave resolved alone. In Unbound that is one interface: line per address, in AdGuard Home the bind_hosts list, in Docker -p 203.0.113.10:53:53/udp and the same with /tcp instead of -p 53:53/udp. BIND (named) already binds every interface address separately rather than the wildcard, which is why it usually starts next to the stub without a clash. The server then keeps using resolved for its own lookups.
Fix 2: turn the stub listener off. This is what the Pi-hole and AdGuard Home documentation describe. Do it in this order, because the machine must never be left with /etc/resolv.conf pointing at a stub that no longer exists:
- 1Check that resolved knows real upstream servers
Debian · Ubuntu (systemd-resolved) grep nameserver /run/systemd/resolve/resolv.confYou need at least one
nameserverline with a real address. If there is none, set DNS servers first (in netplan, or with aDNS=line in a resolved drop-in), or the next steps leave the server unable to resolve anything. - 2Disable the stub, repoint resolv.conf, restart
Debian · Ubuntu (systemd-resolved) sudo mkdir -p /etc/systemd/resolved.conf.d printf '[Resolve]\nDNSStubListener=no\n' | sudo tee /etc/systemd/resolved.conf.d/no-stub.conf sudo ln -sf /run/systemd/resolve/resolv.conf /etc/resolv.conf sudo systemctl restart systemd-resolvedThe symlink comes before the restart:
/run/systemd/resolve/resolv.conflists the upstream servers directly, so lookups keep working while the stub goes away.DNSStubListener=nofrees both127.0.0.53and127.0.0.54. - 3Confirm port 53 is free and names still resolve
Debian · Ubuntu sudo ss -lntup 'sport = :53' getent hosts example.comThe first command should print only its header line, the second an address. Now start your DNS server.
If the server should use your new resolver for its own lookups too, add DNS=127.0.0.1 to the same drop-in and restart resolved again, but only after dig @127.0.0.1 example.com answers. Done the other way round, the machine cannot resolve names, and apt fails with Temporary failure in name resolution. To undo everything, delete the drop-in, run sudo ln -sf ../run/systemd/resolve/stub-resolv.conf /etc/resolv.conf and restart systemd-resolved.
On Windows Server, find the owner of UDP 53 with Get-NetUDPEndpoint -LocalPort 53 | Select-Object LocalAddress, OwningProcess and look the process up with Get-Process -Id.
Can DNS run on a port other than 53?
Not on the internet. An NS record names a server but has no field for a port, so every resolver in the world asks your authoritative servers on 53. Behind the scenes, though, another port is common and avoids the stub conflict entirely. Pi-hole’s own Unbound guide, for example, runs Unbound on 127.0.0.1 port 5335 and lets Pi-hole forward to it. Test such a server with dig’s -p option:
dig @127.0.0.1 -p 5335 example.com/etc/resolv.conf cannot carry a port: its nameserver lines take an address only, so programs that read it always use 53. systemd-resolved can, with DNS=127.0.0.1:5335 in a drop-in, and then forwards the whole machine’s lookups there, as long as /etc/resolv.conf still points at its stub. The port syntax needs systemd 246 or later, so not Ubuntu 20.04.
Never run an open resolver
A resolver that answers recursive questions from anyone on the internet is an open resolver, and internet-wide scans find it quickly. Attackers send small queries with a forged source address, the victim’s, and your server sends the much larger answers to the victim: a DNS amplification attack, paid for with your bandwidth and your IP’s reputation. Decide which of the two jobs your server does:
- Authoritative only (it serves your own zones): port 53 open to the world, recursion off.
- Resolver (it looks up any name for your machines): recursion on, but only for your own addresses, and port 53 closed to everyone else.
options {
directory "/var/cache/bind";
recursion no;
allow-transfer { none; };
};options {
directory "/var/cache/bind";
recursion yes;
allow-recursion { 127.0.0.1; ::1; 198.51.100.0/24; };
};Edit the options block that is already in the file rather than adding a second one. Replace 198.51.100.0/24 with the addresses that may use the resolver, and list your secondaries in allow-transfer if you run any. Left unset, BIND 9.18 (Debian 12, Ubuntu 24.04) allows zone transfers to every host; BIND 9.20 (Debian 13) allows none. Spell the recursion list out too: BIND’s default when you set nothing is localnets; localhost;, and localnets covers every network the server has an address in, which on a hosting network can include other customers’ servers. Unbound is safer by default: it answers only localhost and refuses everyone else until you allow them. To serve your own network, make it listen on the server’s address too (203.0.113.10 here) and allow that network:
server:
interface: 127.0.0.1
interface: 203.0.113.10
access-control: 198.51.100.0/24 allowThen test from a machine that should not have access, for example your laptop on its home connection: dig @YOUR_SERVER_IP example.com must end in status: REFUSED or a timeout. An answer with the ra flag and an ANSWER section means the resolver is open.
Firewall rules for port 53
sudo ufw allow 53sudo ufw allow from 198.51.100.0/24 to any port 53Without /udp or /tcp, UFW opens both protocols, which is what DNS needs. On Rocky and AlmaLinux, firewalld’s dns service covers TCP and UDP 53:
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="198.51.100.0/24" service name="dns" accept'
sudo firewall-cmd --reloadFor an authoritative server use --add-service=dns instead of the rich rule. Add 853/tcp if you offer DNS over TLS. If your firewall also blocks outgoing traffic by default, a resolver needs outbound UDP and TCP 53 to anywhere (sudo ufw allow out 53).
For how DNS fits together, resolvers, authoritative servers and TTLs, see What is DNS; when a client cannot reach any resolver at all, see DNS server not responding. A resolver or an authoritative name server fits on the smallest Standard KVM plan, with root access and an IPv4 address of its own. The systemd-resolved steps above are written for the Ubuntu releases you can pick on an Ubuntu VPS: 24.04, 22.04 and 20.04.
Frequently asked
Is DNS port 53 TCP or UDP?
Both. Ordinary lookups use UDP, while TCP carries answers too large for UDP, retries after a truncated reply and zone transfers. RFC 7766 made TCP support mandatory, so a DNS server's firewall must allow TCP and UDP 53.
What is using port 53 on Ubuntu?
Usually the stub listener of systemd-resolved on 127.0.0.53 (and 127.0.0.54 on Ubuntu 24.04), which you can confirm with sudo ss -lntup 'sport = :53'. Either bind your own DNS server to specific addresses, or set DNSStubListener=no in a resolved.conf drop-in and point /etc/resolv.conf at /run/systemd/resolve/resolv.conf before restarting systemd-resolved.
Should port 53 be open to the internet?
Only on an authoritative server that answers for your own zones, with recursion turned off. A resolver that answers recursive queries from anyone is an open resolver and gets abused for DNS amplification attacks, so limit it to your own addresses.