Skip to content

neerajann/dns-server

Repository files navigation

Custom DNS Server

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.

Features

  • 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

Architecture

          [ 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     │
     └─────────────────────────┘

How It Works

The DNS server processes queries through a multi-layered resolution strategy for optimal performance and control:

Query Resolution Flow

  1. 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)
  2. 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
  3. 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
  4. 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

Design Decisions

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

Prerequisites

  • 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.

Quick Start

  1. Clone the repository:
git clone https://github.com/neerajann/dns-server.git
cd dns-server
  1. Start all services:
docker-compose up -d
  1. Test the DNS server:
dig @localhost example.com

Development Mode

To run in development mode with auto-reload:

npm install
npm run dev

Make sure MongoDB and Redis are running locally update .env with your connection strings.

Managing DNS Records

Add DNS Records

  1. Open Mongo Express at http://localhost:8081
  2. Select the dns database
  3. Go to records collection
  4. Click "New Document"
  5. 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"]
    }
  ]
}

Add to Blocklist

  1. Open Mongo Express at http://localhost:8081
  2. Select the dns database
  3. Go to blocklist collection
  4. Click "New Document"
  5. 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

Supported Record Types

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.

Testing Your DNS Server

Using dig (Linux/macOS/WSL)

# Query A record
dig @localhost nodepost.home

# Query MX record
dig @localhost nodepost.home MX

# Query TXT record
dig @localhost nodepost.home TXT

Using PowerShell (Windows)

# 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 TXT

Docker Services

The 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)

Useful Commands

# 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-server

Configuration

Upstream DNS Servers

Modify 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
]

Use Cases

  • 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

Author

Nirajan Paudel


About

A High-performance DNS server with custom record handling, domain blocking, caching and upstream resolution. Built on Node.js, MongoDB and Redis, perfect for home labs and dev environments where speed and control matter.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors