Routing the Self-Hosted Web: Architecting Local DNS and TLS for the Proposed .self TLD
Discover how to implement a secure, high-performance local DNS and TLS resolution pipeline for the proposed .self TLD. Learn to prevent DNS leakage and automate SSL renewals using Unbound and private ACME servers.
The Evolution of Local Namespace Resolution
For decades, developers, homelab enthusiasts, and network engineers have struggled to find the perfect local top-level domain (TLD). Historically, we turned to .local, only to find it conflicts with Multicast DNS (mDNS) standards (RFC 6762), causing resolution delays and packet loops on macOS and Linux. We migrated to .lan, .home, or custom domains like .dev (which Google subsequently purchased and secured with preloaded HSTS, breaking local HTTP setups overnight). Even RFC 8375's official recommendation, .home.arpa, feels clunky and aesthetically unpleasing for modern self-hosted application suites.
Enter the proposed .self top-level domain—a dedicated, non-delegated namespace designed specifically to support self-hosting, local development, and sovereign infrastructure. By formalizing a TLD that is guaranteed never to be sold on the public internet, we can design robust, deterministic local networks.
However, simply deciding to use .self in your local environment is only the first step. To make it production-ready, you must architect a secure local DNS resolution pipeline, prevent your local queries from leaking to public upstream resolvers, and establish a private PKI (Public Key Infrastructure) to enable HTTPS without browser warnings.
This guide walks you through the complete engineering pipeline to implement a secure, high-performance .self local domain infrastructure.
Phase 1: Preventing DNS Leakage with a Local Resolver
When your local machines query dashboard.home.self, you want those queries resolved instantly by your local DNS server. More importantly, you must guarantee that if a query fails or if a client misbehaves, these queries are never forwarded to public root servers (like Cloudflare's 1.1.1.1 or Google's 8.8.8.8). Leaking local names to public resolvers exposes your internal network topology and application names to third parties.
To prevent this, we will configure Unbound, a validating, recursive, caching DNS resolver, to act as our authoritative local resolver for the .self zone.
Unbound Configuration for .self
Below is a hardened Unbound configuration (unbound.conf) that establishes .self as a local-only zone, restricts access to your local subnet, and blocks external forwarding for this namespace.
server:
interface: 0.0.0.0
port: 53
do-ip4: yes
do-udp: yes
do-tcp: yes
# Access Control: Allow only local subnets
access-control: 127.0.0.0/8 allow
access-control: 10.0.0.0/8 allow
access-control: 192.168.0.0/16 allow
# Define the local-zone type as 'static'
# This ensures Unbound returns NXDOMAIN for any undefined subdomains
# and prevents forwarding queries up the chain.
local-zone: "self." static
# Local A records for your services
local-data: "router.self. IN A 10.0.0.1"
local-data: "nas.self. IN A 10.0.0.10"
local-data: "git.self. IN A 10.0.0.20"
local-data: "proxy.self. IN A 10.0.0.100"
# Wildcard resolution pointing all other .self domains to your reverse proxy
local-data-ptr: "10.0.0.100 proxy.self"
# Forwarding rule for the rest of the internet
forward-zone:
name: "."
forward-addr: 9.9.9.9@53 # Quad9 Secure DNS
forward-addr: 1.1.1.1@53 # Cloudflare DNS
By defining local-zone: "self." static, you instruct Unbound to serve only the records defined in its local data block. Any query for a non-existent host under .self (e.g., malicious-malware.self) will immediately return NXDOMAIN without querying the root hints or upstream forwarders.
Phase 2: Solving the Local TLS Dilemma
Modern browsers enforce secure contexts for almost all advanced web APIs (like Service Workers, Web Crypto, and WebRTC). If you run your local services over plain HTTP under http://nas.self, your browser will restrict functionality. Conversely, self-signing individual certificates for every service leads to a maintenance nightmare and security-alert fatigue.
The solution is to run a private Certificate Authority (CA) using Step-CA (a lightweight, open-source PKI tool) and distribute its root certificate to all your devices.
Step 1: Initialize Your Private CA
Run the following command on your central infrastructure node to initialize a local CA:
step ca init \
--name="Sovereign Self CA" \
--provisioner="admin@self" \
--dns="ca.self" \
--address=":443"
This command generates your root certificate (root_ca.crt), intermediate certificate, and private keys.
Step 2: Distribute the Root Certificate
For your devices to trust certificates signed by your new CA, you must import the root_ca.crt file into their trusted root stores:
- macOS:
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain root_ca.crt - Linux (Debian/Ubuntu): Copy to
/usr/local/share/ca-certificates/and runsudo update-ca-certificates. - Windows: Import via
certlm.mscinto the "Trusted Root Certification Authorities" store.
Phase 3: Automated Certificate Provisioning with ACME
Manually copying SSL certificates to your services every 90 days is a recipe for broken systems. Instead, we can leverage the ACME (Automatic Certificate Management Environment) protocol locally. Step-CA supports the ACME protocol out of the box, allowing you to use standard clients like Certbot or built-in web server integrations (like Caddy) to automate renewals.
Step 1: Enable the ACME Provisioner in Step-CA
Run the following command to enable ACME on your local CA:
step ca provisioner add acme --type ACME
Step 2: Configure a Caddy Reverse Proxy for .self
Caddy is an exceptional choice for self-hosted infrastructure because of its native ACME integration. You can configure Caddy to automatically request, install, and renew TLS certificates from your local Step-CA server for any .self domain.
Create a Caddyfile on your reverse proxy server (10.0.0.100):
{
# Point Caddy to your private CA's ACME endpoint
acme_ca https://ca.self/acme/acme/directory
# Provide the path to the Step-CA root certificate so Caddy trusts it
acme_ca_root /etc/ssl/certs/root_ca.crt
}
nas.self {
reverse_proxy 10.0.0.10:8080
}
git.self {
reverse_proxy 10.0.0.20:3000
}
# Catch-all for other services
*.self {
respond "Service not found in local ingress routing." 404
}
When Caddy starts, it detects the .self domains, contacts your local Step-CA server via the ACME protocol, completes the challenge locally over HTTP, and provisions valid, trusted TLS certificates. Zero manual intervention required.
Phase 4: Validating and Auditing the Architecture
With your DNS resolver and CA configured, it is critical to verify that your system is operating securely.
- Verify Local Resolution:
Run
dig nas.self @10.0.0.100(or your Unbound IP). It should return the local A record instantly. - Verify DNS Leak Prevention:
Run
dig +trace non-existent-subdomain.self. The output should show that the query stops at your local resolver and does not query the root servers. - Verify TLS Handshake:
Navigate to
https://nas.selfin your browser. The lock icon should be green and secure, without any warnings. You can also inspect the certificate chain using OpenSSL:openssl s_client -connect nas.self:443 -showcerts
By architecting your local infrastructure around the .self TLD with local DNS filtering and a private ACME-driven CA, you achieve an enterprise-grade, secure-by-default environment. This setup ensures that your homelab or local development environment behaves exactly like the public cloud, keeping your data entirely within your physical control.