The payload is not encrypted, and the NS line must point at a name with an A record
Official git repo for iodine dns tunnel
At a glance
- What is it?
- iodine carries IPv4 packets inside DNS queries so that it keeps working on networks where the web is blocked but lookups are not. It is a C program built with meson and ninja, the server and client must speak the identical protocol, the tool quietly drops from DNS tunnelling to raw UDP when it finds port 53 open to anywhere, and the README is blunt that nothing inside the tunnel is encrypted.
- Who is it for?
- Take iodine when you are on a network that blocks everything except DNS and you control a domain with a public nameserver you can delegate a subdomain to, and treat it as a raw transport rather than a secure channel. Leave it if you need confidentiality, because the README states that the tunneled data traffic is not encrypted at all and recommends double tunnelling or SSH over the top of it.
- Can I use it commercially?
- Yes. ISC is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 5 days ago.
- What is it written in?
- Mainly C, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 13, 2026, and from our analysis. They are not legal advice.
Star history
- yarrick/iodine8,035 ★ · +56
| Repository | 2026-09-22 | 2026-11-13 |
|---|---|---|
| yarrick/iodine | 8k | 8k |
56 Stars in the last 52 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
meson, ninja, and check for the tests
The build is three commands and one dependency:
meson setup build
cd build
ninjameson is what you need to compile iodine. There is no autotools path and no Makefile at the repository root.
The tests have a separate requirement. To build and run them you need the check library, and the README gives the command to start them:
ninja testrun from inside the build directory, so the same working directory as the build itself.
What is in the tree explains the rest. src/ holds the C sources, tests/ holds the test suite, meson.build is the build description, doc/ and man/ hold the documentation and manual pages, and CHANGELOG sits at the root. There are also four READMEs rather than one: README.md, README-android.txt, README-haiku.txt and README-windows.txt, so the Android, Haiku and Windows builds are documented separately from the general case.
The licence is ISC, which is a short permissive licence, and the canonical project page is code.kryo.se/iodine rather than the GitHub repository.
Client and server must speak the exact same protocol
The first line of the usage section is a compatibility warning, and it is worth reading before anything else on the page.
The server and the client are required to speak the exact same protocol. In most cases this means running the same iodine version. And the reason it cannot be papered over is stated directly: implementing backward and forward protocol compatibility is usually not feasible.
That is a hard constraint in practice.
So before you debug a zone file, check the version on both ends. That is faster than any amount of DNS troubleshooting.
iodined's DNS forwarding is not transparent, and the README says skip it in production
A server usually already runs a nameserver, so iodine has to share port 53 with it, and the README describes both ways with a warning attached.
The first way is to change the existing DNS program's listening port and let iodined listen on 53, forwarding requests onward with the -b option. The warning is in the same sentence: this procedure is not advised in production environments, because iodined's DNS forwarding is not completely transparent, and zone transfers will not work as an example.
The second way is to avoid the conflict entirely: forward the subdomain from your real DNS server to iodined, which then must run on a different port, chosen with -p.
That asymmetry is the practical guidance. If you are running this in anger, use the separate port and let your existing nameserver keep answering for everything else. The project says the -b path is not advised in production.
Once iodined is running it opens a tun device, starts listening for DNS queries on UDP port 53, and asks for a password either on the command line with -P pass or interactively after it starts.
The NS target must be a name with an A record, never a CNAME
The DNS setup is described precisely, and two of the constraints are the kind people get wrong on the first attempt.
You delegate a subdomain, say t1.mydomain.com, to a host that runs iodined, and the zone file gets two records: an NS line naming the host and an A line giving that host an address. The README says the NS line is all that is needed to route queries for that subdomain.
The two constraints. The NS target must be a name, not an IP address, and that name itself must have an A record rather than a CNAME.
The other detail is spacing. The README says to use a short name for the delegated subdomain, explicitly to keep as much space as possible available for the data traffic.
If the server has a dynamic address, the A line is dropped and the NS line points at a dynamic DNS provider instead. Then reload or restart the nameserver program, and queries for anything under that subdomain go to iodined.
The resulting server command line carries all of it:
./iodined -f -c -P secretpassword 192.168.99.1 t1.mydomain.comIodine drops to raw UDP when port 53 turns out to be wide open
The client takes one or two arguments: an optional local relaying DNS server, and the domain. If you do not give the first argument, the system's current DNS setting is consulted.
There is an automatic behaviour in there that surprises people. If DNS queries are allowed to any computer, you can point the client straight at the iodined server's address, and it may then turn out that any traffic is allowed to UDP port 53 of any computer. Iodine detects this and switches to raw UDP tunnelling if it can.
That is a sensible optimisation and a confusing one at the same time. Your tunnel is now fast, your ping works, and you are not testing the thing you built the setup for. So there is an option to force the DNS path:
./iodine -f -P secretpassword t1.mydomain.comand the README explains that adding -r forces DNS tunnelling even when raw UDP tunnelling would be possible, which it calls especially useful when testing within your own network.
Note that the client command above does not include -r. The quickstart does, for the LAN test. Which means the documented real-world invocation is the one that can silently take a different path, and the flag is what stops it.
The tunnel carries IPv4 only, and nothing in it is encrypted
Two hard limits are stated in the same short section, and one of them is a security warning rather than a protocol detail.
The first is scope. The data inside the tunnel is IPv4 only. Around that, the server listens on both IPv4 and IPv6 by default, with -4 and -6 to restrict it, and raw mode is attempted on the same protocol used for the login. The client can use IPv4 or IPv6 nameservers, with the relay nameservers translating between protocols, and -4 or -6 to force its own queries. So the transport negotiation is dual stack while the payload is not.
The second is confidentiality, and it is blunt. The tunneled data traffic is not encrypted at all, and can be read and changed by external parties relatively easily.
That sentence changes how the whole tool should be read. iodine is a transport, not a secure channel, and the DNS encoding is obscurity rather than protection.
The README's own remedy follows immediately. For maximum security, run a VPN through the DNS tunnel, which it calls double tunnelling, or use secure shell access with port forwarding. The second also covers web browsing, by running a proxy such as Privoxy on the server.
Routing all traffic through the tunnel needs a host route to the nameserver and NAT configured on the iodined side, and the same warning applies to that.
Testing means sending an NS query for a subdomain under the tunnel
There is a way to check the server without a client, and the README describes it because the first thing that fails is usually DNS and not the tunnel.
The iodined server replies to NS requests sent for subdomains of the tunnel domain. So if the delegated subdomain is t1.mydomain.com, you send an NS request for a made-up name underneath it, something like foo123.t1.mydomain.com, and see whether iodined answers.
That is a clean separation of concerns. If the NS query gets an answer, name resolution is working. If it does not, the problem is the zone file, the delegation, or the port, and none of those have anything to do with iodine itself.
The server also takes the -c option, which the README recommends if there is a chance you will be using an iodine tunnel from unexpected environments. On the client side, the tunnel interface gets an address close to the server's and a suitable MTU set for you.
Once it works, the check is ping from either end. From the client, ping the server's tunnel address; from the server, ping the client's.
Editorial conclusion
Take iodine when you are on a network that blocks everything except DNS and you control a domain with a public nameserver you can delegate a subdomain to, and treat it as a raw transport rather than a secure channel. Leave it if you need confidentiality, because the README states that the tunneled data traffic is not encrypted at all and recommends double tunnelling or SSH over the top of it. Verify three things. Confirm the client and the server run the same iodine version, since the README says backward and forward protocol compatibility is usually not feasible. Decide whether iodined will share port 53 with an existing DNS server using the -b option or run on another port with -p, because the shared-port forwarding path is described as not completely transparent and not advised in production. And read the -r option before your first test, because without it iodine may detect that UDP port 53 is open to any computer and switch to raw UDP, in which case you are not testing the DNS path you came for.
Frequently asked questions
What is the iodine DNS tunnel?
A piece of software that tunnels IPv4 data through a DNS server, for situations where internet access is firewalled but DNS queries are allowed. It is a C program with an iodined server and an iodine client, built with meson and ninja. iodined opens a tun device and listens for DNS queries on UDP port 53.
How do I build iodine?
With meson. From the repository root run meson setup build, then cd build, then ninja. To build and run the tests you also need the check library, and the tests are started with ninja test from inside the build directory.
Is iodine encrypted?
Not inside the tunnel. The README states that the tunneled data traffic is not encrypted at all and can be read and changed by external parties relatively easily. For maximum security it recommends running a VPN through the DNS tunnel, which it calls double tunneling, or using secure shell access with port forwarding, optionally through a proxy such as Privoxy.
What DNS records does iodine need?
An NS line delegating a short subdomain such as t1.mydomain.com to a host that runs iodined, and an A record for that host. The NS target must be a name rather than an IP address, and that name must have an A record rather than a CNAME. With a dynamic IP you point the NS line at a dynamic DNS provider and leave the A line out.
What is the iodine -r option for?
It forces DNS tunnelling. Without it, iodine checks whether queries are allowed to any computer and, if it finds that UDP port 53 is reachable anywhere, switches to raw UDP tunnelling when that is possible. The README calls -r especially useful when testing within your own network.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/yarrick-iodine)Star history chart for your README
[](https://hysenlabs.com/projects/yarrick-iodine)Was this useful?