A high-performance DNS server with custom record management and domain blocking capabilities. Features intelligent query routing with MongoDB-backed storage for custom DNS records, Redis caching for fast lookups, and automatic upstream fallback to public DNS servers. Built with Node.js for home labs, development environments, and network management.
- Fast Response Times: Redis caching for frequently queried domains
- Domain Blocking: MongoDB-based blocklist for ad/malware blocking
- Upstream Forwarding: Falls back to public DNS servers (Cloudflare, Google DNS) for unknown queries
- Custom Records: Support for A, AAAA, PTR, MX, TXT, CNAME records
- In-Memory Blocklist: O(1) lookup performance for blocked domains loaded in memory from MongoDB
[ Client Query ]
│
▼
┌───────────────────┐
│ DNS SERVER │◄─── [ Blocklist ]
└─────────┬─────────┘
│
(1) Is Blocked? ─────────► YES: Return DNS Sinkhole (0.0.0.0)
│
(2) NO: Check Redis
│
┌────────┴────────┐
│ Cache Hit? ├───────► YES: Return Record
└────────┬────────┘
│
(3) CACHE MISS
│
┌────────┴────────┐
│ Check MongoDB ├───────► FOUND: Return Local Record (no caching)
└────────┬────────┘
│
(4) NOT FOUND
│
┌────────┴────────┐
│ Upstream DNS │
└────────┬────────┘
│
▼
┌─────────────────────────┐
│ 1. Update redis Cache │
│ 2. Return to Client │
└─────────────────────────┘
The DNS server processes queries through a multi-layered resolution strategy for optimal performance and control:
-
Blocklist Check (In-Memory set loaded from Mongo DB)
- First line of defense against unwanted domains
- O(1) lookup using in-memory Set
- Blocked domains return
0.0.0.0(null route)
-
Cache Lookup (Redis)
- Previously resolved queries are cached with TTL
- Follows CNAME chains automatically
- Only caches upstream responses - your custom records are always fresh
- Dramatically reduces response time for repeated queries
-
Custom Records (MongoDB)
- Checks for user-defined DNS records
- Supports A, AAAA, MX, TXT, CNAME, PTR records
- Never cached - ensures instant updates when you modify records
- Perfect for home lab services and custom domains
-
Upstream Forwarding (Cloudflare / Google DNS)
- If not found in previous layers, forwards to public DNS
- Response is cached for future queries
- Ensures full DNS coverage for all domains
Why Custom Records Aren't Cached:
- Instant Updates: Changes to MongoDB records take effect immediately
- No Stale Data: Always serve the latest version of your custom records
- Zero Cache Invalidation: Eliminates the complexity of cache invalidation logic
- Predictable Behavior: What you see in the database is what gets served
What Gets Cached:
- Only responses from upstream DNS servers (Google, Cloudflare, etc.)
- Cached with original TTL values from upstream
- Automatically expires based on DNS TTL
- Docker
- Docker Compose
Note for Windows Users: Port 53 is often used by Windows DNS services, which may cause conflicts. If you ran into port already used issue, you can run the DNS server on a different port (e.g., 5353) or use WSL2 for a better experience.
- Clone the repository:
git clone https://github.com/neerajann/dns-server.git
cd dns-server- Start all services:
docker-compose up -d- Test the DNS server:
dig @localhost example.comTo run in development mode with auto-reload:
npm install
npm run devMake sure MongoDB and Redis are running locally update .env with your connection strings.
- Open Mongo Express at
http://localhost:8081 - Select the
dnsdatabase - Go to
recordscollection - Click "New Document"
- Add your record:
Simple A Record:
{
"name": "myapp.local",
"records": [
{
"type": "A",
"content": ["192.168.1.100"]
}
]
}Multiple Record Types:
{
"name": "nodepost.home",
"records": [
{
"type": "A",
"content": ["192.168.16.50"]
},
{
"type": "AAAA",
"content": ["fe80::b834:fcfd:35e:bd6c%10"]
},
{
"type": "MX",
"content": [
{
"exchange": "mail.nodepost.home",
"preference": 10
}
]
},
{
"type": "TXT",
"content": ["v=spf1 ip4:192.168.16.51 ~all"]
}
]
}- Open Mongo Express at
http://localhost:8081 - Select the
dnsdatabase - Go to
blocklistcollection - Click "New Document"
- Add domain to block:
{
"name": "ads.example.com"
}Important: After adding to the blocklist, restart the DNS server to reload:
docker-compose restart dns-server| Type | Description | Content Format |
|---|---|---|
| A | IPv4 address | ["192.168.1.1"] |
| AAAA | IPv6 address | ["2001:db8::1"] |
| CNAME | Canonical name | ["alias.example.com"] |
| MX | Mail exchange | [{exchange: "mail.example.com", preference: 10}] |
| TXT | Text record | ["v=spf1 include:_spf.example.com ~all"] |
| PTR | Reverse DNS | Automatically handled via A record lookup |
Note: The content field is always an array, even for single values.
# Query A record
dig @localhost nodepost.home
# Query MX record
dig @localhost nodepost.home MX
# Query TXT record
dig @localhost nodepost.home TXT# Query A record
Resolve-DnsName -Name nodepost.home -Server localhost -Type A
# Query MX record
Resolve-DnsName -Name nodepost.home -Server localhost -Type MX
# Query TXT record
Resolve-DnsName -Name nodepost.home -Server localhost -Type TXTThe docker-compose.yml includes:
- DNS Server: Main application (port 53)
- MongoDB: Database for records and blocklist (port 27017)
- Mongo Express: Web UI for MongoDB (port 8081)
- Redis: Cache layer (port 6379)
# Start all services
docker-compose up -d
# View logs
docker-compose logs -f dns-server
# Stop all services
docker-compose down
# Restart DNS server
docker-compose restart dns-serverModify config/constant.js to change upstream DNS providers:
export const UPSTREAM_DNS = [
{ address: '1.1.1.1', port: 53 }, // Cloudflare DNS
{ address: '8.8.8.8', port: 53 }, // Google DNS
]- Home Lab DNS: Manage custom domains for your home network and internal services
- Development Environment: Local DNS for microservices, testing, and dev workflows
- Ad Blocking: Block advertising and tracking domains network-wide
- Mail Server Setup: Configure MX, SPF, and DKIM records for email infrastructure
- Network Filtering: Control domain access and implement parental controls
- Performance Optimization: Cache frequently accessed DNS records for faster resolution
- Custom CDN/Load Balancing: Route traffic to specific servers based on domain names
- IoT Device Management: Assign friendly domain names to IoT devices on your network
- Privacy Enhancement: Prevent DNS queries from leaking to ISP by using your own resolver
- Local Service Discovery: Enable easy access to local services without remembering IP addresses
- Testing DNS Changes: Test DNS configurations before deploying to production
Nirajan Paudel
- LinkedIn: www.linkedin.com/in/nirajan-paudel-a9b052265
- GitHub: https://github.com/neerajann