I’ve decided to introduce a new category of posts on my blog called FAFO. By now, you probably know what it stands for, so I’ll skip the full form. These posts will essentially be write-ups on challenges I’ve encountered and how I managed to overcome them through trial and exploration.

In the first FAFO post, I’ll be sharing how I solved DNS issues on the dev environment for the backend projects I’m working on.

Instead of running a local Django server on my Mac during development, I prefer to have everything on a Raspberry Pi 5 running on the local network. This approach offers some clear advantages:

  • The backend server remains accessible even when the Mac is turned off, which is particularly useful when building mobile apps.
  • It frees up storage space on my Mac by keeping server-related dependencies like Docker and DB software on the Pi.
  • It provides a setup closer to a real production environment, especially when not using Docker.
  • If anything goes wrong, I can quickly spin up a new Pi using a custom script I’ve prepared.

Now, if I only had one web app running on the Pi, things would be simple. I’ve assigned the name raspberrypi.local to the Pi, so using “http://raspberrypi.local” in the request URL would work fine. However, I have multiple web apps, which presented a challenge in making them all accessible on devices across the network.

I considered a few options:

  • Using port numbers (not ideal due to ugly URLs and potential conflicts with production configs)
  • Changing /etc/hosts for each device (too much effort and not efficient)
  • Adding DNS entries in the router settings (led to CORS issues with React frontend projects)

While these options could work, I wasn’t fully satisfied. Then I remembered that I had Pi-hole set up on the Raspberry Pi, primarily for blocking ads on the local network. Pi-hole was already configured as the DNS server, and I could add custom DNS mappings there.

To set up a custom domain in Pi-hole, follow these steps:

  1. Add Local DNS Entry:

    In Pi-hole, go to Settings > DNS and locate the Local DNS Records section. Add an entry for your custom domain (e.g., project.raspberrypi.local) and point it to your Raspberry Pi’s IP address, such as 192.168.1.10.

  2. Restart Pi-hole DNS Service:

    After adding the DNS entry, restart Pi-hole’s DNS service to apply changes:

     sudo pihole restartdns
    
  3. Verify the Setup Locally on Pi-hole:

    Run a ping for project.raspberrypi.local on the Raspberry Pi itself to ensure that Pi-hole resolves it correctly:

     ping project.raspberrypi.local
    

If this resolves correctly, the custom DNS entry is likely set up properly within Pi-hole.

However, in my case, after setting up the mappings in Pi-hole and configuring Nginx on the Raspberry Pi to route requests, I encountered an unexpected issue. DNS resolution was failing on my devices. I ran ping for these names on different devices and found out that the request wasn’t even showing up on the Pi-hole query log.

I ran nslookup and I got the response;

➜ nslookup project1.raspberrypi.local
  Server:       fe80::1%13
  Address:      fe80::1%13#53

  ** server cannot find project1.raspberrypi.local: NXDOMAIN
  Server:       192.168.1.10
  Address:      192.168.1.10#53

➜ nslookup project1.raspberrypi.local 192.168.1.10
  Server:       192.168.1.10
  Address:      192.168.1.10#53

  Name:	project1.raspberrypi.local
  Address: 192.168.1.10

After some digging, I realized that IPv6 was the culprit behind the DNS resolution issues. The nslookup results confirmed that the requests were still not going through Pi-hole and returned the dreaded “NXDOMAIN” error. This was unexpected since I’d set Pi-hole as the primary DNS server in router settings.

To solve this, I had to disable IPv6 on the router to ensure all DNS traffic would go through Pi-hole. I then enabled IPv6 support within Pi-hole itself to prevent any lingering IPv6 requests from slipping through. After these adjustments, I finally had the setup working exactly as I intended. Now, all project-specific names, like project1.raspberrypi.local and project2.raspberrypi.local, mapped directly to the Pi’s IP address, and nslookup responded as expected:

➜ nslookup project1.raspberrypi.local
Server:		192.168.1.10
Address:	192.168.1.10#53

Name:	project1.raspberrypi.local
Address: 192.168.1.10

➜ nslookup project2.raspberrypi.local
Server:		192.168.1.10
Address:	192.168.1.10#53

Name:	project2.raspberrypi.local
Address: 192.168.1.10

There are probably simpler ways to get a setup like this running, but I genuinely enjoyed diving into the process and working through each challenge to build something that fits my needs. If you have any insights on different or more efficient ways to tackle this setup, I’d love to hear them! Drop a comment below, or feel free to reach out on Twitter.