One Zone, 5 Subdomains: A Separate Internal DNS Zone + Nginx Server Blocks for a New System – MOUHIMEN.DEV
One Zone, 5 Subdomains: A Separate Internal DNS Zone + Nginx Server Blocks for a New System

One Zone, 5 Subdomains: A Separate Internal DNS Zone + Nginx Server Blocks for a New System

By Abd Almouhimen shamout | October 20, 2026

One Zone, 5 Subdomains: A Separate Internal DNS Zone + Nginx Server Blocks

Watch the 2-minute video:

Here’s a ticket every sysadmin gets sooner or later. The dev team ships a new system, let’s call it EduHub, and it has five interfaces:

  • an API (Laravel)
  • an admin panel
  • a student portal
  • an exam bank
  • a portal for academic staff

Everyone on the network needs to reach them by name, not by 10.10.20.15:3002. In this post we’ll do the whole thing: a new DNS zone, the records, and the Nginx configuration that sends each name to the right app.

Step 1: Give the system its own zone

The tempting shortcut is to add api, admin and friends straight into your Active Directory domain zone (say college.org). Don’t. Give every system its own zone:

SystemZone
EduHubeduhub.internal
Librarylibrary.internal
HRhr.internal

Why a separate zone per system?

  • Your AD zone stays clean. It holds what AD needs (DCs, SRV records, member servers), not dozens of app names.
  • Each system can move or retire on its own. Decommissioning EduHub is one Remove-DnsServerZone, with no hunting for stray records.
  • You can delegate it. You can give the EduHub team rights on their zone only, or host it on a different DNS server later with a conditional forwarder.

Why .internal and not .local?

  • .local is reserved for multicast DNS (mDNS, RFC 6762). macOS, iOS and many Linux desktops send .local lookups to mDNS instead of your DNS server, so names randomly fail or resolve slowly on those devices.
  • .internal was reserved by ICANN in 2024 for private use. It will never be delegated on the public internet, so it can’t collide with a real domain.

Another good option is a subdomain of a domain you actually own, like eduhub.int.mouhimen.dev. That one also lets you get public TLS certificates later.

Step 2: Create the zone

On your domain controller (or whichever DNS server your clients use):

GUI: DNS Manager → right-click Forward Lookup Zones → New Zone → Primary zone → tick Store the zone in Active Directory → To all DNS servers in this domain → zone name eduhub.internal → Finish.

PowerShell, one line:

Add-DnsServerPrimaryZone -Name "eduhub.internal" -ReplicationScope "Domain"

Because it’s AD-integrated, it replicates to every DC running DNS, and every domain-joined PC can resolve it right away.

Step 3: Add an A record for the main server

Add-DnsServerResourceRecordA -ZoneName "eduhub.internal" -Name "web01" -IPv4Address "10.10.20.15"

web01 is the Ubuntu box that runs Nginx and the five apps. If you also want eduhub.internal by itself to open the site, add -Name "@" with the same IP.

Step 4: Add the subdomains as CNAMEs

"api","admin","students","exams","staff" | ForEach-Object {
    Add-DnsServerResourceRecordCName -ZoneName "eduhub.internal" -Name $_ -HostNameAlias "web01.eduhub.internal"
}

Why CNAMEs and not five A records? If the server’s IP changes, you edit one record, the A record for web01, and all five names follow it.

Check the result:

Get-DnsServerResourceRecord -ZoneName "eduhub.internal" | Format-Table HostName, RecordType, RecordData

Step 5: One Nginx server block per subdomain

DNS now sends all five names to the same IP. Nginx looks at the Host header of each request and picks the server block whose server_name matches. Create one file per subdomain in /etc/nginx/sites-available/:

# /etc/nginx/sites-available/api.eduhub.internal
server {
    listen 80;
    server_name api.eduhub.internal;

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

The other four are the same file with two lines changed:

Subdomainproxy_passApp
api.eduhub.internalhttp://127.0.0.1:8000Laravel API
admin.eduhub.internalhttp://127.0.0.1:3001Admin panel
students.eduhub.internalhttp://127.0.0.1:3000Student portal
exams.eduhub.internalhttp://127.0.0.1:3002Exam bank
staff.eduhub.internalhttp://127.0.0.1:3003Academic staff portal

Next.js apps that use websockets (dev hot reload, live features) also need proxy_http_version 1.1; and the Upgrade/Connection headers. They’re in the commands file that comes with the video.

Enable the sites:

sudo ln -s /etc/nginx/sites-available/*.eduhub.internal /etc/nginx/sites-enabled/

Step 6: Add a catch-all block

Without it, a typo like exmas.eduhub.internal (or someone hitting the raw IP) lands on whichever block Nginx treats as default, which is often your API. Make the default explicit and have it drop the connection:

server {
    listen 80 default_server;
    server_name _;
    return 444;
}

444 is Nginx’s special code: it closes the connection without sending anything back.

Test it

On web01:

sudo nginx -t
sudo systemctl reload nginx

From any domain PC:

nslookup students.eduhub.internal
curl.exe -I http://students.eduhub.internal

nslookup should show Name: web01.eduhub.internal, Address: 10.10.20.15 and Aliases: students.eduhub.internal. curl.exe should return HTTP/1.1 200 OK. Use curl.exe, not curl: in Windows PowerShell 5.1, curl is an alias for Invoke-WebRequest.

Checklist

  • [ ] Separate zone per system (eduhub.internal), AD domain zone untouched
  • [ ] .internal (or a subdomain you own), never .local
  • [ ] A record for the main server
  • [ ] Subdomains as CNAMEs to the main server
  • [ ] One Nginx server block per subdomain, enabled in sites-enabled
  • [ ] Catch-all default_server returning 444
  • [ ] nginx -t, reload, nslookup + curl.exe from a client

What’s next: HTTPS

Public CAs like Let’s Encrypt can’t issue certificates for .internal names. For HTTPS, use your own internal CA (AD Certificate Services) and deploy its root certificate to domain PCs with Group Policy, or issue a wildcard certificate for *.eduhub.internal. That’s a video of its own.

Watch the video:

Sources

  • Microsoft Learn, Add-DnsServerPrimaryZone: https://learn.microsoft.com/en-us/powershell/module/dnsserver/add-dnsserverprimaryzone
  • Microsoft Learn, Add-DnsServerResourceRecordCName: https://learn.microsoft.com/en-us/powershell/module/dnsserver/add-dnsserverresourcerecordcname
  • Microsoft Learn, Add-DnsServerResourceRecordA: https://learn.microsoft.com/en-us/powershell/module/dnsserver/add-dnsserverresourcerecorda
  • The Register, “ICANN reserves .internal for private use at the DNS level” (Aug 2024): https://www.theregister.com/2024/08/08/dot_internal_ratified/
  • Wikipedia, .internal: https://en.wikipedia.org/wiki/.internal
  • RFC 6762, Multicast DNS (the .local name space): https://www.rfc-editor.org/rfc/rfc6762
  • Nginx docs, How nginx processes a request (server_name, default_server, 444): https://nginx.org/en/docs/http/request_processing.html
  • Nginx docs, ngx_http_proxy_module (proxy_pass): https://nginx.org/en/docs/http/ngx_http_proxy_module.html

Abd Almouhimen Shamout

Full-Stack Web Developer with 16+ years of experience in designing, developing, and managing enterprise-level web systems and applications.
Skilled in both frontend and backend technologies with proven expertise in system integration, database management, and cloud services.
Successfully delivered solutions serving thousands of users in higher education and healthcare sectors. Recognized for strong problem-solving skills, leadership, and the ability to drive digital transformation projects.

Skills

tailwindcss
oracle
bootstrap
azuredevops
csharp
mongodb
androidstudio
wordpress
reactnative
javascript
react
livewire