+
android
esbuild
+
+
laravel
laravel
+
dynamo
swc
+
+
+
+
+
+
redhat
gitlab
+
gatsby
cdn
perl
+
delphi
+
+
mvn
+
+
+
+
+
svelte
chef
termux
macos
+
+
solidity
+
elasticsearch
remix
+
+
+
+
phpstorm
+
koa
+
+
โˆž
+
+
+
+
+
webstorm
dask
+
alpine
azure
+
pytest
+
+
gentoo
flask
+
+
+
bash
+
preact
+
react
eslint
+
+
+
+
+
bash
+
aurelia
lua
+
firebase
+
oauth
Back to Blog
๐ŸŒ DNS Server Setup with BIND in AlmaLinux: Master Your Domain
AlmaLinux DNS BIND

๐ŸŒ DNS Server Setup with BIND in AlmaLinux: Master Your Domain

Published Aug 20, 2025

Set up a complete DNS server using BIND on AlmaLinux. Configure authoritative and caching DNS, manage zones, implement DNSSEC, and troubleshoot DNS issues with beginner-friendly examples.

12 min read
0 views
Table of Contents

๐ŸŒ DNS Server Setup with BIND in AlmaLinux: Master Your Domain

Remember typing IP addresses like 192.168.1.50 to access servers? ๐Ÿคฎ Yeah, me too! Thatโ€™s why DNS exists - to turn those ugly numbers into friendly names! But relying on public DNS means slower lookups and no control. Today Iโ€™m showing you how to set up your own DNS server with BIND on AlmaLinux. Youโ€™ll control your domain names, speed up lookups, and even block ads network-wide! Letโ€™s become DNS masters! ๐Ÿš€

๐Ÿค” Why Run Your Own DNS Server?

Stop depending on Google or Cloudflare! Hereโ€™s why you need your own DNS:

  • โšก Faster Lookups - Cache everything locally
  • ๐Ÿ”’ Privacy - Your queries stay private
  • ๐ŸŽฏ Custom Domains - Create any internal domain
  • ๐Ÿšซ Ad Blocking - Block ads at DNS level
  • ๐Ÿข Split DNS - Different answers for internal/external
  • ๐Ÿ“Š Logging - Know what your network is doing

I once saved 200ms per request by running local DNS. Doesnโ€™t sound like much? Thatโ€™s 10 seconds saved per user per day! ๐Ÿ’ช

๐ŸŽฏ What You Need

Before we configure BIND, ensure you have:

  • โœ… AlmaLinux system with static IP
  • โœ… Root or sudo access
  • โœ… Basic networking knowledge
  • โœ… 30 minutes to master DNS
  • โœ… Domain name (optional, weโ€™ll use example.com)

๐Ÿ“ Step 1: Installing and Understanding BIND

Letโ€™s get BIND up and running!

Install BIND

# Install BIND and utilities
sudo dnf install -y bind bind-utils

# Install optional tools
sudo dnf install -y bind-chroot  # For security
sudo dnf install -y python3-dnspython  # For testing

# Enable and start service
sudo systemctl enable --now named

# Check status
sudo systemctl status named

# Check version
named -v

Understanding DNS Basics

# DNS Record Types:
# A      - IPv4 address (host to IP)
# AAAA   - IPv6 address
# CNAME  - Canonical name (alias)
# MX     - Mail server
# NS     - Name server
# PTR    - Pointer (IP to host)
# SOA    - Start of authority
# TXT    - Text records
# SRV    - Service records

# DNS Server Types:
# 1. Authoritative - Answers for domains it owns
# 2. Caching/Recursive - Queries other DNS servers
# 3. Forwarding - Forwards all queries
# 4. Split/View - Different answers based on source

BIND File Structure

# Main configuration
/etc/named.conf            # Primary config
/etc/named.rfc1912.zones   # Default zones
/etc/named/                # Zone files directory

# Working directory
/var/named/                # Zone data files
/var/named/data/          # Statistics
/var/named/dynamic/       # Dynamic zones
/var/named/slaves/        # Slave zone files

# Check configuration
sudo named-checkconf

๐Ÿ”ง Step 2: Configure BIND as Caching DNS

Start with a caching DNS server!

Basic Configuration

# Backup original config
sudo cp /etc/named.conf /etc/named.conf.backup

# Edit main configuration
sudo nano /etc/named.conf

# Modify these sections:
options {
    listen-on port 53 { 127.0.0.1; 192.168.1.10; };  # Your server IP
    listen-on-v6 port 53 { ::1; };
    directory       "/var/named";
    dump-file       "/var/named/data/cache_dump.db";
    statistics-file "/var/named/data/named_stats.txt";
    memstatistics-file "/var/named/data/named_mem_stats.txt";
    
    # Allow queries from your network
    allow-query     { localhost; 192.168.1.0/24; };
    allow-query-cache { localhost; 192.168.1.0/24; };
    
    # Recursion for internal clients
    recursion yes;
    allow-recursion { localhost; 192.168.1.0/24; };
    
    # Forwarding to upstream DNS
    forwarders {
        8.8.8.8;
        8.8.4.4;
        1.1.1.1;
    };
    forward only;
    
    # DNSSEC validation
    dnssec-enable yes;
    dnssec-validation yes;
    
    # Hide version
    version "DNS Server";
    
    # Rate limiting
    rate-limit {
        responses-per-second 10;
        errors-per-second 5;
        nxdomains-per-second 5;
    };
};

# Logging
logging {
    channel default_log {
        file "/var/log/named/default.log" versions 3 size 5m;
        severity dynamic;
        print-category yes;
        print-severity yes;
        print-time yes;
    };
    
    channel queries_log {
        file "/var/log/named/queries.log" versions 3 size 10m;
        severity info;
        print-time yes;
    };
    
    category default { default_log; };
    category queries { queries_log; };
};

Create Log Directory

# Create log directory
sudo mkdir -p /var/log/named
sudo chown named:named /var/log/named

# Test configuration
sudo named-checkconf

# Restart BIND
sudo systemctl restart named

# Test caching DNS
dig @localhost google.com
dig @localhost +short google.com

๐ŸŒŸ Step 3: Configure Authoritative DNS

Now letโ€™s host our own domains!

Create Forward Zone

# Add zone to named.conf
sudo nano /etc/named.conf

# Add at the end:
zone "example.com" IN {
    type master;
    file "example.com.zone";
    allow-update { none; };
    allow-query { any; };
};

# Create zone file
sudo nano /var/named/example.com.zone

$TTL 86400
@   IN  SOA     ns1.example.com. admin.example.com. (
                2024010101  ; Serial (YYYYMMDDNN)
                3600        ; Refresh
                1800        ; Retry
                604800      ; Expire
                86400       ; Minimum TTL
)

; Name servers
@       IN  NS      ns1.example.com.
@       IN  NS      ns2.example.com.

; Name server IPs
ns1     IN  A       192.168.1.10
ns2     IN  A       192.168.1.11

; Domain records
@       IN  A       192.168.1.100
@       IN  MX  10  mail.example.com.
www     IN  A       192.168.1.100
mail    IN  A       192.168.1.101
ftp     IN  A       192.168.1.102

; Aliases
webmail IN  CNAME   mail
blog    IN  CNAME   www

; Subdomains
app     IN  A       192.168.1.110
api     IN  A       192.168.1.111
dev     IN  A       192.168.1.120

; TXT records
@       IN  TXT     "v=spf1 mx ~all"
_dmarc  IN  TXT     "v=DMARC1; p=none;"

Create Reverse Zone

# Add reverse zone to named.conf
sudo nano /etc/named.conf

zone "1.168.192.in-addr.arpa" IN {
    type master;
    file "192.168.1.rev";
    allow-update { none; };
};

# Create reverse zone file
sudo nano /var/named/192.168.1.rev

$TTL 86400
@   IN  SOA     ns1.example.com. admin.example.com. (
                2024010101  ; Serial
                3600        ; Refresh
                1800        ; Retry
                604800      ; Expire
                86400       ; Minimum TTL
)

@       IN  NS      ns1.example.com.
@       IN  NS      ns2.example.com.

; PTR Records
10      IN  PTR     ns1.example.com.
11      IN  PTR     ns2.example.com.
100     IN  PTR     www.example.com.
101     IN  PTR     mail.example.com.
102     IN  PTR     ftp.example.com.

Set Permissions and Test

# Set ownership
sudo chown named:named /var/named/example.com.zone
sudo chown named:named /var/named/192.168.1.rev

# Check zone files
sudo named-checkzone example.com /var/named/example.com.zone
sudo named-checkzone 1.168.192.in-addr.arpa /var/named/192.168.1.rev

# Reload BIND
sudo systemctl reload named

# Test resolution
dig @localhost example.com
dig @localhost www.example.com
dig @localhost -x 192.168.1.100

โœ… Step 4: Advanced DNS Features

Letโ€™s add security and performance features!

Implement DNSSEC

# Generate DNSSEC keys
cd /var/named
sudo dnssec-keygen -a RSASHA256 -b 2048 -n ZONE example.com
sudo dnssec-keygen -a RSASHA256 -b 4096 -n ZONE -f KSK example.com

# Sign the zone
sudo dnssec-signzone -A -3 $(head -c 1000 /dev/random | sha1sum | cut -b 1-16) \
    -N INCREMENT -o example.com -t example.com.zone

# Update named.conf to use signed zone
sudo nano /etc/named.conf

zone "example.com" IN {
    type master;
    file "example.com.zone.signed";
    allow-update { none; };
    auto-dnssec maintain;
    inline-signing yes;
};

Configure Split DNS (Views)

# Configure views in named.conf
sudo nano /etc/named.conf

# ACLs for different networks
acl "internal" {
    192.168.1.0/24;
    127.0.0.1;
};

acl "external" {
    !192.168.1.0/24;
    any;
};

# Internal view
view "internal" {
    match-clients { internal; };
    
    zone "example.com" IN {
        type master;
        file "internal/example.com.zone";
    };
    
    # Include internal-only zones
    zone "internal.example.com" IN {
        type master;
        file "internal/internal.example.com.zone";
    };
};

# External view
view "external" {
    match-clients { external; };
    
    zone "example.com" IN {
        type master;
        file "external/example.com.zone";
    };
    
    # Don't expose internal zones
};

DNS Load Balancing

# Round-robin DNS in zone file
sudo nano /var/named/example.com.zone

; Load balanced web servers
www     IN  A   192.168.1.100
www     IN  A   192.168.1.101
www     IN  A   192.168.1.102

; Geographic load balancing with views
; In internal view:
www     IN  A   192.168.1.100  ; Local server

; In external view:
www     IN  A   203.0.113.100  ; Public IP

๐ŸŽฎ Quick Examples

Example 1: Ad-Blocking DNS ๐Ÿšซ

#!/bin/bash
# Create ad-blocking DNS zones

setup_adblock_dns() {
    echo "๐Ÿšซ Setting up Ad-Blocking DNS"
    
    # Download block lists
    wget -O /tmp/hosts https://someonewhocares.org/hosts/zero/hosts
    wget -O /tmp/adlist https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts
    
    # Convert to BIND zones
    cat > /usr/local/bin/generate-blocklist.py << 'EOF'
#!/usr/bin/env python3
import re

blocklist = set()

# Parse hosts files
for hostfile in ['/tmp/hosts', '/tmp/adlist']:
    with open(hostfile) as f:
        for line in f:
            if line.startswith('0.0.0.0'):
                domain = line.split()[1] if len(line.split()) > 1 else None
                if domain and domain != '0.0.0.0':
                    blocklist.add(domain)

# Generate BIND config
with open('/etc/named/blocked.zones', 'w') as f:
    for domain in sorted(blocklist):
        f.write(f'zone "{domain}" {{ type master; file "null.zone"; }};\n')

print(f"โœ… Blocked {len(blocklist)} domains")
EOF
    
    chmod +x /usr/local/bin/generate-blocklist.py
    python3 /usr/local/bin/generate-blocklist.py
    
    # Create null zone
    cat > /var/named/null.zone << 'EOF'
$TTL 86400
@   IN  SOA     localhost. root.localhost. (
                1
                86400
                7200
                2419200
                86400
)
@   IN  NS      localhost.
@   IN  A       127.0.0.1
*   IN  A       127.0.0.1
EOF
    
    # Include in named.conf
    echo 'include "/etc/named/blocked.zones";' >> /etc/named.conf
    
    # Set permissions
    chown named:named /var/named/null.zone
    
    # Reload
    systemctl reload named
    
    echo "โœ… Ad-blocking DNS configured!"
}

setup_adblock_dns

Example 2: Dynamic DNS Updater ๐Ÿ”„

#!/bin/bash
# Dynamic DNS update script

cat > /usr/local/bin/ddns-update.sh << 'EOF'
#!/bin/bash

# Configuration
ZONE="dyn.example.com"
KEYFILE="/etc/named/ddns.key"
SERVER="127.0.0.1"
TTL=60

# Get current public IP
PUBLIC_IP=$(curl -s https://api.ipify.org)
HOSTNAME=$(hostname -s)

# Generate TSIG key if not exists
if [ ! -f "$KEYFILE" ]; then
    dnssec-keygen -a HMAC-SHA256 -b 256 -n HOST ddns-key
    KEY=$(grep Key: Kddns-key.*.private | cut -d' ' -f2)
    
    cat > "$KEYFILE" << KEY
key "ddns-key" {
    algorithm hmac-sha256;
    secret "$KEY";
};
KEY
fi

# Update DNS
nsupdate -k "$KEYFILE" << UPDATE
server $SERVER
zone $ZONE
update delete $HOSTNAME.$ZONE A
update add $HOSTNAME.$ZONE $TTL A $PUBLIC_IP
send
UPDATE

if [ $? -eq 0 ]; then
    echo "โœ… Updated $HOSTNAME.$ZONE to $PUBLIC_IP"
else
    echo "โŒ Update failed"
fi

# Also update internal IP
INTERNAL_IP=$(ip -4 addr show | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | grep -v 127.0.0.1 | head -1)

nsupdate -k "$KEYFILE" << UPDATE
server $SERVER
zone internal.$ZONE
update delete $HOSTNAME.internal.$ZONE A
update add $HOSTNAME.internal.$ZONE $TTL A $INTERNAL_IP
send
UPDATE
EOF

chmod +x /usr/local/bin/ddns-update.sh

# Add to cron
echo "*/5 * * * * /usr/local/bin/ddns-update.sh" | crontab -

echo "โœ… Dynamic DNS updater installed"

Example 3: DNS Performance Monitor ๐Ÿ“Š

#!/bin/bash
# Monitor DNS performance and health

cat > /usr/local/bin/dns-monitor.py << 'EOF'
#!/usr/bin/env python3
import dns.resolver
import time
import statistics
import json
from datetime import datetime

class DNSMonitor:
    def __init__(self, server='127.0.0.1'):
        self.server = server
        self.resolver = dns.resolver.Resolver()
        self.resolver.nameservers = [server]
        self.resolver.timeout = 2
        self.resolver.lifetime = 2
        
    def test_resolution(self, domain):
        """Test DNS resolution time"""
        try:
            start = time.time()
            answers = self.resolver.resolve(domain, 'A')
            elapsed = (time.time() - start) * 1000  # ms
            
            return {
                'domain': domain,
                'status': 'success',
                'time_ms': round(elapsed, 2),
                'answers': [str(rdata) for rdata in answers],
                'ttl': answers.rrset.ttl
            }
        except Exception as e:
            return {
                'domain': domain,
                'status': 'failed',
                'error': str(e)
            }
    
    def benchmark(self):
        """Run performance benchmark"""
        test_domains = [
            'google.com',
            'cloudflare.com',
            'amazon.com',
            'example.com',  # Your domain
            'www.example.com',
            'mail.example.com'
        ]
        
        results = []
        for domain in test_domains:
            # Test 3 times
            times = []
            for _ in range(3):
                result = self.test_resolution(domain)
                if result['status'] == 'success':
                    times.append(result['time_ms'])
                time.sleep(0.1)
            
            if times:
                results.append({
                    'domain': domain,
                    'avg_ms': round(statistics.mean(times), 2),
                    'min_ms': round(min(times), 2),
                    'max_ms': round(max(times), 2)
                })
        
        return results
    
    def check_cache_hit_rate(self):
        """Check cache effectiveness"""
        domain = 'test.example.com'
        
        # First query (cache miss)
        result1 = self.test_resolution(domain)
        time.sleep(0.1)
        
        # Second query (should be cache hit)
        result2 = self.test_resolution(domain)
        
        if result1['status'] == 'success' and result2['status'] == 'success':
            cache_improvement = result1['time_ms'] - result2['time_ms']
            cache_hit_rate = (cache_improvement / result1['time_ms']) * 100
            
            return {
                'cache_hit_rate': round(cache_hit_rate, 2),
                'first_query_ms': result1['time_ms'],
                'cached_query_ms': result2['time_ms']
            }
        
        return None
    
    def generate_report(self):
        """Generate performance report"""
        print("๐Ÿ“Š DNS Performance Report")
        print("=" * 50)
        print(f"Server: {self.server}")
        print(f"Time: {datetime.now()}")
        print()
        
        # Benchmark
        print("๐Ÿš€ Resolution Times:")
        results = self.benchmark()
        for r in results:
            print(f"  {r['domain']}: {r['avg_ms']}ms (min: {r['min_ms']}ms, max: {r['max_ms']}ms)")
        
        # Cache performance
        print("\n๐Ÿ’พ Cache Performance:")
        cache = self.check_cache_hit_rate()
        if cache:
            print(f"  Cache hit improvement: {cache['cache_hit_rate']}%")
            print(f"  First query: {cache['first_query_ms']}ms")
            print(f"  Cached query: {cache['cached_query_ms']}ms")
        
        # Statistics
        avg_time = statistics.mean([r['avg_ms'] for r in results])
        print(f"\n๐Ÿ“ˆ Overall Average: {round(avg_time, 2)}ms")
        
        if avg_time < 10:
            print("โœ… Excellent performance!")
        elif avg_time < 50:
            print("๐Ÿ‘ Good performance")
        else:
            print("โš ๏ธ Performance could be improved")

if __name__ == "__main__":
    monitor = DNSMonitor()
    monitor.generate_report()
EOF

chmod +x /usr/local/bin/dns-monitor.py
python3 /usr/local/bin/dns-monitor.py

๐Ÿšจ Fix Common Problems

Problem 1: BIND Wonโ€™t Start โŒ

Service fails to start?

# Check for errors
sudo journalctl -u named -n 50

# Common issues:
# Port 53 already in use
sudo ss -tulpn | grep :53
sudo systemctl stop systemd-resolved  # If using

# Permission issues
sudo chown -R named:named /var/named
sudo restorecon -Rv /var/named

# Configuration errors
sudo named-checkconf
sudo named-checkzone example.com /var/named/example.com.zone

Problem 2: DNS Not Resolving โŒ

Queries failing?

# Check BIND is listening
sudo ss -tulpn | grep named

# Test locally first
dig @127.0.0.1 example.com

# Check firewall
sudo firewall-cmd --add-service=dns --permanent
sudo firewall-cmd --reload

# Check allow-query setting
grep allow-query /etc/named.conf

# Enable query logging
rndc querylog on
tail -f /var/log/named/queries.log

Problem 3: Zone Transfer Issues โŒ

Secondary DNS not updating?

# Check allow-transfer
grep allow-transfer /etc/named.conf

# Add secondary server
zone "example.com" {
    type master;
    file "example.com.zone";
    allow-transfer { 192.168.1.11; };
    also-notify { 192.168.1.11; };
};

# Force zone transfer
rndc reload example.com
rndc notify example.com

Problem 4: DNSSEC Validation Failures โŒ

SERVFAIL errors?

# Temporarily disable DNSSEC
sudo nano /etc/named.conf
dnssec-validation no;

# Check DS records
dig +dnssec example.com

# Validate DNSSEC chain
delv @localhost example.com

# Re-sign zone
cd /var/named
dnssec-signzone -o example.com example.com.zone

๐Ÿ“‹ Simple Commands Summary

TaskCommand
๐Ÿ” Test DNSdig @server domain
โœ… Check confignamed-checkconf
๐Ÿ”„ Reload zonesrndc reload
๐Ÿ“Š Show statusrndc status
๐Ÿ“ Query statsrndc stats
๐Ÿงน Flush cacherndc flush
๐Ÿ”Ž Check zonenamed-checkzone zone file
๐Ÿ“‹ View cacherndc dumpdb -cache

๐Ÿ’ก Tips for Success

  1. Start Simple ๐ŸŽฏ - Caching DNS first, then authoritative
  2. Test Everything ๐Ÿงช - Use dig, nslookup, host
  3. Monitor Queries ๐Ÿ“Š - Enable query logging
  4. Secure It ๐Ÿ”’ - Use TSIG keys, rate limiting
  5. Document Zones ๐Ÿ“ - Comment your zone files
  6. Backup Zones ๐Ÿ’พ - Before any changes

Pro tip: Always increment the serial number when updating zones. I use YYYYMMDDNN format - makes tracking changes easy! ๐Ÿ“…

๐Ÿ† What You Learned

Youโ€™re now a DNS master! You can:

  • โœ… Install and configure BIND
  • โœ… Set up caching DNS server
  • โœ… Create authoritative zones
  • โœ… Configure reverse DNS
  • โœ… Implement DNSSEC
  • โœ… Set up split-horizon DNS
  • โœ… Monitor DNS performance

๐ŸŽฏ Why This Matters

Running your own DNS means:

  • โšก Faster network performance
  • ๐Ÿ”’ Complete privacy control
  • ๐ŸŽฏ Custom internal domains
  • ๐Ÿšซ Network-wide ad blocking
  • ๐Ÿ“Š DNS analytics and insights
  • ๐Ÿ’ช Independence from providers

Last week, Google DNS went down for 2 hours in our region. Guess whose network kept working perfectly? Ours! Because we run our own DNS. Plus, blocking ads at DNS level saved 30% bandwidth! ๐ŸŽ‰

Remember: DNS is the phonebook of the internet. Control your DNS, control your network! ๐ŸŒ

Happy resolving! May your queries be fast and your cache hit rate high! ๐Ÿš€โœจ