Skip to main content
Proxy Basics

How to Configure Local DNS for Nginx Proxy Manager [2026]

8 min read

Introduction

When self-hosting applications on your Local Area Network (LAN) using Nginx Proxy Manager (NPM), a common point of confusion is how to make internal domains—like photos.local or dash.internal—accessible to other devices in your house. Unlike public domains purchased through registrars (like Namecheap or GoDaddy), local domains do not exist on the public internet.

Consequently, you cannot simply point a browser to them unless you explicitly tell your network how to resolve these names. This guide covers the architectural requirements and step-by-step implementation of integrating a Local DNS system with Nginx Proxy Manager to create a seamless, secure local development or homelab environment.

---

Architecture Overview

To understand the solution, we must visualize the traffic flow. In a standard setup without Local DNS:

1. Client Device: Tries to resolve myservice.home. 2. ISP/Router DNS: Says "I don't know this domain" and returns an error (NXDOMAIN).

In a correctly architected Local DNS + NPM setup:

1. Client Device: Tries to resolve myservice.home. 2. Local DNS Server (e.g., Pi-hole): Recognizes the custom domain and replies with the Local IP of the NPM Server (e.g., 192.168.1.50). 3. Client Device: Connects to 192.168.1.50 (NPM) via HTTP/HTTPS. 4. Nginx Proxy Manager: Inspects the Host header, matches it to a specific Proxy Host configuration, and forwards the traffic to the actual backend container (e.g., a Docker container running on port 8080).

---

Prerequisites

Before proceeding, ensure you have the following:

  • Nginx Proxy Manager Installed: Typically deployed via Docker or a bare-metal installation.
  • Static Local IP: The device running NPM must have a static DHCP reservation or a static IP address (e.g., 192.168.1.50) configured on your router. If the server IP changes, your local DNS will point to the wrong location.
  • Root/Admin Access: Ability to modify your router's DHCP settings or access a device to act as a DNS server.
  • ---

    Step 1: Setting Up a Local DNS Server

    Most standard routers do not allow granular control over DNS hostmapping easily. Therefore, the industry standard for homelabs and self-hosting is to deploy a dedicated DNS solution. The most popular choices are Pi-hole or AdGuard Home.

    Option A: Using Pi-hole (Recommended)

    Pi-hole acts as a DNS sinkhole but, crucially, allows you to define Custom DNS Records (Local DNS).

    1. Deploy Pi-hole: Use Docker or install it on a Raspberry Pi.

        docker run -d \
    

    --name pihole \ -p 53:53/tcp -p 53:53/udp \ -p 80:80 \ -e TZ='America/New_York' \ pihole/pihole:latest

    2. Configure Router DHCP: Log into your home router. Locate the DHCP settings. Change the "Primary DNS" setting from your ISP's default or Google's (8.8.8.8) to the local IP address of your Pi-hole (e.g., 192.168.1.60). This forces every device on your network to ask Pi-hole for DNS resolution.

    Option B: Using Hosts Files (Manual/Single Device)

    If you do not wish to run a dedicated DNS server, you can modify the hosts file on individual devices. This is not scalable for a whole family but works for the administrator.

  • Windows: C:\Windows\System32\drivers\etc\hosts
  • Linux/Mac: /etc/hosts
  • Entry to add: 192.168.1.50 npm.yourdomain.local

    ---

    Step 2: Defining Local DNS Records

    Once your Local DNS server is the authority for your network, you need to point your domains to the NPM instance.

    1. Log in to your Pi-hole/AdGuard admin dashboard. 2. Navigate to Local DNS (Pi-hole) or DNS Rewrites (AdGuard). 3. Add a new record: * Domain: npm.local (or home.local, *.internal) * IP Address: 192.168.1.50 (The IP of your Nginx Proxy Manager server).

    Wildcard Support: For a cleaner setup, use a wildcard if your DNS server supports it (Pi-hole does via Regex or CNAME setups, but specific A records are safer for beginners). Create individual records for every service you intend to proxy:

  • photos.local -> 192.168.1.50
  • dash.local -> 192.168.1.50
  • stream.local -> 192.168.1.50
  • All of these point to the same NPM server. NPM handles the routing based on the domain name in the request header.

    ---

    Step 3: Nginx Proxy Manager Configuration

    Now that the network knows where to send the traffic, you must configure NPM to accept it.

    1. Access the NPM Web UI (usually port 81 or 443). 2. Navigate to Hosts > Proxy Hosts. 3. Click Add Proxy Host. 4. Details: * Domain Names: photos.local (Must match the DNS record exactly). * Scheme: http * Forward Hostname/IP: 192.168.1.55 (The actual internal IP of the service, e.g., your PhotoPrism server). * Forward Port: 2342 (The port the service listens on). 5. SSL (Critical): * Select Request a new SSL Certificate. * Force SSL is recommended. * *Challenge Type:* Since .local domains are not public, you cannot use HTTP-01 validation (Let's Encrypt tries to verify from the public internet). * *Solution:* You must use DNS Challenge or simply generate a self-signed certificate (and trust it on your devices). For the DNS Challenge, you need a provider that supports API validation (even for public domains pointing to local IPs).

    ---

    Python Script: Automating Local DNS Resolution Checks

    As a scraping or proxy expert, you often need to verify if your local DNS is routing correctly before running a complex scraping job. Below is a Python script using socket and dnspython to verify your configuration.

    import socket
    

    import dns.resolver

    def check_local_dns(domain, expected_ip): """ Checks if the local domain resolves to the specific NPM IP. """ print(f"Checking DNS for: {domain}") try: # Verify resolution using the system's configured DNS (Pi-hole) answers = dns.resolver.resolve(domain, 'A') for rdata in answers: resolved_ip = rdata.address print(f"Resolved IP: {resolved_ip}")

    if resolved_ip == expected_ip: print(f"[SUCCESS] DNS correctly points to NPM Server ({expected_ip}).") return True else: print(f"[FAIL] Expected {expected_ip}, got {resolved_ip}.") return False except dns.resolver.NXDOMAIN: print(f"[ERROR] Domain {domain} does not exist (NXDOMAIN). Check DNS Records.") return False except Exception as e: print(f"[ERROR] An error occurred: {e}") return False

    def check_npm_connectivity(ip, port=80): """ Checks if Nginx Proxy Manager is accepting connections. """ print(f"\nChecking TCP connectivity to NPM at {ip}:{port}...") s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.settimeout(2) result = s.connect_ex((ip, port)) s.close()

    if result == 0: print("[SUCCESS] NPM is reachable and listening.") else: print("[FAIL] Cannot connect to NPM. Check firewall/port forwarding.")

    if __name__ == "__main__": # Configuration LOCAL_DOMAIN = "photos.local" NPM_SERVER_IP = "192.168.1.50"

    check_local_dns(LOCAL_DOMAIN, NPM_SERVER_IP) check_npm_connectivity(NPM_SERVER_IP)

    ---

    Handling SSL Certificates for Local Domains

    One of the biggest hurdles with Local DNS and NPM is encryption. Browsers naturally complain when using HTTPS on a non-public domain.

    1. Self-Signed Certificates (The Default)

    NPM can generate a certificate authority (CA). You must download this CA and install it into the "Trusted Root Certification Authorities" store on Windows, macOS, Android, and iOS.

  • Pros: Free, secure, private.
  • Cons: Tedious to install the CA on every device.
  • 2. DNS Challenge (The Advanced Way)

    If you own a public domain (e.g., mydomain.com), you can use subdomains like photos.mydomain.com to point to local services via Local DNS. 1. Set Local DNS for photos.mydomain.com -> 192.168.1.50. 2. In NPM, use DNS Challenge for Let's Encrypt. This validates the domain via your domain registrar's API (like Cloudflare) rather than opening a port to the world. 3. This provides valid, trusted SSL certificates for internal services without exposing ports to the internet.

    ---

    Comparison: Local DNS Methods

    | Feature | Router Hostmapping | Pi-hole / AdGuard Hosts | Hosts File | | :--- | :--- | :--- | :--- | | Ease of Setup | Medium (varies by router firmware) | Easy (Web UI) | Tedious (Per Device) | | Central Management | Yes | Yes | No (Decentralized) | | Wildcard Support | Rare / No | Yes (via Regex/CNAME) | No | | Speed | Fast | Very Fast | Instant | | Recommended? | Only if no other options | Yes (Best Choice) | Only for testing |

    ---

    Troubleshooting Common Issues

    1. "Unable to Connect" or "ERR_CONNECTION_REFUSED"

    This usually means the DNS worked, but the NPM container isn't running or the port is closed.

  • Fix: Ensure the NPM Docker container is running. Check docker ps. Verify no firewall on the host is blocking port 80/443.
  • 2. "502 Bad Gateway"

    Your DNS is correct, NPM accepted the request, but it cannot reach the backend service.

  • Fix: Check the "Forward IP" in NPM. Ensure your backend service (e.g., a Python scraper container) is actually running and accessible from the NPM server (try curl http://backend-ip:port from the NPM server terminal).
  • 3. DNS Reverting to ISP Default

    If your Local DNS (Pi-hole) restarts, clients might fall back to Google DNS.

  • Fix: Configure your Router to use the Pi-hole IP for DNS, and configure the Pi-hole to use a Conditional Forwarder for your local TLD (e.g., forward all .local requests to a specific internal DNS).

---

Conclusion

Integrating Local DNS with Nginx Proxy Manager transforms a messy list of IP addresses and ports into a clean, professional-looking dashboard of named services. By decoupling your internal routing from public internet DNS, you gain significant performance improvements and control. While setting up a dedicated DNS server like Pi-hole requires initial effort, it is the foundation of any robust homelab or development environment. Remember to always validate your routing with scripts if you are automating deployments, and pay close attention to SSL management to keep your internal browsing secure and warning-free.

Share: