This file contains the technical, build, test, storage, API, and deployment details that are intentionally kept out of the quick-start README.
- Blocklist: block domains by exact match or wildcard (
*.example.com) - Allowlist: bypass blocklist for specific domains
- DNS Rewrite: return custom IPs for specific domains
- Parallel Forwarding: races queries across multiple upstream resolvers
- UDP + TCP: listens on both protocols simultaneously
- Web Management UI: TailwindCSS dark UI
- Query Statistics: tracks totals, top clients, top domains, and recent queries
- Live Query Log: Server-Sent Events stream
- REST API: CRUD for lists, rewrites, settings, upstreams, sources, stats, sync, and ACME
- SQLite database: portable single-file storage
- Hot reload: list and rewrite changes take effect immediately
- Auto-update sources: scheduled blocklist/allowlist URL refresh
- Admin password authentication: generated/reset through
--genpass - Security headers: CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy
- Replica sync: hash-diffed configuration mirroring
- HTTPS with ACME: Let's Encrypt via Cloudflare DNS-01
- Certificate auto-renewal: checks every 24 hours, renews within 7 days of expiry
- Activity log: real-time ACME, settings, restart, and update progress
Prerequisites:
- Rust toolchain
- Node.js, only needed for Tailwind CSS builds
npm install
npm run build:css
cargo build --release
sudo ./target/release/rustblockerPort 53 requires elevated privileges.
For a static Linux binary, cargo-zigbuild is recommended:
cargo install cargo-zigbuild
cargo zigbuild --release --target x86_64-unknown-linux-muslOn Linux you can also use:
rustup target add x86_64-unknown-linux-musl
sudo apt-get install musl-tools
cargo build --release --target x86_64-unknown-linux-muslRequired checks before committing code changes:
cargo fmt --all -- --check
cargo clippy --all-targets -- -D warnings
cargo testIf static/index.html or static/input.css changes:
npm run build:css
git diff --exit-code static/tailwind.min.cssIf static/app.js changes:
node --check static/app.jsRustBlocker uses SQLite (rustblocker.db) for configuration and runtime state.
- Created automatically on first run
- No config files required
- Stores settings, upstream servers, blocklist domains, allowlist domains, rewrite rules, query logs, sources, TLS certificates, and auth/session settings
- Portable: copy the database to migrate configuration
Installed service database path:
/var/lib/rustblocker/rustblocker.db
The Web UI is available at:
http://<server-ip>:54
Default tabs:
- Dashboard: summary counters and source state
- Upstreams: upstream DNS servers
- Sources: scheduled blocklist/allowlist sources
- Blocklist: blocked domains
- Allowlist: allowed domains
- Rewrites: local DNS rewrites
- Settings: listen address, ports, sinkhole IPs, timeouts, ACL, sync, password
- HTTPS: ACME settings, certificate status, renewal state
- Activity Log: real-time progress stream
Changes to blocklist, allowlist, rewrites, sinkhole IPs, upstream timeout, stats retention, and ACL apply live. Listen address, listen port, and log level require restart.
Base URL:
http://<listen_address>:<listen_port + 1>/api
Protected endpoints require a session cookie from /api/auth/login.
| Method | Path | Description |
|---|---|---|
GET |
/api/health |
Health check |
POST |
/api/auth/login |
Log in with admin password |
POST |
/api/auth/logout |
Clear session cookie |
GET |
/api/auth/check |
Check active session |
PUT |
/api/auth/password |
Change admin password |
GET |
/api/settings |
Get settings |
PUT |
/api/settings |
Update one setting |
GET |
/api/upstreams |
List upstream servers |
POST |
/api/upstreams |
Add upstream server |
DELETE |
/api/upstreams/{id} |
Remove upstream server |
GET |
/api/blocklist |
List blocked domains |
POST |
/api/blocklist |
Add blocked domain |
DELETE |
/api/blocklist/{id} |
Remove blocked domain |
POST |
/api/blocklist/import |
Bulk import blocklist |
GET |
/api/allowlist |
List allowed domains |
POST |
/api/allowlist |
Add allowed domain |
DELETE |
/api/allowlist/{id} |
Remove allowed domain |
POST |
/api/allowlist/import |
Bulk import allowlist |
GET |
/api/rewrites |
List DNS rewrites |
POST |
/api/rewrites |
Add DNS rewrite |
DELETE |
/api/rewrites/{id} |
Remove DNS rewrite |
GET |
/api/sources |
List auto-update sources |
POST |
/api/sources |
Add source and fetch immediately |
DELETE |
/api/sources/{id} |
Remove source |
POST |
/api/sources/refresh |
Refresh all enabled sources |
GET |
/api/stats |
Query statistics |
GET |
/api/stats/queries |
Recent query log |
GET |
/api/stats/live |
Live query stream |
DELETE |
/api/stats |
Clear query statistics |
GET |
/api/sync/config |
Get replica sync config |
PUT |
/api/sync/config |
Save replica sync config |
GET |
/api/sync/manifest |
Master-side category hashes |
GET |
/api/sync/snapshot/{category} |
Master-side data snapshot |
POST |
/api/acme/request |
Request certificate |
POST |
/api/acme/renew |
Force certificate renewal |
GET |
/api/acme/status |
Certificate and renewal status |
GET |
/api/activity/stream |
Activity SSE stream |
POST |
/api/cloudflare/test |
Test Cloudflare token |
Log in:
curl -c jar.txt -X POST http://127.0.0.1:54/api/auth/login \
-H "Content-Type: application/json" \
-d '{"password": "your-genpass-password"}'Add a blocked domain:
curl -b jar.txt -X POST http://127.0.0.1:54/api/blocklist \
-H "Content-Type: application/json" \
-d '{"domain": "ads.example.com"}'Bulk import blocklist content:
curl -b jar.txt -X POST http://127.0.0.1:54/api/blocklist/import \
-H "Content-Type: application/json" \
-d '{"content": "0.0.0.0 ads.example.com\n0.0.0.0 tracker.example.com"}'curl -b jar.txt -X PUT http://127.0.0.1:54/api/settings \
-H "Content-Type: application/json" \
-d '{"key": "domain", "value": "dns.example.com"}'
curl -b jar.txt -X PUT http://127.0.0.1:54/api/settings \
-H "Content-Type: application/json" \
-d '{"key": "acme_email", "value": "admin@example.com"}'
curl -b jar.txt -X PUT http://127.0.0.1:54/api/settings \
-H "Content-Type: application/json" \
-d '{"key": "cloudflare_api_token", "value": "your-token-here"}'
curl -b jar.txt -X POST http://127.0.0.1:54/api/cloudflare/test \
-H "Content-Type: application/json" \
-d '{"api_token": "your-token-here"}'
curl -b jar.txt -X POST http://127.0.0.1:54/api/acme/request \
-H "Content-Type: application/json" \
-d '{"domain": "dns.example.com", "wildcard": false}'
curl -b jar.txt http://127.0.0.1:54/api/acme/statuscurl -b jar.txt -X PUT http://192.168.1.2:54/api/sync/config \
-H "Content-Type: application/json" \
-d '{
"enabled": true,
"master_url": "http://192.168.1.1:54",
"password": "<master-admin-password>",
"interval_secs": 30
}'
curl -b jar.txt http://192.168.1.2:54/api/sync/configRestart the replica for the setting to take effect.
RustBlocker can fetch and update blocklists or allowlists from URLs on a schedule.
Add a source from the Web UI Sources tab, or via API:
curl -b jar.txt -X POST http://127.0.0.1:54/api/sources \
-H "Content-Type: application/json" \
-d '{"url": "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts", "list_type": "blocklist", "update_interval_hours": 24}'Refresh all sources:
curl -b jar.txt -X POST http://127.0.0.1:54/api/sources/refreshOne-time URL import without saving a recurring source:
curl -b jar.txt -X POST http://127.0.0.1:54/api/blocklist/import \
-H "Content-Type: application/json" \
-d '{"url": "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts"}'# Plain domain
ads.example.com
# Wildcard, matches subdomains but not the bare domain
*.tracking.example.com
# Hosts file format, leading IP is stripped
0.0.0.0 ads.example.com
127.0.0.1 tracker.example.com
| Setting | Default | Description |
|---|---|---|
listen_address |
0.0.0.0 |
Bind address |
listen_port |
53 |
DNS listen port |
sinkhole_ipv4 |
0.0.0.0 |
IPv4 for blocked domains |
sinkhole_ipv6 |
:: |
IPv6 for blocked domains |
log_level |
info |
Log level |
upstream_timeout_secs |
5 |
Upstream timeout |
allowed_networks |
empty | CIDR ACL |
domain |
empty | HTTPS certificate domain |
acme_email |
empty | Let's Encrypt contact email |
cloudflare_api_token |
empty | Cloudflare token |
wildcard_cert |
false |
Wildcard certificate flag |
acme_directory_url |
Let's Encrypt production | ACME directory override |
Install:
curl -sSL https://raw.githubusercontent.com/shabilullah/rustblocker/main/scripts/install.sh | sudo bashBuild directly on Alpine:
apk add rust cargo musl-dev
git clone https://github.com/shabilullah/rustblocker.git
cd rustblocker
cargo build --release
sudo ./scripts/install.shcargo run --bin xtask -- mock-deploy is the native end-to-end deploy proof for a designated test machine. It replaces the old scripts/mock-deploy.sh shell runner.
cp scripts/.deployenv.example scripts/.deployenv
# Fill SSH_HOST, SSH_USER, SSH_PASSWORD, WEBUI_PASSWORD, DOMAIN, ACME_EMAIL, and CF_TOKEN.
cargo run --bin xtask -- mock-deploy --report-dir=target/mock-report --timeout=45Useful options:
cargo run --bin xtask -- mock-deploy --skip-build
cargo run --bin xtask -- mock-deploy --skip-deploy
cargo run --bin xtask -- mock-deploy --compare=target/mock-report/summary.json
ACME_POLL_ATTEMPTS=30 cargo run --bin xtask -- mock-deploy --timeout=45The runner reads scripts/.deployenv, writes JSON-lines to <report-dir>/run.jsonl, writes <report-dir>/summary.json, and exits non-zero if any step fails. Process environment values override .deployenv values, which lets CI pin gates such as MEMORY_RSS_GROWTH_MAX_KB or QUERY_LOG_PRUNE_WAL_MAX_BYTES without editing secrets.
FROM rust:1.82-alpine AS builder
RUN apk add --no-cache musl-dev
WORKDIR /build
COPY . .
RUN cargo build --release --target x86_64-unknown-linux-musl
FROM alpine:3.20
COPY --from=builder /build/target/x86_64-unknown-linux-musl/release/rustblocker /usr/local/bin/
EXPOSE 53/udp 53/tcp 54/tcp
CMD ["rustblocker"]/usr/local/bin/
└── rustblocker # wrapper script
/usr/local/lib/rustblocker/
└── rustblocker # real binary
/var/lib/rustblocker/
└── rustblocker.db # service database
/var/log/
└── rustblocker.log
DNS Request
|
v
RequestHandler
|
|- Rewrite match? -> Return custom IP
|- Allowlist? -> Skip blocklist
|- Blocklist? -> Return sinkhole IP
`- Forward -> Race upstream resolvers
Web UI + API
|
|- SQLite database
|- Hot-reload stores
|- Activity SSE stream
`- Embedded static UI
HTTPS
|
|- ACME client
|- Cloudflare DNS-01
|- rustls server config
`- Auto-renewal every 24h, 7-day threshold
Replica Sync
|
|- Login to master
|- Fetch manifest hashes
|- Fetch changed snapshots
`- Apply changed categories
MIT OR Apache-2.0