Securing Internal Services with Split-Horizon DNS, ACME Standalone, and Interface-Bound Reverse Proxies
Managing TLS for private networks without self-signed certificates or public exposure can be achieved using split-horizon DNS paired with a public Certificate Authority. This approach binds reverse proxies directly to VPN interfaces and leverages non-root ACME clients with Linux capabilities for automated, secure renewals.
The Operational Overhead of Private TLDs
Configuring internal HTTP services using reserved private top-level domains, such as `.internal`, introduces friction in modern infrastructure. While routing traffic to private IP addresses (e.g., `10.0.1.10`) via a VPN-provided DNS resolver is straightforward, securing these endpoints with TLS typically requires self-signed certificates.
This approach introduces significant certificate management overhead. Every HTTP client across the internal network must be explicitly configured to trust the custom root Certificate Authority (CA). The alternative—instructing users to bypass browser TLS warnings—compromises the organization’s security posture and trains users to ignore critical security indicators.
Architecture: Split-Horizon DNS and Interface Binding
A cleaner alternative leverages a split-horizon DNS configuration using a public apex domain owned by the operator, such as `tuxnet.dev`. For public DNS resolvers, the domain resolves to a public IP, which allows public CAs like Let's Encrypt or ZeroSSL to validate domain ownership. Conversely, for clients connected to the internal VPN, the DNS resolver overrides this record, pointing the domain directly to the internal IP address.
To prevent public access to the internal service, access control must be enforced at the network and application layers. This is achieved by binding the reverse proxy exclusively to the VPN interface. In an Nginx configuration, this is defined by specifying the VPN IP or host address in the listen directive:
`listen our-server.netbird.cloud:443 ssl;`
This configuration ensures that Nginx only accepts connections originating from the VPN interface, effectively acting as a firewall. If the split-horizon DNS routing is bypassed or misconfigured, the underlying service remains protected because the port is not exposed to the public internet.
Automated Issuance via Standalone ACME and Linux Capabilities
To issue and renew certificates, the ACME client `acme.sh` can be executed in standalone mode using the `--standalone` flag. This mode temporarily spins up a listener on port 80 to complete the `http-01` challenge, meaning Nginx does not need to listen on port 80 or handle ACME challenge routing.
To maintain a strong security profile, the ACME client should run under a non-root system user, such as `acmesh`. However, binding to privileged ports like port 80 normally requires root privileges. This limitation is resolved by granting the `CAP_NET_BIND_SERVICE` capability to the underlying socket relay tool, `socat`, during the renewal window:
`setcap CAP_NET_BIND_SERVICE=+ep /usr/bin/socat1`
Once the cron job completes the `acme.sh --cron` execution, the capability is immediately revoked using `setcap -r /usr/bin/socat1`, minimizing the security footprint of the system.
Automated Verification and Deployment Pipeline
Automating certificate deployment requires a robust renewal pipeline that avoids unnecessary service disruptions. A daily cron utility handles certificate validation, verification, and service reloads.
The automation script compares the SHA256 checksums of the newly generated key files against the active keys in the Nginx SSL directory. If a mismatch is detected, indicating a successful certificate renewal, the script copies the new certificates and keys to their target locations.
- File permissions are set to `640` to restrict read access.
- The private key is assigned to the `www-data` group, allowing the worker processes to read it.
- The certificate is assigned to the `ssl-cert` group.
Before reloading the web server, the script verifies the configuration integrity by running `nginx -t`. If the test passes, it executes `systemctl reload nginx` to apply the updated certificates gracefully without dropping active connections.
Scaling with Subject Alternative Names
Generating individual certificates for every internal service introduces unnecessary renewal traffic and management complexity. While wildcard certificates are an option, they present broader security implications if a key is compromised.
A more precise approach uses Subject Alternative Names (SANs) combined with DNS CNAME records. A master "A" record is created for a primary internal domain, such as `internal.tuxnet.dev`. Individual services, such as `grafana.tuxnet.dev` and `analytics.tuxnet.dev`, are configured as CNAME records pointing to the primary internal domain.
A single certificate is then issued with multiple domain flags:
`acme.sh --issue -d internal.tuxnet.dev -d grafana.tuxnet.dev -d analytics.tuxnet.dev --server letsencrypt --standalone`
This single multi-domain certificate is then shared across the respective Nginx server blocks, streamlining certificate lifecycle management while maintaining isolated virtual host configurations.