Automated domain blocking controller for NextDNS with per-domain scheduling
Project description
NextDNS Blocker
Automated system to control domain access with per-domain schedule configuration using the NextDNS API.
Features
- Per-domain scheduling: Configure unique availability hours for each domain
- Flexible time ranges: Multiple time windows per day, different schedules per weekday
- Protected domains: Mark domains as protected to prevent accidental unblocking
- Pause/Resume: Temporarily disable blocking without changing configuration
- Automatic synchronization: Runs every 2 minutes via cron with watchdog protection
- Timezone-aware: Respects configured timezone for schedule evaluation
- Secure: File permissions, input validation, and audit logging
- NextDNS API integration: Works via NextDNS denylist
- Dry-run mode: Preview changes without applying them
- Smart caching: Reduces API calls with intelligent denylist caching
- Rate limiting: Built-in protection against API rate limits
- Exponential backoff: Automatic retries with increasing delays on failures
Requirements
- Python 3.9+
- NextDNS account with API key
- Linux/macOS/Windows
Installation
Option 1: Install from PyPI (Recommended)
pip install nextdns-blocker
Then run the setup wizard:
nextdns-blocker init
Option 2: Install from Source
git clone https://github.com/aristeoibarra/nextdns-blocker.git
cd nextdns-blocker
pip install -e .
nextdns-blocker init
Quick Setup
1. Get NextDNS Credentials
- API Key: https://my.nextdns.io/account
- Profile ID: From URL (e.g.,
https://my.nextdns.io/abc123->abc123)
2. Run Setup Wizard
nextdns-blocker init
The wizard will prompt for:
- API Key
- Profile ID
- Timezone
- Option to create sample domains.json
3. Configure Domains and Schedules
Edit domains.json in your config directory to configure your domains and their availability schedules.
See SCHEDULE_GUIDE.md for detailed schedule configuration examples.
4. Install Watchdog (Optional)
For automatic syncing every 2 minutes with cron:
nextdns-blocker watchdog install
Done! The system will now automatically sync based on your configured schedules.
Docker Setup
Alternatively, run NextDNS Blocker using Docker:
1. Configure Environment
cp .env.example .env
nano .env # Add your API key, profile ID, and timezone
2. Configure Domains
cp domains.json.example domains.json
nano domains.json # Configure your domains and schedules
3. Run with Docker Compose
docker compose up -d
Docker Commands
# View logs
docker compose logs -f
# Stop the container
docker compose down
# Rebuild after changes
docker compose up -d --build
# Check status
docker compose ps
# Run a one-time sync
docker compose exec nextdns-blocker python nextdns_blocker.py sync -v
# Check blocking status
docker compose exec nextdns-blocker python nextdns_blocker.py status
Environment Variables for Docker
| Variable | Required | Default | Description |
|---|---|---|---|
NEXTDNS_API_KEY |
Yes | - | Your NextDNS API key |
NEXTDNS_PROFILE_ID |
Yes | - | Your NextDNS profile ID |
DOMAINS_URL |
No | - | URL to fetch domains.json remotely |
TZ |
No | America/Mexico_City |
Container timezone |
Commands
Main Blocker Commands
# Sync based on schedules (runs automatically every 2 min)
nextdns-blocker sync
# Preview what sync would do without making changes
nextdns-blocker sync --dry-run
# Sync with verbose output showing all actions
nextdns-blocker sync --verbose
nextdns-blocker sync -v
# Check current blocking status
nextdns-blocker status
# Manually unblock a domain (won't work on protected domains)
nextdns-blocker unblock example.com
# Pause all blocking for 30 minutes (default)
nextdns-blocker pause
# Pause for custom duration (e.g., 60 minutes)
nextdns-blocker pause 60
# Resume blocking immediately
nextdns-blocker resume
Watchdog Commands
# Check cron status
nextdns-blocker watchdog status
# Disable watchdog for 30 minutes
nextdns-blocker watchdog disable 30
# Disable watchdog permanently
nextdns-blocker watchdog disable
# Re-enable watchdog
nextdns-blocker watchdog enable
# Manually install cron jobs
nextdns-blocker watchdog install
# Remove cron jobs
nextdns-blocker watchdog uninstall
Logs
# View application logs
tail -f ~/.local/share/nextdns-blocker/logs/app.log
# View audit log (all blocking/unblocking actions)
cat ~/.local/share/nextdns-blocker/logs/audit.log
# View cron execution logs
tail -f ~/.local/share/nextdns-blocker/logs/cron.log
# View watchdog logs
tail -f ~/.local/share/nextdns-blocker/logs/wd.log
# View cron jobs
crontab -l
Configuration
Environment Variables (.env)
| Variable | Required | Default | Description |
|---|---|---|---|
NEXTDNS_API_KEY |
Yes | - | Your NextDNS API key |
NEXTDNS_PROFILE_ID |
Yes | - | Your NextDNS profile ID |
TIMEZONE |
No | UTC |
Timezone for schedule evaluation |
API_TIMEOUT |
No | 10 |
API request timeout in seconds |
API_RETRIES |
No | 3 |
Number of retry attempts |
DOMAINS_URL |
No | - | URL to fetch domains.json from |
Domain Schedules
Edit domains.json to configure which domains to manage and their availability schedules:
{
"domains": [
{
"domain": "reddit.com",
"description": "Social media",
"protected": false,
"schedule": {
"available_hours": [
{
"days": ["monday", "tuesday", "wednesday", "thursday", "friday"],
"time_ranges": [
{"start": "12:00", "end": "13:00"},
{"start": "18:00", "end": "22:00"}
]
},
{
"days": ["saturday", "sunday"],
"time_ranges": [
{"start": "10:00", "end": "22:00"}
]
}
]
}
},
{
"domain": "gambling-site.com",
"description": "Always blocked",
"protected": true,
"schedule": null
}
]
}
Domain Configuration Options
| Field | Required | Description |
|---|---|---|
domain |
Yes | Domain name to manage |
description |
No | Human-readable description |
protected |
No | If true, domain cannot be manually unblocked |
schedule |
No | Availability schedule (null = always blocked) |
Changes take effect on next sync (every 2 minutes).
See SCHEDULE_GUIDE.md for complete documentation and examples.
Allowlist (Exceptions)
Use the allowlist to keep specific subdomains accessible even when their parent domain is blocked:
{
"domains": [
{
"domain": "amazon.com",
"description": "E-commerce - blocked with schedule",
"schedule": { ... }
}
],
"allowlist": [
{
"domain": "aws.amazon.com",
"description": "AWS Console - always accessible"
},
{
"domain": "developer.amazon.com",
"description": "Amazon Developer - always accessible"
}
]
}
Allowlist Behavior
- Allowlist entries are always active 24/7 (no schedule support)
- A domain cannot be in both
domains(denylist) andallowlist - Use for subdomain exceptions: block
amazon.combut allowaws.amazon.com - Changes sync automatically every 2 minutes
Allowlist Commands
# Add domain to allowlist (always accessible)
nextdns-blocker allow aws.amazon.com
# Remove domain from allowlist
nextdns-blocker disallow aws.amazon.com
# View current status including allowlist
nextdns-blocker status
Timezone
Edit .env to change timezone:
TIMEZONE=America/New_York
See list of timezones.
Troubleshooting
Sync not working?
- Check cron:
crontab -l(should see sync job running every 2 minutes) - Check logs:
tail -f ~/.local/share/nextdns-blocker/logs/app.log - Test manually:
nextdns-blocker sync - Validate JSON:
python3 -m json.tool domains.json
Domains.json errors?
- Ensure valid JSON syntax (use jsonlint.com)
- Check time format is HH:MM (24-hour)
- Check day names are lowercase (monday, tuesday, etc.)
- Domain names must be valid (no spaces, special characters)
- See
domains.json.examplefor reference
Wrong timezone?
- Update
TIMEZONEin.env - Re-run
./install.sh - Check logs to verify timezone is being used
API timeouts?
- Increase
API_TIMEOUTin.env(default: 10 seconds) - Increase
API_RETRIESin.env(default: 3 attempts)
Cron not running?
# Check cron service status
sudo service cron status || sudo service crond status
# Check watchdog status
nextdns-blocker watchdog status
Uninstall
# Remove cron jobs
nextdns-blocker watchdog uninstall
# Remove files
rm -rf ~/nextdns-blocker
# Remove logs (optional)
rm -rf ~/.local/share/nextdns-blocker
Log Rotation
To prevent log files from growing indefinitely, set up log rotation:
chmod +x setup-logrotate.sh
./setup-logrotate.sh
This configures automatic rotation with:
app.log: daily, 7 days retentionaudit.log: weekly, 12 weeks retentioncron.log: daily, 7 days retentionwd.log: daily, 7 days retention
Development
Running Tests
pip install -e ".[dev]"
pytest tests/ -v
Test Coverage
pytest tests/ --cov=nextdns_blocker --cov-report=html
Current coverage: 85% with 379 tests.
Code Quality
The codebase follows these practices:
- Type hints on all functions
- Docstrings with Args/Returns documentation
- Custom exceptions for error handling
- Secure file permissions (0o600)
- Input validation before API calls
Documentation
- SCHEDULE_GUIDE.md - Complete schedule configuration guide with examples
- domains.json.example - Example configuration file
- CONTRIBUTING.md - Contribution guidelines
Security
- Never share your
.envfile (contains API key) .gitignoreis configured to ignore sensitive files- All API requests use HTTPS
- Sensitive files created with
0o600permissions - Domain names validated before API calls
- Audit log tracks all blocking/unblocking actions
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nextdns_blocker-5.0.0.tar.gz.
File metadata
- Download URL: nextdns_blocker-5.0.0.tar.gz
- Upload date:
- Size: 57.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4ffde77479e456b7893ec7618558dc8b732c6f83b3fb85f543ea1e895824b09a
|
|
| MD5 |
0068b376fc72b5caa0c35805a0451c5a
|
|
| BLAKE2b-256 |
071e2f550f3c93d117d76e802f17c04f6223ea16597d385ecb0766ef621a4f7e
|
Provenance
The following attestation bundles were made for nextdns_blocker-5.0.0.tar.gz:
Publisher:
publish.yml on aristeoibarra/nextdns-blocker
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
nextdns_blocker-5.0.0.tar.gz -
Subject digest:
4ffde77479e456b7893ec7618558dc8b732c6f83b3fb85f543ea1e895824b09a - Sigstore transparency entry: 743771831
- Sigstore integration time:
-
Permalink:
aristeoibarra/nextdns-blocker@3e634d832b20756e1f3febb5c8a6c0344d522fe2 -
Branch / Tag:
refs/tags/v5.0.0 - Owner: https://github.com/aristeoibarra
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3e634d832b20756e1f3febb5c8a6c0344d522fe2 -
Trigger Event:
push
-
Statement type:
File details
Details for the file nextdns_blocker-5.0.0-py3-none-any.whl.
File metadata
- Download URL: nextdns_blocker-5.0.0-py3-none-any.whl
- Upload date:
- Size: 33.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ceafcfd0f8e1c3c097b106583c77ffc228d616a6d8bc7a0bfef2765ec221458c
|
|
| MD5 |
ad9049a642f42e3ac598673ff76668d4
|
|
| BLAKE2b-256 |
9c7af77925fd2552315bc2a4a4b002a89f572f45e4e3221a315d11816a3b748b
|
Provenance
The following attestation bundles were made for nextdns_blocker-5.0.0-py3-none-any.whl:
Publisher:
publish.yml on aristeoibarra/nextdns-blocker
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
nextdns_blocker-5.0.0-py3-none-any.whl -
Subject digest:
ceafcfd0f8e1c3c097b106583c77ffc228d616a6d8bc7a0bfef2765ec221458c - Sigstore transparency entry: 743771833
- Sigstore integration time:
-
Permalink:
aristeoibarra/nextdns-blocker@3e634d832b20756e1f3febb5c8a6c0344d522fe2 -
Branch / Tag:
refs/tags/v5.0.0 - Owner: https://github.com/aristeoibarra
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3e634d832b20756e1f3febb5c8a6c0344d522fe2 -
Trigger Event:
push
-
Statement type: