Welcome back to the build! In this guide, we're going to tackle one of the most fundamental pieces of any infrastructure: DNS. If you've been following along with this series, you know I'm all about building with intention. That means understanding the why behind each component before we start configuring anything.
DNS is typically one of the first services I set up when building out any environment. Why? Because it gives you a solid foundation for everything else. Server naming, service discovery, internal routing. it all starts here. When your DNS is properly structured from day one, the rest of your infrastructure just falls into place.
In this article, we're implementing CoreDNS, but this isn't just a step-by-step installation guide. We're going to talk about DNS architecture, the different roles a DNS server can play, and how to think strategically about your DNS setup. Then we'll walk through the actual implementation.
This guide is divided into two parts. The first part focuses on concepts and architecture. Helping you understand what we're building and why. The second part, which you'll find at the end, is a complete Command Reference Guide with all the commands you need to replicate this setup in your own environment.
Before We Begin: Prerequisites
Before diving into DNS, you need to have a few things already configured in your environment. DNS won't work without a proper network foundation, and you'll need storage to download the necessary templates and run VMs.
- Your network infrastructure configured and operational
- Storage already set up for templates and VMs
- A working virtualization platform (Proxmox)
If you haven't set these up yet, I recommend getting those foundational pieces in place first. I also have a previous guide covering Bind9 if you want a deeper dive into how DNS works at a fundamental level. That video walks through DNS concepts in detail, so I won't be repeating all of that here. Instead, we're focusing on the architecture and how CoreDNS fits into a modern, automated infrastructure.
Understanding DNS Architecture
Let's slow down and talk about DNS architecture before we install anything. For me, understanding how I want DNS to function comes before configuration. It makes the build intentional instead of reactive. If you've been following this series, you know that's the whole point. Building with structure and purpose.
The Three Core DNS Roles
DNS servers don't come in fixed types. Instead, they perform roles, and a single DNS server can perform one role, multiple roles, or all of them at the same time depending on how it's configured. Once you understand these roles, a lot of the confusion around DNS setup clears up, and you can make much better architectural decisions.
Authoritative DNS Server: An authoritative DNS server is the source of truth for a domain or zone. It answers queries only for the zones it owns, usually using zone files or another authoritative data source. If the server is authoritative for a zone, it doesn't need to ask anyone else for answers related to that zone. It already knows the answer.
Forwarder: A forwarder doesn't resolve queries on its own. Instead, it forwards DNS requests to another DNS server and returns the response. This is often used to centralize resolution, enforce policy, or control where DNS traffic is allowed to go. Think of it as a middleman that knows where to send requests but doesn't do the heavy lifting itself.
Recursive Resolver: A recursive DNS server performs full resolution by itself. It works through the DNS hierarchy using root hints, starting at the root servers, following referrals through top-level domains, and resolving the query all the way to the authoritative server. This is the role that actually reaches out to the public internet. If you're inside your network and you query something like google.com, somewhere along the line there's a recursive server going out to the internet, grabbing that information, and bringing it back.
Designing DNS for Segmented Networks
Understanding these roles becomes especially important when you're building environments with multiple subdomains and segmented networks. Let's say you have multiple VLANs, and each VLAN owns its own subdomain. You need to be intentional about how DNS traffic flows between those VLANs.
One common approach for a secure environment is to run a DNS server per VLAN that is authoritative for its own subdomain while also acting as a forwarder. That server only answers for its own zone and forwards anything else outside of its zone to a central recursive DNS server.
Each VLAN has its own authoritative DNS server, and a single recursive server handles all external resolution. You get to decide which VLANs are even allowed to reach that recursive server at all.
The beauty of this setup is that because the recursive server is the only system reaching the outside world, it can be much more tightly locked down. This is where features like DNS over TLS make a lot of sense. Internal VLANs forward queries internally, and only the recursive server encrypts and sends requests externally. This helps prevent DNS leaks and gives you a cleaner security boundary.
At the same time, this setup allows for smooth internal resolution. If you want to control name resolution between VLANs, the recursive server can handle that too, based entirely on your design decisions. Just be careful here, if you're creating separation between VLANs, you need to be intentional about which VLANs can communicate with each other's DNS servers.
I wanted to cover this architectural thinking before we even talk about CoreDNS specifically. Once you understand these concepts, you can design your DNS infrastructure with clarity. You can say, "I want this DNS server in this VLAN to only be an authoritative server," or "I want this server to be both authoritative and a forwarder." Then you can have that one central recursive server where everything ultimately goes, and you can apply all your security controls there.
About CoreDNS
Now that we understand DNS architecture, let's talk about CoreDNS. CoreDNS is a plugin-based DNS server that primarily focuses on being authoritative and acting as a forwarder. Unlike traditional DNS servers, CoreDNS builds DNS behavior by chaining plugins together rather than relying on a single monolithic DNS engine.
What does that mean? CoreDNS doesn't have one big "do everything" DNS brain. Instead, it works like building blocks. Each DNS feature, authoritative responses, forwarding, rewrites, service discovery, and more...Is a small, focused piece. You stack only the pieces you need in the exact order you want them to run.
This design is incredibly powerful when you're building an environment focused on automation. CoreDNS lets you design how DNS works instead of just configuring a preset DNS server. You decide the behavior upfront, and if you need something new later, you add or change a plugin rather than redesigning the whole system.
CoreDNS comes with about 30 plugins on a default install, but you're not limited to just those. You can use external plugins or even build your own if you know how to program in Go (which CoreDNS is written in). If you want CoreDNS to do something specific and you can't find a plugin for it, you can write your own.
One important thing to keep in mind: CoreDNS does not officially endorse external plugins, so if you're going to use them, you need to be very careful and intentional about which ones you choose.
Now that you have a clear understanding of DNS architecture and why we're using CoreDNS, we can move forward with the actual setup. We'll be using an LXC container for this installation. I personally like using LXC containers for core services because they're lightweight and provide good separation.
You could install CoreDNS on Docker if you prefer, but I like my DNS server to be standalone and not just another container in a Docker stack.
Command Reference Guide
Update the system:
- apt update && apt upgrade -y
Test network connectivity:
- ping google.com
Installing CoreDNS Binary
Install curl:
- apt install curl -y
Cd Into the Temp Directory and download CoreDNS:
- cd /tmp curl -LO
- https://github.com/coredns/coredns/releases/download/v1.14.1/coredns_1.14.1_linux_amd64.tgz (make sure you update the version number)
Extract the binary:
- tar -xvf coredns_1.14.1_linux_amd64.tgz
Move binary to system path:
- mv coredns /usr/local/bin/
Make it executable:
- chmod +x /usr/local/bin/coredns
Verify installation:
- coredns -version (Show version)
- coredns -plugins (Show plugin)
Creating Configuration Directories
Create directory structure:
- mkdir -p /etc/coredns/zones
Verify directories were created:
- ls -la /etc/coredns/
Creating the Corefile
Create the Corefile:
- nano /etc/coredns/Corefile
Basic Corefile content (authoritative only):
- Replace 'yourdomain.local' with your actual domain and 'YOUR_DNS_SERVER_IP' with your server's IP address.

yourdomain.local:53 {
bind YOUR_DNS_SERVER_IP
errors
log
file /etc/coredns/zones/test.com.db
}
Creating the Zone File
Create zone file:
- nano /etc/coredns/zones/yourdomain.local.db

$ORIGIN yourdomain.local.
$TTL 300
@ IN SOA ns01.yourdomain.local. admin.test.com. (
2026020301 ; serial
3600 ; refresh
900 ; retry
604800 ; expire
300 ; minimum
)
@ IN NS ns01.yourdomain.local.
ns01 IN A YOUR_DNS_SERVER_IP
Replace placeholders with your actual domain and IP addresses. Add additional A records as needed.
Testing the Configuration
Test CoreDNS manually:
- coredns -conf /etc/coredns/Corefile
Leave this running and test from another machine. Press Ctrl+C to stop when done testing.
From a test client (configured to use your DNS server):
- ping ns01.yourdomain.local dig @YOUR_DNS_SERVER_IP ns01.yourdomain.local
Creating the Systemd Service
Create dedicated user:
- useradd --system --no-create-home --shell /usr/sbin/nologin coredns
Grant capability to bind to port 53:
- setcap 'cap_net_bind_service=+ep' /usr/local/bin/coredns
Create service file:
- nano /etc/systemd/system/coredns.service
[Unit]
Description=CoreDNS DNS Server
After=network-online.target
Wants=network-online.target
[Service]
ExecStart=/usr/local/bin/coredns -conf /etc/coredns/Corefile
Restart=on-failure
RestartSec=2
User=coredns
LimitNOFILE=1048576
[Install]
WantedBy=multi-user.target
Enable and start the service:
- systemctl daemon-reload
- systemctl enable coredns
- systemctl start coredns
Check service status:
- systemctl status coredns
Adding Forwarding Capability (Optional)
Edit Corefile to add forwarding:
- nano /etc/coredns/Corefile
Updated Corefile With forwarding
Make sure you Replace 'YOUR_UPSTREAM_DNS' with your gateway IP or another recursive DNS server.

yourdomain.local:53 {
file /etc/coredns/Zones/yourdomain.local.db
log
errors
}
.:53 {
forward . YOUR_UPSTREAM_DNS
log
errors
}
Restart CoreDNS:
- systemctl restart coredns
Test external resolution:
- ping google.com
Final Verification
Test reboot persistence:
- reboot
After reboot, check service status:
- systemctl status coredns
Test DNS resolution:
- ping ns01.yourdomain.local ping google.com
You now have a DNS server that's authoritative for your internal domain and forwards external queries to an upstream resolver. The architecture we've discussed gives you and incredible flexibility. You can scale this to multiple VLANs, each with their own authoritative DNS server, all forwarding to a single secured recursive resolver. You control the traffic flow, you control the security boundaries, and you build with intention. Stay curious. Stay geeking. I’ll see you in the next one. Peace.