Termux DNS & Nameservers: The No-Root Practical Guide
TL;DR: In Termux there is no /etc/resolv.conf magic and no root to fake one. Apps built on Android's libc (curl, python, nmap) resolve through Android's own resolver, while dig/kdig read a file Termux gives them — $PREFIX/etc/resolv.conf, which defaults to hardcoded Google DNS. You'll learn to see exactly what you're resolving through, override it per command, test DNS-over-HTTPS/TLS without root, and run your own local unbound resolver on 127.0.0.1. You need Termux from F-Droid or GitHub, a network connection, and nothing else.
Two hours into a CTF I was sure the box was behind a firewall. My payloads hit nothing, the target's DNS records looked rotated, and I was blaming the network. Then I noticed something uncomfortable: dig on my phone was answering from 8.8.8.8 — the IP I never configured. Termux had silently made Google my resolver the moment I installed a DNS tool, while every other app on the phone was talking to the café's captive portal DNS. Two resolvers, one phone, zero visibility. That's the gap this guide closes.
DNS is the layer attackers touch first — poison a lookup and your "bank" becomes their server. On Android that fight is weirder than on Linux because you don't own the resolver. So let's map exactly how Termux resolves names, what you can and can't change, and how to take control without root.
⚠ Ethical framing: test DNS overrides and alternative resolvers only on networks you're authorized to probe — your lab, your CTF box, your own hotspot. Pointing dig @ at foreign resolvers on someone else's network is their traffic, not yours.
How DNS Works Inside Termux (It's Not Linux)
On desktop Linux, getaddrinfo() funnels into glibc's resolver, which reads /etc/resolv.conf — that's the whole story from the companion guide. Android is different in one fundamental way: Termux is an unprivileged app. It can't touch the system's resolver configuration, it has no /etc of its own, and by default no resolv.conf exists anywhere in its prefix. There is nothing to edit.
| Layer | Desktop Linux | Termux on Android |
|---|---|---|
| Resolver lib | glibc reads /etc/resolv.conf | Android's bionic calls into the system resolver (netd) — no file involved |
| Config file | /etc/resolv.conf (or symlink) | None by default; $PREFIX/etc/resolv.conf appears only if you install tools that pull the resolv-conf package |
| Root access | Full, if you have the password | None — binding low ports, editing Android files: blocked |
| Who answers | The nameservers you listed | Android's resolver with whatever DNS your network hands it |
That last row is the mental model to keep: Termux doesn't pick your DNS — Android does. Apps compiled against bionic (curl, wget, python, nmap's default resolution) query the resolver Android gives them. Tools with their own resolver logic (dig, kdig, host, nslookup) read a file instead. Two worlds, and the file one of them reads is a stub.
Where Termux Gets Its Nameservers
Install the DNS clients and watch what appears:
pkg update && pkg upgrade -y
pkg install dnsutils nmap whois
Note the package is dnsutils, not bindutils — that's the classic Debian package name (Debian itself renamed it to bind9-dnsutils/bind9-utils in 2022, but Termux kept the historical dnsutils). dnsutils is the real BIND client suite (dig, nslookup, host). Installing it pulls a tiny dependency called resolv-conf, and that package writes two files into $PREFIX/etc/ — with values Termux hardcoded:
cat "$PREFIX/etc/resolv.conf"
# nameserver 8.8.8.8
# nameserver 8.8.4.4
ls -l "$PREFIX/etc/hosts"
# -rw-r--r-- 1 u0_a123 u0_a123 54 Jul 19 09:12 hosts
Read that carefully: it's not "Google DNS because Android says so". It's "Google DNS because the resolv-conf package wrote a default file". This is the trap — dig will happily tell you it's talking to 8.8.8.8 while the rest of your phone resolves through the ISP's captive portal. When you see that Server: line in dig output, you're seeing this file, not your network's real DNS.
Checking What You're Resolving Through
Your first honest check is simply asking dig who it uses:
dig example.com
# ;;<<>> DiG 9.20.26 <<>> example.com
# ;; global options: +cmd
# ;; Got answer:
# ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 4321
# ;; flags: qr rd ra; 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. 300 IN A 104.20.23.154
# example.com. 300 IN A 172.66.147.243
# ;; SERVER: 8.8.8.8#53(8.8.8.8) (UDP)
The SERVER: line is your answer — that's the resolver dig actually queried. Now, for Android's own view: on older devices getprop could reveal the network DNS directly. On modern Android (8+) netd manages per-network resolvers and the old properties are almost always empty:
getprop net.dns1
# (usually empty on Android 8+)
getprop | grep -i dns
Two honest corrections while we're here — because I chased both for an hour once:
- There is no
getentin Termux. It ships with glibc on Linux; bionic doesn't have it and no Termux package provides it. Don't burn time hunting. Use dig for lookups andgetpropfor Android properties. pingis a weak DNS tool on Android anyway. It resolves throughgetaddrinfo(Android's resolver), so it never shows you which server answered. (It does work without root on most stock devices — Termux wraps Android's setuid/system/bin/ping; the root requirement applies to raw sockets, i.e. crafting packets or SYN scans, not to ping.) Use ping for reachability, not DNS diagnostics.
To see Android's real resolution in action, use a bionic-compiled tool and compare it against an explicit query:
# What does Android's resolver hand curl?
curl -s --max-time 5 https://example.com -o /dev/null -w "curl: %{remote_ip}\n"
# What does 1.1.1.1 say?
dig @1.1.1.1 +short example.com A
If those IPs diverge on a network you control, you've just spotted DNS-level filtering or hijacking — the classic "compare against a known-good resolver" trick. That's a genuine pentest move, and it costs nothing.
Overriding DNS Per Command (No Root)
You can't edit a global file, so you override at the point of use. The bread and butter:
dig @9.9.9.9 +short example.com A # Quad9, DNSSEC-validating
dig @1.1.1.1 example.com TXT # full output from Cloudflare
host example.com 8.8.8.8 # host with explicit server
nslookup example.com 1.1.1.1 # nslookup with explicit server
dig @server is the pattern that saves you in CTFs: one query against the target's DNS to enumerate internal records, one against 1.1.1.1 to confirm what the public view is. Zone transfer check, anyone?
# Always probe for open zone transfers (authorize first!)
dig @ns1.example.com example.com AXFR
# Point nmap's reverse lookups at a resolver you trust
nmap --dns-servers 1.1.1.1,8.8.8.8 -sL scanme.nmap.org
For single hosts, curl --resolve pins the address without touching any resolver at all — handy when the DNS itself is the attacker:
# Force example.com to 104.20.23.154 for this request only
curl --resolve example.com:443:104.20.23.154 https://example.com/
Encrypted DNS Without Root: DoH & DoT
Plain DNS leaks everything to whoever watches the network. On a laptop you'd flip systemd-resolved to DoT; in Termux you test it per query. First, DoH over plain curl — no extra packages, and it works anywhere HTTPS does:
curl -s -H 'accept: application/dns-json' \
'https://cloudflare-dns.com/dns-query?name=example.com&type=A'
# {"Status":0,"TC":false,"RD":true,"RA":true,"AD":false,"CD":false,"Question":[...],
# "Answer":[{"name":"example.com","type":1,"TTL":238,"data":"104.20.23.154"},
# {"name":"example.com","type":1,"TTL":238,"data":"172.66.147.243"}]}
curl also has --doh-url, which resolves the whole request through a DoH endpoint:
curl --doh-url https://cloudflare-dns.com/dns-query -s https://example.com -o /dev/null -w "%{http_code}\n"
Then the serious tool: knot-utils ships kdig, which speaks DoT and DoH natively. Install it, and the phone's DNS queries get a TLS jacket:
pkg install libknot knot-utils
kdig @1.1.1.1 +tls example.com A # DNS over TLS (port 853)
kdig @cloudflare-dns.com +https example.com A # DNS over HTTPS
Even BIND's dig got the memo — dig 9.18+ (Termux ships 9.20) accepts a DoH URL as the server:
dig @https://cloudflare-dns.com/dns-query example.com A
One note for honesty: DoH/DoT encrypts the query to the resolver. It doesn't hide which resolver you're using, and it doesn't make the resolver trustworthy — 1.1.1.1 vs 9.9.9.9 is still a trust decision. And DNSSEC validation (which you get for free with kdig by default) is what actually catches tampered answers.
Route DNS Through a Tunnel: proxychains-ng
Sometimes you don't want a different resolver — you want resolution to happen somewhere else, like inside a SOCKS pivot on a CTF network. That's what proxychains-ng is for. Its proxy_dns option forces every DNS query through the SOCKS proxy, so the proxy's network does the resolving:
pkg install proxychains-ng microsocks
# A local SOCKS5 server to test with (no auth, loopback only)
microsocks -i 127.0.0.1 -p 1080 &
# Make sure $PREFIX/etc/proxychains.conf ends with:
# proxy_dns
# [ProxyList]
# socks5 127.0.0.1 1080
proxychains4 curl -s https://api.ipify.org
With proxy_dns on, that curl resolves through the SOCKS chain, not through Android. In a real engagement you point the ProxyList at your pivot host and suddenly every tool on the phone speaks the target's DNS. That's the same trick as proxychains nmap on Kali — but running on a phone with zero root.
Run Your Own Local Resolver: unbound on 127.0.0.1
The proper fix for "I don't trust this network's DNS" is a recursive, validating resolver you control. Termux packages unbound — the same daemon your desktop article might use — and it runs fine as an unprivileged app:
pkg install unbound
Write a minimal config. Two deliberate choices: port 5353, not 53 (unprivileged apps can't bind low ports, and Android's own resolver may already be squatting on 127.0.0.1:53), and a DoT forwarder so upstream queries are encrypted and DNSSEC-validated:
cat > "$PREFIX/etc/unbound/unbound.conf" <<'EOF'
server:
interface: 127.0.0.1
port: 5353
access-control: 127.0.0.0/8 allow
do-daemonize: yes
forward-zone:
name: "."
forward-addr: 1.1.1.1@853#cloudflare-dns.com
forward-tls-upstream: yes
EOF
unbound -c "$PREFIX/etc/unbound/unbound.conf"
dig @127.0.0.1 -p 5353 example.com A
Now dig @127.0.0.1 -p 5353 is your secure, local, cache-having resolver. Point any tool at it (nmap --dns-servers 127.0.0.1), and every answer is validated before it reaches you. Unbound in Termux has no chroot and no daemon user — it just runs as you, which is exactly why it works without root.
The Android Ceiling: System-Wide DNS Needs the OS
Here's the wall: Termux cannot change Android's global DNS. No file write, no iptables trick, no LD_PRELOAD hack will convince netd to use your resolver for other apps. Even with root (pkg install tsu), Android doesn't have a resolv.conf to rewrite — its resolution is netd's internal state. So for system-wide DNS control, go to the OS level:
- Android Private DNS (Android 9+): Settings → Network → Private DNS → set hostname (e.g.
dns.googleorone.one.one.one). It's DoT-only, applies to the whole device, and needs no root. Note: dig/kdig bypass it, because they don't use Android's resolver. - ADB, no root required:
adb shell settings put global private_dns_mode hostnamethenadb shell settings put global private_dns_specifier dns.google— scriptable for testing labs. - VPN apps (WireGuard/OpenVPN) can push their own DNS to the whole phone, which is how a pentester's "secure DNS everywhere" setup usually runs.
Within Termux itself, everything above — per-query overrides, DoH/DoT, proxychains, local unbound — is your playground, root or not.
Gotchas We Hit So You Don't
digdoes NOT talk to Android's DNS. It reads$PREFIX/etc/resolv.conf(Google DNS by default). Check theSERVER:line before trusting a result.- Binding port 53 fails. Low ports are root-only and netd may already hold loopback:53. Use 5353 for your local resolver.
getentdoesn't exist in Termux. bionic has no getent, and no package provides it. Stop searching; usegetpropand dig.pingnever shows which resolver answered (it resolves through Android's resolver, not a file you control). Use it for reachability, not DNS diagnostics.- Android Private DNS won't cover dig. It only affects the platform resolver. Per-query tools read their own file — that's why you now control that file.
Takeaways
- Know your resolver split – dig/kdig read
$PREFIX/etc/resolv.conf(Google's 8.8.8.8 by default after installingdnsutils), while curl, python, and nmap resolve through Android's netd. "Which resolver am I actually using?" should be your first question, every time. dig @serverbeats editing files – per-query overrides are the rootless way to compare resolvers, hunt filtering, and probe zone transfers; addkdig +tls/DoH and you've encrypted the whole exchange.- Run
unboundon 127.0.0.1:5353 – a local validating resolver with DoT forwarding gives you DNSSEC-checked, cache-fast lookups in Termux with no root; port 5353 dodges Android's port-53 squatting. - System-wide DNS is the OS's job – Android Private DNS (DoT), ADB settings, or a VPN; Termux can't rewrite netd, and claiming otherwise is how scripts break.
Anchor insight: in Termux, dig ≠ your phone's DNS — one reads a stub file Termux wrote, the other answers to Android's netd. Know which one you're querying, and you've already won half the DNS battles on Android.
Companion piece: Linux Nameservers & DNS Resolution: The Practical Guide covers the desktop side — resolvectl, nsswitch order, and the full dig/getent battery. And if you're new to the environment itself, start with Termux on Android (Non-Root): Complete Setup Guide.
