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
- Cross-platform: Native support for macOS (launchd), Linux (cron), and Windows (Task Scheduler)
- 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 with watchdog protection
- Discord notifications: Real-time alerts for block/unblock events
- 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
- Self-update: Built-in command to check and install updates
Requirements
- Python 3.9+
- NextDNS account with API key
- Linux/macOS/Windows
Installation
Option 1: Homebrew (macOS/Linux)
brew tap aristeoibarra/tap
brew install nextdns-blocker
Then run the setup wizard:
nextdns-blocker init
Option 2: Install from PyPI
pip install nextdns-blocker
Then run the setup wizard:
nextdns-blocker init
Option 3: Install from Source
git clone https://github.com/aristeoibarra/nextdns-blocker.git
cd nextdns-blocker
pip install -e .
nextdns-blocker init
Option 4: Windows Installation
On Windows, you can also use the PowerShell installer:
# Download and run the installer
irm https://raw.githubusercontent.com/aristeoibarra/nextdns-blocker/main/install.ps1 | iex
# Or run locally after cloning
.\install.ps1
The installer will:
- Check for Python installation
- Install the package via pip
- Run the interactive setup wizard
- Configure Windows Task Scheduler for automatic sync
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 is automatically detected from your system and saved to config.json.
3. Configure Domains and Schedules
Edit config.json in your config directory to configure your domains and their availability schedules:
nextdns-blocker config edit
See SCHEDULE_GUIDE.md for detailed schedule configuration examples.
4. Install Watchdog (Optional)
For automatic syncing every 2 minutes:
nextdns-blocker watchdog install
This installs platform-specific scheduled jobs:
- macOS: launchd jobs (
~/Library/LaunchAgents/) - Linux: cron jobs (
crontab -l) - Windows: Task Scheduler tasks (view with
taskschd.msc)
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 config.json.example config.json
nano config.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 |
TZ |
No | UTC |
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
# Check for updates and upgrade
nextdns-blocker update
# Update without confirmation prompt
nextdns-blocker update -y
Pending Actions Commands
# List all pending unblock actions
nextdns-blocker pending list
# Show details of a specific pending action
nextdns-blocker pending show <action-id>
# Cancel a pending unblock action
nextdns-blocker pending cancel <action-id>
# Cancel without confirmation
nextdns-blocker pending cancel <action-id> -y
Config Commands
# Show current configuration
nextdns-blocker config show
# Edit config in your editor ($EDITOR)
nextdns-blocker config edit
# Set a configuration value
nextdns-blocker config set timezone America/New_York
nextdns-blocker config set editor vim
# Validate configuration syntax and structure
nextdns-blocker config validate
# Sync domains (same as root sync, but preferred)
nextdns-blocker config sync
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
Panic Mode Commands
Emergency lockdown mode that temporarily blocks all domains and hides dangerous commands.
# Activate panic mode for 1 hour
nextdns-blocker panic 60
# Check panic mode status
nextdns-blocker panic status
# Extend panic mode by 30 minutes
nextdns-blocker panic extend 30
During panic mode:
- All domains are immediately blocked
- Commands like
unblock,pause,resume,allow,disalloware hidden - Sync skips unblocks and allowlist operations
- Pending actions are paused
- Minimum duration is 15 minutes
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
Shell Completion
Enable tab completion for commands, subcommands, and domain names.
Bash - add to ~/.bashrc:
eval "$(nextdns-blocker completion bash)"
Zsh - add to ~/.zshrc:
eval "$(nextdns-blocker completion zsh)"
Fish - save to completions directory:
nextdns-blocker completion fish > ~/.config/fish/completions/nextdns-blocker.fish
After adding the completion script, restart your shell or source the config file.
What completes:
| Context | Completions |
|---|---|
nextdns-blocker <TAB> |
All commands |
nextdns-blocker config <TAB> |
Subcommands: edit, show, sync, etc. |
nextdns-blocker unblock <TAB> |
Domain names from your blocklist |
nextdns-blocker disallow <TAB> |
Domain names from your allowlist |
nextdns-blocker pending cancel <TAB> |
Pending action IDs |
nextdns-blocker --<TAB> |
Flags: --help, --version, --no-color |
Configuration
Environment Variables (.env)
| Variable | Required | Default | Description |
|---|---|---|---|
NEXTDNS_API_KEY |
Yes | - | Your NextDNS API key |
NEXTDNS_PROFILE_ID |
Yes | - | Your NextDNS profile ID |
API_TIMEOUT |
No | 10 |
API request timeout in seconds |
API_RETRIES |
No | 3 |
Number of retry attempts |
DISCORD_WEBHOOK_URL |
No | - | Discord webhook URL for notifications |
DISCORD_NOTIFICATIONS_ENABLED |
No | false |
Enable Discord notifications (true/false) |
Note: Timezone is now configured in
config.jsonundersettings.timezoneand is auto-detected during setup.
Discord Notifications
Get real-time alerts when domains are blocked or unblocked:
- Create a Discord webhook in your server (Server Settings → Integrations → Webhooks)
- Add to your
.env:
DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/...
DISCORD_NOTIFICATIONS_ENABLED=true
Notifications show:
- Domain name
- Action (blocked/unblocked)
- Timestamp
- Color-coded embeds (red=block, green=unblock)
Domain Schedules
Edit config.json to configure which domains to manage and their availability schedules:
{
"version": "1.0",
"settings": {
"timezone": "America/New_York",
"editor": null
},
"blocklist": [
{
"domain": "reddit.com",
"description": "Social media",
"unblock_delay": "0",
"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",
"unblock_delay": "never",
"schedule": null
}
],
"allowlist": []
}
Domain Configuration Options
| Field | Required | Description |
|---|---|---|
domain |
Yes | Domain name to manage |
description |
No | Human-readable description |
unblock_delay |
No | Cooldown before unblock executes (see below) |
schedule |
No | Availability schedule (null = always blocked) |
Unblock Delay Options
The unblock_delay field creates friction against impulsive unblocking:
| Value | Behavior |
|---|---|
"0" |
Instant unblock (no protection) |
"30m" |
Unblock queued, executes in 30 minutes |
"4h" |
Unblock queued, executes in 4 hours |
"24h" |
Unblock queued, executes in 24 hours |
"never" |
Cannot unblock (fully protected) |
When a delay is set, attempting to unblock creates a pending action:
$ nextdns-blocker unblock bumble.com
Unblock scheduled for 'bumble.com'
Delay: 24h
Execute at: 2025-12-16T03:45:00
ID: pnd_20251215_034500_a1b2c3
Use 'pending list' to view or 'pending cancel' to abort
You can cancel the pending action before it executes:
$ nextdns-blocker pending cancel a1b2c3
Cancelled pending unblock for bumble.com
This is based on research showing that cravings typically fade within 20-30 minutes. The delay creates space for better decisions.
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. Allowlist entries also support schedules for time-based access:
{
"domains": [
{
"domain": "amazon.com",
"description": "E-commerce - blocked with schedule",
"schedule": { ... }
}
],
"allowlist": [
{
"domain": "aws.amazon.com",
"description": "AWS Console - always accessible"
},
{
"domain": "youtube.com",
"description": "Streaming - blocked by NextDNS category, allow evenings",
"schedule": {
"available_hours": [
{
"days": ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"],
"time_ranges": [{ "start": "20:00", "end": "22:30" }]
}
]
}
}
]
}
Allowlist Behavior
| Schedule | Behavior |
|---|---|
null or missing |
Always in allowlist (24/7) |
| defined | Only in allowlist during scheduled hours |
- Scheduled allowlist is useful for domains blocked by NextDNS categories or services
- Outside schedule hours, the domain is removed from the allowlist (category/service blocks it)
- During schedule hours, the domain is added to the allowlist (unblocked)
- 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
Blocklist and Allowlist Interaction
NextDNS processes these lists with specific priority rules:
- Allowlist has highest priority - If a domain is in both lists, NextDNS will ALLOW it
- Subdomain inheritance - Blocking
amazon.comblocks all subdomains (*.amazon.com) - Subdomain exceptions - You can allow
aws.amazon.comwhile blockingamazon.com
Example configuration:
{
"blocklist": [
{"domain": "amazon.com", "schedule": null}
],
"allowlist": [
{"domain": "aws.amazon.com"}
]
}
Result in NextDNS:
amazon.com→ Blockedwww.amazon.com→ Blocked (inherits from parent)aws.amazon.com→ Allowed (allowlist overrides)console.aws.amazon.com→ Allowed (inherits from allowlist entry)
Note: This tool validates that the exact same domain doesn't appear in both lists. Subdomain relationships are allowed and will generate an informational warning during config load.
Panic Mode and Allowlist
During panic mode, all allowlist operations are blocked:
- The
allowcommand is hidden - The
disallowcommand is hidden - Scheduled allowlist sync is completely skipped
This prevents scheduled allowlist entries from creating security holes during emergency lockdown. The allowlist has highest priority in NextDNS and would bypass all blocks if allowed to sync.
Timezone
The timezone is auto-detected during init based on your system settings:
- macOS/Linux: Reads
/etc/localtimesymlink - Windows: Uses
tzutil /gcommand - Fallback:
TZenvironment variable orUTC
Timezone is stored in config.json under settings.timezone. To change it:
nextdns-blocker config set 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 config.json
Config.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)
- Validate with:
nextdns-blocker config validate - See
config.json.examplefor reference
Wrong timezone?
- Change with:
nextdns-blocker config set timezone America/New_York - Or re-run
nextdns-blocker init(timezone is auto-detected) - 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
Windows Troubleshooting
Task Scheduler not running?
# Check Task Scheduler status
nextdns-blocker watchdog status
# View tasks in Task Scheduler GUI
taskschd.msc
# List tasks via command line
schtasks /query /tn "NextDNS-Blocker-Sync"
schtasks /query /tn "NextDNS-Blocker-Watchdog"
# Manually run sync task
schtasks /run /tn "NextDNS-Blocker-Sync"
Paths with spaces causing issues?
- The application handles paths with spaces automatically
- If you see errors, check that your username doesn't contain special characters like
<,>,|,& - Log files are stored in:
%LOCALAPPDATA%\nextdns-blocker\logs\
PowerShell script won't run?
# Check execution policy
Get-ExecutionPolicy
# Allow scripts for current user (if needed)
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
# Run installer
.\install.ps1
View Windows logs
# Application log
Get-Content "$env:LOCALAPPDATA\nextdns-blocker\logs\app.log" -Tail 50
# Sync log
Get-Content "$env:LOCALAPPDATA\nextdns-blocker\logs\sync.log" -Tail 50
# Audit log
Get-Content "$env:LOCALAPPDATA\nextdns-blocker\logs\audit.log" -Tail 50
File permissions on Windows
- Windows uses ACLs instead of Unix file permissions (0o600)
- Files are created with default user permissions
- Configuration files in
%APPDATA%\nextdns-blocker\are only accessible by the current user in typical configurations
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: 94% with 1154 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
- examples/ - Ready-to-use configuration templates:
minimal.json- Quick-start templateswork-focus.json- Productivity-focused rulesgaming.json- Gaming platforms schedulingsocial-media.json- Social networks managementparental-control.json- Protected content blockingstudy-mode.json- Student-focused scheduling for distraction-free studying
- config.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
❓ Frequently Asked Questions
What is the difference between this tool and the NextDNS dashboard?
While the NextDNS dashboard allows you to manually toggle blocklists or set basic parental controls, nextdns-blocker is an automation agent. It allows for:
- Dynamic Scheduling: Automatically blocking and unblocking specific domains at precise times (e.g., blocking gaming sites only during study hours).
- State Enforcement: The "Watchdog" feature actively monitors your configuration to ensure restrictions haven't been manually disabled or bypassed.
How do I get my NextDNS API Key and Profile ID?
- Profile ID: This is the 6-character code found in the URL of your NextDNS dashboard (e.g.,
https://my.nextdns.io/abcdef->abcdef). - API Key: Go to your NextDNS Account page, scroll to the "API Key" section, and click to reveal/copy your key.
Does this tool block ads automatically?
No. This tool is designed to manage access policies (blocking specific websites/apps) rather than maintaining ad-block lists. For ad blocking, please enable the NextDNS Ads & Trackers Blocklist directly in your profile settings.
How does the "Watchdog" feature work?
The Watchdog runs in the background to prevent unauthorized changes. If a blocked domain is manually unblocked via the dashboard (or by another user), the Watchdog detects the discrepancy and immediately re-applies the block rule to maintain the security policy.
Where can I see what changes the blocker has made?
The tool includes an Audit Log feature. Check the generated log files (default location typically in the installation directory) to view a history of all block/unblock actions and watchdog enforcement events.
Can I run this on a Raspberry Pi?
Yes. Since nextdns-blocker is a Python package, it is lightweight and compatible with any system that supports Python 3, including Raspberry Pi, Linux servers, macOS, and Windows.
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-6.2.0.tar.gz.
File metadata
- Download URL: nextdns_blocker-6.2.0.tar.gz
- Upload date:
- Size: 173.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
aeb81b4b228ab288e6197ed8483f6ec7f645bb50ce90b558bff8c41ecec0ae1c
|
|
| MD5 |
c6011d44de40a6bac6cd66dd538af4ec
|
|
| BLAKE2b-256 |
89d3fac6247e248a04f0a5612e0f21f4d7067a5038e21c96cc344d5aef365d2b
|
Provenance
The following attestation bundles were made for nextdns_blocker-6.2.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-6.2.0.tar.gz -
Subject digest:
aeb81b4b228ab288e6197ed8483f6ec7f645bb50ce90b558bff8c41ecec0ae1c - Sigstore transparency entry: 774123474
- Sigstore integration time:
-
Permalink:
aristeoibarra/nextdns-blocker@f08db025cfb24fc4d1aaf0c09313dee1db8a4fe1 -
Branch / Tag:
refs/tags/v6.2.0 - Owner: https://github.com/aristeoibarra
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@f08db025cfb24fc4d1aaf0c09313dee1db8a4fe1 -
Trigger Event:
push
-
Statement type:
File details
Details for the file nextdns_blocker-6.2.0-py3-none-any.whl.
File metadata
- Download URL: nextdns_blocker-6.2.0-py3-none-any.whl
- Upload date:
- Size: 86.3 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 |
c6f92489e370a0dcd7bf3ff0e2ef7d0bbaf92b5701da3b3643ae022a5461792e
|
|
| MD5 |
21ed4b0e809ad316be45a81f1ee1acb1
|
|
| BLAKE2b-256 |
9afb8dd24aa3f520cc03982d78ca657d6976749d5ba668e124813e409fdf7e3c
|
Provenance
The following attestation bundles were made for nextdns_blocker-6.2.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-6.2.0-py3-none-any.whl -
Subject digest:
c6f92489e370a0dcd7bf3ff0e2ef7d0bbaf92b5701da3b3643ae022a5461792e - Sigstore transparency entry: 774123475
- Sigstore integration time:
-
Permalink:
aristeoibarra/nextdns-blocker@f08db025cfb24fc4d1aaf0c09313dee1db8a4fe1 -
Branch / Tag:
refs/tags/v6.2.0 - Owner: https://github.com/aristeoibarra
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@f08db025cfb24fc4d1aaf0c09313dee1db8a4fe1 -
Trigger Event:
push
-
Statement type: