Required ports

DisplayGrid uses three ports. All traffic stays on your local network. Nothing needs to be exposed to the internet.

PortServiceProtocolDirection
3000Dashboard (Next.js)HTTPKiosks → Server, Admin browsers → Server
3001WebSocket serverWSKiosks → Server
5173Display client (dev only)HTTPKiosk 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 VLANDestination VLANPortProtocolPurpose
DisplaysServer3000TCPAsset fetching, API calls
DisplaysServer3001TCPWebSocket real-time updates
AdminServer3000TCPDashboard 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.

CheckCommand / 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.