An Ansible playbook to deploy a secure, decentralized DNS infrastructure combining:
- Handshake (HNS) blockchain for decentralized TLD resolution
- Unbound DNS server with DNS-over-TLS (DoT) and DNS-over-HTTPS (DoH)
- Caddy reverse proxy for HTTPS termination
- Quad9 for secure upstream DNS resolution
β Handshake Full Node - Authoritative resolution for HNS TLDs
β Unbound DNS - Caching resolver with DoT/DoH support
β Caddy 2 - Modern DoH endpoint with Let's Encrypt automation
β Quad9 Integration - Secure DNS-over-TLS upstream
β HIP-5 Protocol - Cross-protocol resolution (ENS, IPFS, Tor)
π DNSSEC Validation - Full DNSSEC support with auto-trust anchors
π Security Hardening - Glue protection, referral path validation, algorithm downgrade protection
π Firewall Configuration - UFW-based firewall with rate limiting
π Rate Limiting - DDoS protection for both DNS and DoH endpoints
π Container Health Checks - Automated health monitoring for all services
π Privacy Features - Query minimization, identity hiding
π Automated Backups - Daily backups of wallets, configs, and certificates
π CI/CD Pipeline - GitHub Actions for linting, syntax checking, and security scanning
π Comprehensive Logging - Structured logging with performance profiling
π Ansible Best Practices - Tags, pre/post-tasks, validation, and error handling
This playbook uses the following stable software versions:
| Component | Version | Release Date | Notes |
|---|---|---|---|
| Handshake (HSD) | v8.0.0 | Aug 2024 | Requires database migration |
| Unbound | 1.21.0 | Aug 2024 | Latest stable from NLnet Labs |
| Caddy | 2.10.2 | Nov 2024 | Official v2 with auto HTTPS |
| community.docker | >=5.0.2 | Nov 2024 | Requires ansible-core >=2.17.0 |
| community.crypto | >=3.0.5 | Oct 2024 | For certificate management |
-
Backup your wallet first:
docker exec hsd hsd-cli wallet backup --name=backup-$(date +%Y%m%d)
-
First run requires migration flags:
# The playbook will handle this, but if running manually: hsd --chain-migrate=4 --wallet-migrate=7 -
Migration is automatic when deploying with this playbook, but allow extra time for the first deployment after upgrade.
[Client Devices]
|
| DoH/DoT
β
[Caddy (443/tcp)] β Let's Encrypt Certificates
| β
| | Decrypted DNS
β |
[Unbound (53/udp, 853/tcp)]
βββ [Handshake Full Node (.hns TLDs)]
βββ [Quad9 (9.9.9.9:853) - All other TLDs]
- Ansible 2.10+
- Target system:
- Ubuntu 20.04+/Debian 11+
- 2 vCPU, 4GB RAM, 50GB storage
- Open ports: 53/udp, 53/tcp, 443/tcp, 853/tcp
# Clone repository
git clone https://github.com/yourusername/ansible-handshake-dns.git
cd ansible-handshake-dns
# Configure inventory
cp inventory/production.example inventory/production
nano inventory/production # Add your servers
# Edit group variables
nano group_vars/all.yml
# Install dependencies
ansible-galaxy install -r requirements.yml
# Deploy full infrastructure
ansible-playbook -i inventory/production playbooks/deploy_dns.yml
# Deploy specific components using tags
ansible-playbook -i inventory/production playbooks/deploy_dns.yml --tags "firewall,docker,hsd"
# Run only configuration changes
ansible-playbook -i inventory/production playbooks/deploy_dns.yml --tags "configure"
The playbook supports granular control through tags:
# Available tags:
# - install: Install packages and dependencies
# - configure: Configure services
# - deploy: Deploy containers
# - verify: Verify deployment
# - firewall: Firewall configuration only
# - docker: Docker setup only
# - hsd, unbound, caddy: Individual services
# - security: Security-related tasks
# Examples:
# Deploy only DNS services (skip firewall)
ansible-playbook -i inventory/production playbooks/deploy_dns.yml --skip-tags "firewall"
# Update configurations without reinstalling
ansible-playbook -i inventory/production playbooks/deploy_dns.yml --tags "configure"
# Verify deployment health
ansible-playbook -i inventory/production playbooks/deploy_dns.yml --tags "verify"# Network
docker_network: "dns_net"
# Quad9 DNS
quad9_servers:
- 9.9.9.9@853#dns.quad9.net
- 149.112.112.112@853#dns.quad9.net
# Certificates
acme_email: "admin@yourdomain.hns"
# HIP-5 Protocols
hip5_protocols: ["_eth", "_ipfs", "_tor"]
hip5_resolvers:
eth: "https://ethresolver.yourdomain.hns"
ipfs: "https://ipfsgateway.yourdomain.hns"
Note: Replace placeholder values (yourusername, yourdomain.hns, etc.) with your actual information before use.
To enable automated backups, add a backup role to your playbook:
# In playbooks/deploy_dns.yml, add:
- role: backup
tags: [backup, optional]Configure backup settings in group_vars/dns_servers.yml:
# Backup configuration
backup_dir: /opt/backups/dns
backup_retention_days: 30
backup_hsd_wallet: true
backup_configurations: true
backup_certificates: true
# Optional remote backup
backup_remote_enabled: false
backup_remote_type: "s3" # or "rsync"
backup_remote_destination: "s3://my-bucket/dns-backups"Run manual backup:
/usr/local/bin/dns-backup.shThe firewall role automatically configures UFW with:
- Allow: 22/tcp (SSH, rate-limited), 53/udp+tcp (DNS), 443/tcp (HTTPS), 853/tcp (DoT)
- Deny: All other incoming traffic
- Rate limiting on SSH to prevent brute force attacks
DNSSEC is enabled by default in Unbound with:
- Auto-trust anchor updates
- Strict validation for signed zones
- Protection against algorithm downgrades
- Glue record validation
Verify DNSSEC is working:
dig @your-server dnssec.works +dnssec
# Look for "ad" flag in the responseProtection against DDoS attacks:
- Unbound: 1000 queries/sec per IP, 100 total IP connections
- Caddy: 100 requests/minute per IP for DoH endpoint
- UFW: Connection rate limiting on SSH
# Test Handshake resolution
dig @your-server +short icann.
# Test standard DNS over DoT
kdig @your-server -p 853 google.com. +tls
# Test DoH endpoint
curl -H 'accept: application/dns-json' \
'https://your-server/dns-query?name=example.com&type=A'
- Certificate Management
- Uses Let's Encrypt with auto-renewal
- Certificates stored in
/opt/certs
- Network Isolation
- Dedicated Docker bridge network
- Firewall rules recommended for public exposure
- Regular Updates
- Container versions are pinned for reproducibility
- Review Software Versions section before upgrading
- Test in non-production environment first
# Deploy with updated versions ansible-playbook -i inventory/production playbooks/deploy.yml
Common Issues:
- Port Conflicts: Ensure host ports 53/udp and 443/tcp are free
- Certificate Errors: Verify ACME email in
group_vars/all.yml - HIP-5 Resolution: Check
docker logs hsd
Diagnostic Commands:
# Check container status
docker ps -a --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
# View Unbound logs
docker logs unbound | grep -i error
# Test DoH directly
curl -v -H 'accept: application/dns-message' \
--data-binary @query.bin https://your-server/dns-query
This project includes a GitHub Actions CI/CD pipeline that automatically:
- Lints all Ansible playbooks with
ansible-lint - Validates YAML syntax with
yamllint - Performs syntax checking on playbooks
- Runs security scans with Trivy
- Executes dry-run deployments in check mode
The CI pipeline runs on:
- All pushes to
mainanddevelopbranches - All pull requests
- Manual workflow dispatch
Before committing, run local checks:
# Install development dependencies
pip install ansible ansible-lint yamllint
# Run linting
ansible-lint playbooks/
yamllint .
# Syntax check
ansible-playbook playbooks/deploy_dns.yml --syntax-check
# Dry run (check mode)
ansible-playbook playbooks/deploy_dns.yml --check --diffAll containers include health checks that run every 30 seconds:
# Check container health status
docker ps --format "table {{.Names}}\t{{.Status}}"
# View health check logs
docker inspect hsd --format='{{json .State.Health}}' | jq
docker inspect unbound --format='{{json .State.Health}}' | jq
docker inspect caddy --format='{{json .State.Health}}' | jqAnsible execution logs are stored in ansible.log with:
- Task timing information (profile_tasks callback)
- Total execution time (timer callback)
- All task results and changes
Container logs:
# View container logs
docker logs -f hsd
docker logs -f unbound
docker logs -f caddy
# Follow all logs
docker logs -f --tail=100 hsd unbound caddy- Fork the repository
- Create feature branch (
git checkout -b feature/improvement) - Run local linting and tests
- Commit changes (
git commit -am 'Add some feature') - Push to branch (
git push origin feature/improvement) - Open Pull Request (CI will run automatically)
All contributions must pass CI checks before merging.
MIT License - See LICENSE for details