The Problem
If you're self-hosting services at home, you've probably run into this frustrating problem: your services work perfectly from outside your network, but when you're at home and try to access them using your domain name, it just doesn't work.
This is called the hairpin NAT problem (also known as NAT loopback), and this is how it finally got fixed.
What is Hairpin NAT?
When you access humfurie.org from outside, your request goes to your public IP, gets forwarded to your internal server, and everything works fine. But when you're inside your own network and try the same thing, your router gets confused, traffic goes out, tries to come back in, and breaks.
The "proper" fix is hairpin NAT rules on your router. But that turns out to be unreliable, especially on MikroTik routers where a feature called fasttrack bypasses NAT rules for established connections, making hairpin NAT silently fail even when configured correctly.
The Better Fix: DNS Override with AdGuard Home
Instead of fighting with NAT rules, the cleaner solution is to make your local network resolve your domains directly to your server's internal IP. No hairpin needed.
Step 1: Install AdGuard Home
On your Ubuntu server, create a docker-compose.yml:
services:
adguardhome:
image: adguard/adguardhome
container_name: adguardhome
restart: unless-stopped
ports:
- "53:53/tcp"
- "53:53/udp"
- "3000:3000/tcp"
volumes:
- ./adguard/work:/opt/adguardhome/work
- ./adguard/conf:/opt/adguardhome/conf
labels:
- "traefik.enable=true"
- "traefik.http.routers.adguard.rule=Host(`adguard.yourdomain.com`)"
- "traefik.http.routers.adguard.entrypoints=websecure"
- "traefik.http.services.adguard.loadbalancer.server.port=80"
Step 2: Free Up Port 53
Ubuntu uses systemd-resolved on port 53 by default. Disable it first:
sudo systemctl disable systemd-resolved
sudo systemctl stop systemd-resolved
sudo rm /etc/resolv.conf
echo "nameserver 8.8.8.8" | sudo tee /etc/resolv.conf
Then start AdGuard:
docker compose up -d
Step 3: Complete AdGuard Setup
Access http://your-server-ip:3000 and complete the setup wizard. After setup, update your Traefik label to use port 80 instead of 3000 and restart the container.
Step 4: Point MikroTik DHCP to AdGuard
In MikroTik go to IP, DHCP Server, Networks and set the DNS Server to your Ubuntu server's IP (e.g. 192.168.88.109). This makes all devices on your network use AdGuard automatically.
Step 5: Add DNS Rewrites in AdGuard
Go to Filters, DNS Rewrites and add entries for each domain, you need both the root domain and a wildcard for subdomains:
| Domain | Answer |
|---|---|
| humfurie.org | 192.168.88.109 |
| *.humfurie.org | 192.168.88.109 |
| yourdomain.com | 192.168.88.109 |
| *.yourdomain.com | 192.168.88.109 |
Note: *.humfurie.org covers subdomains but NOT the root domain itself, so you need both entries.
Step 6: Verify
Renew your DHCP lease and test:
nslookup humfurie.org
You should see your internal IP in the response instead of your public IP.
Gotchas
Tailscale: If you use Tailscale, it overrides DNS on your device. Disable "Use Tailscale DNS settings" in Tailscale preferences, otherwise your device ignores AdGuard entirely.
MikroTik DNS cache: MikroTik caches DNS responses. If things aren't working after changes, flush the cache in terminal: /ip dns cache flush
Docker and port 53: AdGuard's admin UI runs on port 3000 during initial setup, then switches to port 80. Update your Traefik config accordingly after setup.
Result
All your self-hosted services are now accessible from inside and outside your network using the same domain names. AdGuard Home also gives you network-wide ad blocking and DNS query logging as a bonus.