Configure Domains & DNS
Setup
Prerequisite: DNS server inside the VPN
The DNS server (Pi-Hole/AdGuard) must be reachable over the WireGuard VPN:
- Add Pi-Hole/AdGuard as a WireGuard peer in GateControl
- The peer receives a VPN IP (e.g.
10.8.0.50) - Configure Pi-Hole/AdGuard to listen on the VPN IP
Configure the global DNS
- Settings → General → DNS Server
- Enter the IP address of the DNS server (e.g.
10.8.0.50) - Click Save
Multiple DNS servers can be entered comma-separated (e.g. 10.8.0.50,1.1.1.1).
Per-peer DNS override
Individual peers can use a different DNS server:
- Peers → edit peer (edit icon)
- DNS Server (Override) — enter IP address
- Save
Leaving it empty = the global DNS is used.
Updating existing peers
Important: After a DNS change, existing peers must re-download their WireGuard configuration. GateControl cannot push DNS settings live to connected peers — this is a WireGuard limitation.
How to do it:
- Open the peer page → QR code or config download
- On the end device: delete the old WireGuard config, import the new one
- Re-establish the connection
New peers automatically receive the current DNS configuration.
Usage examples
Ad blocking for all VPN clients
- Connect Pi-Hole as a peer →
10.8.0.50 - Settings → DNS Server →
10.8.0.50 - All peers are ad-free
Mixed configuration
- Smartphones/Laptops → Pi-Hole DNS (global,
10.8.0.50) - IoT devices → default DNS (per-peer override:
1.1.1.1) — because Pi-Hole blocks some IoT domains - Kids' tablet → AdGuard with family filter (per-peer override:
10.8.0.51)
Internal DNS server
Enter a corporate DNS server as the custom DNS. VPN peers can then resolve internal hostnames (e.g. intranet.company.local).
API
Read global DNS
GET /api/v1/settings/dns
Response:
{
"ok": true,
"data": {
"dns": "10.8.0.50",
"is_custom": true,
"default_dns": "1.1.1.1,8.8.8.8"
}
}
Set global DNS
curl -X PUT /api/v1/settings/dns \
-H "Content-Type: application/json" \
-d '{"dns": "10.8.0.50"}'
An empty string resets to the default:
curl -X PUT /api/v1/settings/dns \
-H "Content-Type: application/json" \
-d '{"dns": ""}'
Set per-peer DNS
# When creating
curl -X POST /api/v1/peers \
-H "Content-Type: application/json" \
-d '{"name": "Laptop", "dns": "10.8.0.50"}'
# When editing
curl -X PUT /api/v1/peers/:id \
-H "Content-Type: application/json" \
-d '{"dns": "1.1.1.1"}'
# Remove override (back to global)
curl -X PUT /api/v1/peers/:id \
-H "Content-Type: application/json" \
-d '{"dns": ""}'
Validation
- DNS addresses must be valid IPv4 addresses
- Multiple addresses are accepted comma-separated (e.g.
10.8.0.50,1.1.1.1) - Format validation:
/^(\d{1,3}\.){3}\d{1,3}$/per entry
Limitations
- No automatic update of existing peers — peers must re-download their config (WireGuard limitation)
- IPv4 only — IPv6 DNS servers are not validated
- No DNS-over-HTTPS/TLS — standard DNS via UDP/TCP
- Client-side — DNS is only set in the client config, not on the server side
Technical details
Affected files
| File | Function |
|---|---|
src/services/license.js |
Feature key custom_dns in COMMUNITY_FALLBACK |
src/services/peers.js |
DNS fallback chain in getClientConfig() |
src/routes/api/settings.js |
GET/PUT /settings/dns endpoint |
src/routes/api/peers.js |
dns field in POST (create) |
src/middleware/locals.js |
wgDns to template context |
templates/default/pages/settings.njk |
DNS card in the General tab |
templates/default/partials/modals/peer-add.njk |
DNS override field |
templates/default/partials/modals/peer-edit.njk |
DNS override field |
public/js/settings.js |
DNS save handler |
public/js/peers.js |
DNS in create/edit payload |
Database
Global DNS: settings table, key custom_dns
Per-peer DNS: column peers.dns (TEXT) — has existed since the initial migration. Automatically included in backup/restore.
WireGuard client config
[Interface]
PrivateKey = ...
Address = 10.8.0.3/32
DNS = 10.8.0.50 ← custom DNS from the fallback chain
[Peer]
PublicKey = ...
Endpoint = vpn.example.com:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25
The DNS line is a wg-quick(8) directive — it is evaluated by the WireGuard client, not the server. The server-side wg syncconf correctly ignores DNS.