Required ports
DisplayGrid uses three ports. All traffic stays on your local network. Nothing needs to be exposed to the internet.
| Port | Service | Protocol | Direction |
|---|---|---|---|
3000 | Dashboard (Next.js) | HTTP | Kiosks → Server, Admin browsers → Server |
3001 | WebSocket server | WS | Kiosks → Server |
5173 | Display client (dev only) | HTTP | Kiosk browser → Kiosk localhost |
Port 5173 is only used in development (Vite dev server). For production kiosks using a static build, the display client is served on whatever port you choose (e.g. 8080) and is local to the kiosk. Kiosks only need outbound access to ports 3000 and 3001 on the server.
Firewall rules
Linux (ufw)
# Allow dashboard and WS server from any LAN device
sudo ufw allow 3000/tcp
sudo ufw allow 3001/tcp
# Check status
sudo ufw status
Linux (firewalld / RHEL-based)
sudo firewall-cmd --permanent --add-port=3000/tcp
sudo firewall-cmd --permanent --add-port=3001/tcp
sudo firewall-cmd --reload
macOS
macOS prompts automatically when Node.js first binds to a port. Click Allow in the dialog. If you missed it, go to System Settings → Network → Firewall → Options and add Node.js to the allowed list.
Windows
Run these commands in PowerShell as Administrator:
New-NetFirewallRule -DisplayName "DisplayGrid Dashboard" `
-Direction Inbound -Protocol TCP -LocalPort 3000 -Action Allow
New-NetFirewallRule -DisplayName "DisplayGrid WebSocket" `
-Direction Inbound -Protocol TCP -LocalPort 3001 -Action Allow
Or use the GUI: Windows Defender Firewall → Advanced Settings → Inbound Rules → New Rule → Port.
mDNS / local hostname
By default you access DisplayGrid at 192.168.x.x:3000. There are two ways to use a friendlier hostname instead.
Option 1: hosts file (recommended for fixed setups)
Add a line to your hosts file pointing displaygrid.test at the server IP. The setup scripts do this automatically:
# macOS / Linux
sudo ./scripts/add-hosts.sh
# Windows (run PowerShell as Administrator)
.\scripts\add-hosts.ps1
Or add manually:
# File: /etc/hosts (macOS/Linux) or C:\Windows\System32\drivers\etc\hosts (Windows)
192.168.1.10 displaygrid.test
All admin machines need the hosts entry. The hosts file change only affects the device it's made on. Each laptop or PC you use to access the dashboard needs its own entry pointing to the server's IP.
Option 2: mDNS / Bonjour
If your network supports mDNS (most home and small office networks do), the server's hostname is reachable at hostname.local without any hosts file changes.
# Find the server's hostname
hostname # Linux / macOS
hostname # Windows (in cmd or PowerShell)
Then access DisplayGrid at http://<hostname>.local:3000 from any device on the same subnet. Update your env files accordingly:
VITE_API_BASE=http://displaygrid-server.local:3000
VITE_WS_BASE=ws://displaygrid-server.local:3001
mDNS does not cross VLAN boundaries. If your server and kiosks are on different VLANs, use a static IP or DNS entry. See the Cross-VLAN section below.
Cross-VLAN deployments
In managed network environments (offices, schools, hotels) kiosks are often on a separate VLAN from the server, for example a "displays" VLAN isolated from the admin LAN. DisplayGrid works across VLANs as long as the correct traffic is permitted.
What to allow on your router / firewall
| Source VLAN | Destination VLAN | Port | Protocol | Purpose |
|---|---|---|---|---|
| Displays | Server | 3000 | TCP | Asset fetching, API calls |
| Displays | Server | 3001 | TCP | WebSocket real-time updates |
| Admin | Server | 3000 | TCP | Dashboard access |
No traffic needs to flow from the server VLAN to the display VLAN; the connection is always initiated by the kiosk.
Using a static IP for the server
In multi-VLAN environments, assign the server a static IP (via DHCP reservation or static network config) so kiosk env vars never need to change:
# Ubuntu / Debian: edit Netplan config
sudo nano /etc/netplan/01-netcfg.yaml
network:
version: 2
ethernets:
eth0:
addresses: [192.168.10.5/24]
gateway4: 192.168.10.1
nameservers:
addresses: [192.168.10.1]
sudo netplan apply
Internal DNS
If you have an internal DNS server (Pi-hole, Unbound, Active Directory, etc.), add an A record for displaygrid.test or any hostname pointing to the server's static IP. All devices on all VLANs that use that DNS server will resolve it automatically, with no per-device hosts file changes needed.
Caddy reverse proxy
By default, the dashboard is served on port 3000 and the WebSocket server on port 3001. Caddy lets you serve both on standard port 80 (or 443 with HTTPS) so kiosks connect to http://displaygrid.test with no port number.
Install Caddy
# Debian / Ubuntu
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install caddy
# macOS (Homebrew)
brew install caddy
# Windows: download from https://caddyserver.com/download
The Caddyfile
The project root includes a Caddyfile already configured for DisplayGrid:
http://displaygrid.test {
# Dashboard
reverse_proxy /api/* localhost:3000
reverse_proxy /auth/* localhost:3000
reverse_proxy /_next/* localhost:3000
# WebSocket server
reverse_proxy /ws* localhost:3001
# Everything else → dashboard
reverse_proxy localhost:3000
}
Start Caddy
# From the project root
caddy run --config Caddyfile
Or run it as a background service:
# Linux systemd
sudo systemctl enable caddy
sudo systemctl start caddy
Update your env files
Once Caddy is running, remove the port numbers from your environment variables:
# apps/dashboard/.env.local
NEXTAUTH_URL=http://displaygrid.test
# apps/display-client/.env.local
VITE_API_BASE=http://displaygrid.test
VITE_WS_BASE=ws://displaygrid.test
Windows note: Port 80 requires elevated privileges on Windows. Run Caddy as Administrator, or add { http_port 8080 } to the top of your Caddyfile to use a non-privileged port instead.
HTTPS with a self-signed cert
For HTTPS on a local network, Caddy can generate a self-signed certificate automatically. Change the site address in the Caddyfile:
https://displaygrid.test {
tls internal
reverse_proxy localhost:3000
}
Run caddy trust once to add Caddy's local CA to your system trust store so browsers don't show a certificate warning.
Pre-flight checklist
Use this checklist before deploying to production to confirm the network is configured correctly.
| Check | Command / Action |
|---|---|
| Server reachable from kiosk | ping 192.168.1.10 from kiosk |
| Port 3000 open | curl http://192.168.1.10:3000/api/health → should return {"ok":true} |
| Port 3001 open | curl http://192.168.1.10:3001 → should return 426 Upgrade Required (normal, WS only) |
| WS connection works | Open kiosk display client → browser DevTools → Network → WS tab, look for a connected frame |
| Assets load on kiosk | Assign an image to a playlist and check the kiosk displays it |
| Dashboard reachable from admin machine | Navigate to http://192.168.1.10:3000 (or your hostname) |
| Firewall rules applied | sudo ufw status (Linux) or check Windows Firewall inbound rules |
426 Upgrade Required on port 3001 is normal. The WebSocket server speaks only the WebSocket protocol; any plain HTTP request (including curl or a browser address bar) returns 426. This confirms the port is open and the server is running correctly.