A comprehensive DNS monitoring system built with Django that replicates the functionality of zonewatcher.com. Monitor DNS records across multiple providers, track changes, and receive real-time alerts via email, webhooks, Slack, Teams, and Discord.
- DNS Record Monitoring: Monitor all major DNS record types (A, AAAA, MX, TXT, CNAME, NS, SOA, SRV, CAA, PTR)
- Real-Time Change Detection: Instantly detect and log DNS record changes with full audit history
- Multi-Provider Support: Direct API integration with major DNS providers:
- Cloudflare
- AWS Route53
- Google Cloud DNS
- Azure DNS
- DigitalOcean DNS
- DNS Made Easy
- Public DNS (any domain)
- Automatically scan domains for common DNS records
- Pre-configured with 5000+ most common subdomain names
- Discover records without DNS provider API access
- Multiple Channels:
- Webhooks (custom HTTP endpoints)
- Slack
- Microsoft Teams
- Discord
- Flexible Filtering: Configure which change types trigger notifications
- Maintenance Windows: Suppress alerts during planned maintenance
- Domain expiration monitoring via WHOIS
- Complete change history with unlimited retention
- RESTful API for programmatic access
- Modern web interface with Tailwind CSS
- Django admin panel for easy management
- Python 3.11+
- pip
- SQLite (default) or PostgreSQL (recommended for production)
Use the provided setup script for automated installation:
Linux/macOS:
git clone <repository-url>
cd zonewatcher-poc
chmod +x setup.sh
./setup.shWindows:
git clone <repository-url>
cd zonewatcher-poc
setup.batThe setup script will automatically:
- Create and activate a virtual environment (.venv)
- Install all dependencies
- Run database migrations
- Create necessary directories
- Prompt to create a superuser
If you prefer manual installation:
- Clone the repository
git clone <repository-url>
cd zonewatcher-poc- Create necessary directories
mkdir -p logs static staticfiles- Create and activate virtual environment
# Create virtual environment
python3 -m venv .venv
# Activate virtual environment
# On Linux/macOS:
source .venv/bin/activate
# On Windows:
# .venv\Scripts\activate- Install dependencies
pip install -r requirements.txt- Run migrations
python manage.py migrate- Create a superuser
python manage.py createsuperuser- Run the development server
# For local access only
python manage.py runserver
# For access from other machines (specify IP and port)
python manage.py runserver 0.0.0.0:8000Once the server is running:
-
Local access:
- Web Interface: http://localhost:8000
- Admin Panel: http://localhost:8000/admin
- API: http://localhost:8000/api/
-
Network access: (when using
0.0.0.0:8000)- Replace
localhostwith your server's IP address - Example: http://192.168.1.100:8000
- Replace
Note: The application is configured with ALLOWED_HOSTS = ['*'] for development, allowing access from any IP. For production, restrict this to specific domains.
Note: Always ensure your virtual environment is activated before running any Python/Django commands:
# Linux/macOS
source .venv/bin/activate
# Windows
.venv\Scripts\activateFor more details on virtual environment usage, see VIRTUAL_ENV_GUIDE.md
The project uses a minimal set of required dependencies with optional packages you can enable as needed.
Required dependencies (automatically installed):
- Django 5.0.1
- Django REST Framework
- dnspython
- requests
- python-dateutil
- pytz
Optional dependencies (commented out in requirements.txt):
- PostgreSQL:
psycopg2-binary- For production database (recommended) - DNS Providers: Individual provider SDKs (Cloudflare, AWS Route53, Google Cloud, Azure, DigitalOcean)
- Email:
sendgrid- For advanced email features - Additional Django packages: CORS headers, filtering, static file serving
To enable optional dependencies, edit requirements.txt and uncomment the packages you need, then run:
pip install -r requirements.txtBy default, the application uses SQLite. For production, configure PostgreSQL in zonewatcher/settings.py:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'zonewatcher',
'USER': 'your_user',
'PASSWORD': 'your_password',
'HOST': 'localhost',
'PORT': '5432',
}
}Configure email settings in zonewatcher/settings.py:
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = 'your-email@gmail.com'
EMAIL_HOST_PASSWORD = 'your-app-password'
DEFAULT_FROM_EMAIL = 'noreply@yourdomain.com'Navigate to Admin Panel → DNS Providers → Add DNS Provider
Cloudflare Example:
- Name: My Cloudflare Account
- Provider Type: Cloudflare
- API Key: Your Cloudflare API token
- Is Active: Yes
Public DNS Example:
- Name: Public DNS
- Provider Type: Public DNS
- (No API key needed)
Admin Panel → Domains → Add Domain
- Name: example.com
- Provider: Select your DNS provider
- Check Interval: 300 (seconds)
- Status: Active
Admin Panel → Notification Channels → Add
- Name: Email Alerts
- Channel Type: Email
- Email Address: alerts@yourdomain.com
- Notify on Create/Modify/Delete: Yes
- Name: Custom Webhook
- Channel Type: Webhook
- Webhook URL: https://your-endpoint.com/webhook
- Webhook Method: POST
- Webhook Headers:
{"Authorization": "Bearer token"}
- Name: Slack Channel
- Channel Type: Slack
- Webhook URL: https://hooks.slack.com/services/YOUR/WEBHOOK/URL
Admin Panel → Domain Notification Channels → Add
- Domain: example.com
- Channel: Select notification channel
Via API:
curl -X POST http://localhost:8000/api/domains/{id}/check_now/ \
-H "Authorization: Bearer YOUR_TOKEN"Use the public DNS scanner to discover records:
curl -X POST http://localhost:8000/api/domains/scan/ \
-H "Content-Type: application/json" \
-d '{
"domain_name": "example.com",
"limit": 5000
}'The API uses session authentication. To access protected endpoints:
- Login via the web interface at
/api-auth/login/ - Use session cookies for subsequent requests
GET /api/domains/ List all domains
POST /api/domains/ Create domain
GET /api/domains/{id}/ Get domain details
PUT /api/domains/{id}/ Update domain
DELETE /api/domains/{id}/ Delete domain
POST /api/domains/{id}/check_now/ Trigger immediate check
GET /api/domains/{id}/records/ Get domain records
GET /api/domains/{id}/history/ Get change history
POST /api/domains/scan/ Scan domain
GET /api/providers/ List providers
POST /api/providers/ Create provider
GET /api/providers/{id}/ Get provider
PUT /api/providers/{id}/ Update provider
DELETE /api/providers/{id}/ Delete provider
POST /api/providers/{id}/test_connection/ Test connection
GET /api/dns-records/ List all records
GET /api/dns-records/{id}/ Get record details
GET /api/history/ List all changes
GET /api/history/{id}/ Get change details
GET /api/notification-channels/ List channels
POST /api/notification-channels/ Create channel
GET /api/notification-channels/{id}/ Get channel
PUT /api/notification-channels/{id}/ Update channel
DELETE /api/notification-channels/{id}/ Delete channel
POST /api/notification-channels/{id}/test/ Send test notification
GET /api/notification-logs/ List notification logs
GET /api/notification-logs/{id}/ Get log details
GET /api/maintenance-windows/ List windows
POST /api/maintenance-windows/ Create window
GET /api/maintenance-windows/{id}/ Get window
PUT /api/maintenance-windows/{id}/ Update window
DELETE /api/maintenance-windows/{id}/ Delete window
curl -X GET http://localhost:8000/api/domains/ \
-H "Content-Type: application/json"curl -X POST http://localhost:8000/api/domains/ \
-H "Content-Type: application/json" \
-d '{
"name": "example.com",
"provider": 1,
"status": "active",
"check_interval": 300
}'curl -X POST http://localhost:8000/api/domains/1/check_now/curl -X GET http://localhost:8000/api/domains/1/history/curl -X POST http://localhost:8000/api/notification-channels/ \
-H "Content-Type: application/json" \
-d '{
"name": "My Webhook",
"channel_type": "webhook",
"webhook_url": "https://example.com/webhook",
"webhook_method": "POST",
"is_active": true,
"notify_on_create": true,
"notify_on_modify": true,
"notify_on_delete": true
}'When DNS changes are detected, webhooks receive the following JSON payload:
{
"event": "dns_record_change",
"domain": "example.com",
"change_type": "modified",
"record": {
"type": "A",
"name": "www",
"old_value": "1.2.3.4",
"new_value": "5.6.7.8",
"old_ttl": 300,
"new_ttl": 600,
"old_priority": null,
"new_priority": null
},
"detected_at": "2024-01-15T10:30:00Z",
"summary": "Modified A record www: Value: 1.2.3.4 → 5.6.7.8, TTL: 300 → 600"
}zonewatcher-poc/
├── core/ # Core DNS monitoring functionality
│ ├── models.py # Domain, DNSRecord, DNSRecordHistory models
│ ├── dns_monitor.py # DNS monitoring service
│ └── admin.py # Admin configuration
├── providers/ # DNS provider integrations
│ ├── dns_providers.py # Provider implementations
│ └── common_subdomains.py # 5000+ common subdomain list
├── notifications/ # Notification system
│ ├── models.py # NotificationChannel, NotificationLog
│ ├── notification_service.py # Notification handlers
│ └── admin.py # Admin configuration
├── api/ # REST API
│ ├── serializers.py # API serializers
│ ├── views.py # API viewsets
│ └── urls.py # API URL configuration
├── templates/ # HTML templates
│ └── index.html # Landing page
├── static/ # Static files
├── zonewatcher/ # Django project settings
│ ├── settings.py
│ └── urls.py
└── manage.py
- DNSProvider: DNS provider configurations (API credentials)
- Domain: Domains to monitor
- DNSRecord: Current DNS records
- DNSRecordHistory: Change history log
- NotificationChannel: Notification configurations
- NotificationLog: Notification delivery log
- MaintenanceWindow: Scheduled maintenance periods
- DomainExpirationAlert: Domain expiration tracking
The system includes integration code for multiple DNS providers. To use a specific provider, uncomment the corresponding package in requirements.txt and install it.
- Installation: None required (works out of the box)
- Use case: Monitor any publicly accessible domain
- Queries public DNS servers (Google DNS, Cloudflare DNS)
- Scans 5000+ common subdomains
- Perfect for getting started or monitoring external domains
- Installation: Uncomment
cloudflare==2.19.2in requirements.txt - Requirements: API token with DNS read permissions
- Automatic zone discovery
- Full record type support
- Installation: Uncomment
boto3==1.34.34in requirements.txt - Requirements: AWS access key and secret key
- Automatic hosted zone discovery
- Supports all Route53 record types
- Installation: Uncomment
google-cloud-dns==0.35.0in requirements.txt - Requirements: Service account JSON credentials and Project ID
- Supports all Google Cloud DNS record types
- Installation: Uncomment
azure-mgmt-dns==8.1.0in requirements.txt - Requirements: Azure credentials
- Installation: Uncomment
python-digitalocean==1.17.0in requirements.txt - Requirements: DigitalOcean API token
To enable automatic DNS monitoring, set up a scheduled task (cron or similar):
# Example management command (create in core/management/commands/)
from django.core.management.base import BaseCommand
from core.models import Domain
from core.dns_monitor import DNSMonitor
from django.utils import timezone
class Command(BaseCommand):
help = 'Check all active domains'
def handle(self, *args, **options):
domains = Domain.objects.filter(
status='active',
next_check__lte=timezone.now()
)
for domain in domains:
monitor = DNSMonitor(domain)
result = monitor.check_domain()
self.stdout.write(f"Checked {domain.name}: {result}")Run via cron:
*/5 * * * * cd /path/to/zonewatcher-poc && python manage.py check_domains- API Keys: Store sensitive credentials securely, use environment variables
- HTTPS: Always use HTTPS in production
- Authentication: Enable authentication for API endpoints
- Rate Limiting: Implement rate limiting for public-facing APIs
- Input Validation: All user inputs are validated via Django forms/serializers
- SQL Injection: Protected by Django ORM
- XSS: Protected by Django template escaping
This guide provides step-by-step instructions for deploying DNSTrailer in production using uWSGI, nginx, and systemd.
- Ubuntu/Debian Linux server
- Python 3.11+
- nginx web server
- sudo access
ALLOWED_HOSTS = ['*']) for development convenience. This MUST be changed for production!
-
Set
DEBUG = Falsein settings.pyDEBUG = False
-
Configure
ALLOWED_HOSTSwith your specific domainsALLOWED_HOSTS = ['yourdomain.com', 'www.yourdomain.com', 'your-ip-address']
-
Use PostgreSQL instead of SQLite for better performance and reliability
-
Set up proper SMTP for email notifications via the web interface at
/portal/settings/
sudo apt update
sudo apt install -y python3.11 python3.11-venv python3-pip nginx# Clone the repository
cd /home/yourusername
git clone <repository-url> zonewatcher-poc
cd zonewatcher-poc
# Create virtual environment
python3 -m venv .venv
source .venv/bin/activate
# Install dependencies
pip install -r requirements.txt
# Install uWSGI
pip install uwsgi
# Create necessary directories
mkdir -p logs staticfiles media
# Run migrations
python manage.py migrate
# Collect static files
python manage.py collectstatic --noinput
# Create superuser
python manage.py createsuperuserThe repository includes a production-ready uWSGI configuration at uwsgi-production.ini. Verify the paths match your installation:
[uwsgi]
# Django-related settings
chdir = /home/yourusername/zonewatcher-poc
module = zonewatcher.wsgi:application
home = /home/yourusername/zonewatcher-poc/.venv
# Process-related settings
master = true
processes = 4
threads = 2
enable-threads = true
# Socket (for nginx)
socket = /home/yourusername/zonewatcher-poc/uwsgi.sock
chmod-socket = 666
vacuum = true
# Logging
logto = /home/yourusername/zonewatcher-poc/logs/uwsgi.log
disable-logging = false
# Performance
buffer-size = 65535
harakiri = 300
socket-timeout = 60
# Environment
env = DJANGO_SETTINGS_MODULE=zonewatcher.settings
pythonpath = /home/yourusername/zonewatcher-poc
# Reload
touch-reload = /home/yourusername/zonewatcher-poc/reload.txt
die-on-term = trueCopy the service file to systemd:
sudo cp dnstrailer.service /etc/systemd/system/dns-trailer.serviceEdit the service file if needed to match your username:
sudo nano /etc/systemd/system/dns-trailer.serviceThe service file should look like this:
[Unit]
Description=DNSTrailer uWSGI Service
After=network.target
[Service]
Type=simple
User=yourusername
Group=www-data
WorkingDirectory=/home/yourusername/zonewatcher-poc
Environment="PATH=/home/yourusername/zonewatcher-poc/.venv/bin:/usr/bin"
ExecStart=/home/yourusername/zonewatcher-poc/.venv/bin/uwsgi --ini /home/yourusername/zonewatcher-poc/uwsgi-production.ini
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.targetCopy and configure the nginx configuration:
sudo cp nginx-dnstrailer.conf /etc/nginx/sites-available/nginx-dnstrailer.confEdit the file to match your setup:
sudo nano /etc/nginx/sites-available/nginx-dnstrailer.confThe configuration should look like this:
upstream dnstrailer {
server unix:///home/yourusername/zonewatcher-poc/uwsgi.sock;
}
server {
listen 8002;
server_name your-server-ip dnstrailer.local;
charset utf-8;
# Max upload size
client_max_body_size 10M;
# Logs
access_log /var/log/nginx/dnstrailer_access.log;
error_log /var/log/nginx/dnstrailer_error.log;
# Django static files
location /static {
alias /home/yourusername/zonewatcher-poc/staticfiles;
expires 30d;
add_header Cache-Control "public, immutable";
}
# Django media files
location /media {
alias /home/yourusername/zonewatcher-poc/media;
expires 7d;
}
# Send all other requests to Django
location / {
uwsgi_pass dnstrailer;
include uwsgi_params;
uwsgi_read_timeout 300;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
}
# Health check endpoint
location /health {
access_log off;
return 200 "OK\n";
add_header Content-Type text/plain;
}
}Enable the site:
sudo ln -s /etc/nginx/sites-available/nginx-dnstrailer.conf /etc/nginx/sites-enabled/Critical for nginx to access the uWSGI socket:
# Directory permissions (nginx must be able to traverse to the socket)
chmod 755 /home/yourusername
chmod 755 /home/yourusername/zonewatcher-poc
# Project ownership
sudo chown -R yourusername:www-data /home/yourusername/zonewatcher-poc
# Add user to www-data group
sudo usermod -a -G www-data yourusername
# Ensure logs directory exists
mkdir -p /home/yourusername/zonewatcher-poc/logs# Reload systemd daemon
sudo systemctl daemon-reload
# Enable service to start on boot
sudo systemctl enable dns-trailer.service
# Start the service
sudo systemctl start dns-trailer.service
# Check service status
sudo systemctl status dns-trailer.service
# Test nginx configuration
sudo nginx -t
# Reload nginx
sudo systemctl reload nginx# Check service is running
sudo systemctl status dns-trailer.service
# Verify socket exists and has correct permissions
ls -la /home/yourusername/zonewatcher-poc/uwsgi.sock
# Test nginx can access socket
sudo -u www-data test -r /home/yourusername/zonewatcher-poc/uwsgi.sock && echo "OK" || echo "FAIL"
# Test application
curl http://your-server-ip:8002
# Or access via browser
# http://your-server-ip:8002# Restart the service
sudo systemctl restart dns-trailer.service
# Stop the service
sudo systemctl stop dns-trailer.service
# View service logs
sudo journalctl -u dns-trailer.service -f
# View uWSGI logs
tail -f ~/zonewatcher-poc/logs/uwsgi.log
# View nginx logs
sudo tail -f /var/log/nginx/dnstrailer_error.log
sudo tail -f /var/log/nginx/dnstrailer_access.log
# Hot reload Django (without restarting service)
touch ~/zonewatcher-poc/reload.txt-
Check uWSGI service is running:
sudo systemctl status dns-trailer.service
-
Verify socket exists and permissions are correct:
ls -la /home/yourusername/zonewatcher-poc/uwsgi.sock # Should show: srw-rw-rw- -
Check directory permissions:
namei -l /home/yourusername/zonewatcher-poc/uwsgi.sock # All directories should have at least 755 (drwxr-xr-x) -
Test nginx can access socket:
sudo -u www-data test -r /home/yourusername/zonewatcher-poc/uwsgi.sock && echo "OK" || echo "FAIL"
-
Check logs for errors:
tail -50 ~/zonewatcher-poc/logs/uwsgi.log sudo tail -50 /var/log/nginx/dnstrailer_error.log
- Check for syntax errors in uwsgi-production.ini
- Verify virtual environment exists and has correct packages
- Check service logs:
sudo journalctl -u dns-trailer.service -n 50
- Collect static files:
python manage.py collectstatic --noinput - Verify nginx has permission to read staticfiles directory
- Check nginx configuration paths match actual directories
For production, set up SSL/TLS certificates using Let's Encrypt:
# Install certbot
sudo apt install certbot python3-certbot-nginx
# Obtain certificate (replace with your domain)
sudo certbot --nginx -d yourdomain.com
# Certificates auto-renew via cronFor production, set up automated PostgreSQL backups:
# Create backup script
cat > ~/backup-db.sh << 'EOF'
#!/bin/bash
BACKUP_DIR="/home/yourusername/backups"
DATE=$(date +%Y%m%d_%H%M%S)
mkdir -p $BACKUP_DIR
pg_dump dnstrailer > $BACKUP_DIR/dnstrailer_$DATE.sql
# Keep only last 7 days
find $BACKUP_DIR -name "dnstrailer_*.sql" -mtime +7 -delete
EOF
chmod +x ~/backup-db.sh
# Add to crontab (daily at 2 AM)
(crontab -l 2>/dev/null; echo "0 2 * * * /home/yourusername/backup-db.sh") | crontab -- Check network connectivity
- Verify DNS provider API credentials
- Ensure firewall allows DNS queries (port 53)
- Check notification channel configuration
- Verify webhook URLs are accessible
- Check email SMTP settings
- Review notification logs in admin panel
- Increase check interval for domains
- Optimize database with indexes
- Use PostgreSQL for better performance
- Implement caching for frequently accessed data
Contributions are welcome! Please feel free to submit pull requests or open issues.
This project is provided as-is for educational and testing purposes.
For issues and questions, please use the GitHub issue tracker.