Networking
Reach your biga over IPv4 and IPv6, on an intranet, over a VPN, or behind a proxy you already run.
This page assumes the Docker setup. The same guide ships with
it as docs/networking.md.
How traffic flows
Browsers and the biga CLI connect to Caddy on TCP 80 and 443 and UDP 443. Caddy sends /api/sessions requests to the API server and everything else to the website. The server talks to Postgres on an internal network that has no route out and no published port.
- Only Caddy is exposed. It terminates TLS and routes requests.
- Postgres is on an internal network, with no route out and no published port.
- The server trusts only Caddy for a client's address, and Caddy sends
exactly one: the real client, after its own
BIGA_UPSTREAM_PROXIES. That address drives rate limits, and IPv6 clients are counted per /64.
The CLI and the viewer share one URL, BIGA_PUBLIC_URL: the CLI uploads to
/api/sessions/... on it and prints BIGA_PUBLIC_URL/session/<id>.
Pick a mode
| Mode | Use it for | Certificates | Clients need |
|---|---|---|---|
public | A domain on the internet | Let's Encrypt, automatic | Nothing |
internal | Office LAN, homelab, VPN, a bare IP | Caddy's local CA | To trust the CA once |
http | A network you fully trust, or TLS terminated in front | None | Nothing |
./scripts/setup.sh asks for the mode and the name or address and writes the
rest. What follows is what to set up around it.
IPv4 on the internet
Point DNS at the host
Create an A record for your domain with the host's public IPv4 address.
Open the ports
Allow inbound TCP 80 and 443, and UDP 443 for HTTP/3, in your cloud provider's firewall and on the host. Port 80 is required: Let's Encrypt validates through it, and Caddy redirects it to HTTPS. Behind a home router, forward the same ports to the host's LAN address and give the host a fixed LAN address.
Check from outside
curl -4 -sI https://biga.example.com/ | head -1Carrier-grade NAT
If your router's WAN address is in 100.64.0.0/10, or differs from what
curl -4 ifconfig.co reports, your ISP shares one IPv4 address between
customers and inbound IPv4 cannot reach you. Use IPv6, a VPN, a tunnel such
as Cloudflare Tunnel, or a small VPS.
Docker bypasses ufw
Docker publishes ports with its own iptables rules, which run before ufw's, so
ufw deny 443 does not close a port Docker published. Bind Caddy to one
address with BIGA_BIND_ADDRESS, or filter in the DOCKER-USER chain, which
Docker evaluates first:
sudo iptables -I DOCKER-USER -i eth0 ! -s 203.0.113.0/24 -j DROPIPv6
biga works over IPv6 end to end: Caddy, the server and the CLI all handle it, and Let's Encrypt validates over IPv6.
Add an AAAA record
Use the host's stable global address. On Linux, ip -6 addr show scope global
lists them; skip any marked temporary, which rotate.
Open the firewall
IPv6 has no NAT, so there is nothing to forward: allow inbound TCP 80 and 443 and UDP 443 to the host, on the host firewall and on the router. Many routers drop unsolicited inbound IPv6 by default.
Turn on IPv6 inside Docker
docker compose down
docker compose -f compose.yaml -f compose.ipv6.yaml up -dDocker publishes ports on [::] either way, but on an IPv4-only network it
relays IPv6 connections through a userland proxy, and Caddy sees every IPv6
visitor as the Docker gateway: one shared rate-limit budget, and logs that
say nothing. With IPv6 on the edge network, connections are routed natively
and keep their addresses.
Docker Engine 27 or newer picks a unique local /64 by itself. On older engines
set "ipv6": true and "ip6tables": true in /etc/docker/daemon.json and
choose a subnet in compose.ipv6.yaml. Add
COMPOSE_FILE=compose.yaml:compose.ipv6.yaml to .env so every later
command includes the file.
Check both families
curl -6 -sI https://biga.example.com/ | head -1
curl -4 -sI https://biga.example.com/ | head -1Brackets around IPv6 addresses
Bare IPv6 addresses go in brackets wherever a URL or port is involved:
BIGA_PUBLIC_URL=https://[2001:db8::10], BIGA_BIND_ADDRESS=[2001:db8::10],
BIGA_API_URL=https://[2001:db8::10]. setup.sh adds them for you.
An IPv6-only host serves only IPv6 clients. Offer both families if viewers might be on IPv4-only networks, which is still common on mobile and in offices.
An office or home network
Choose a name
- A record on your local DNS server, router, Pi-hole or dnsmasq:
biga.internal → 192.168.1.20. Prefer.internal, which is reserved for private networks, orhome.arpa. Avoid.local, which belongs to mDNS. - The host's mDNS name, such as
studio.local. macOS, most Linux desktops and Windows 10+ resolve it without a DNS server. - A bare address:
192.168.1.20, or[fd00::20]for IPv6.
Set up in internal mode
./scripts/setup.sh --mode internal --domain biga.internal
docker compose up -d --build
./scripts/trust-ca.shtrust-ca.sh exports the root certificate of Caddy's local certificate
authority. Install it once on every device that opens the site or runs the
CLI; the script prints the command for each OS. After that HTTPS works with no
warnings, and the CLI connects as it would to a public server.
Listen only on the LAN
If the host has other interfaces, such as a public NIC or a VPN, bind to the
LAN address in .env:
BIGA_BIND_ADDRESS=192.168.1.20Keep access keys on: anyone on the network can reach the server, and a key
decides who may broadcast through it. If many people reach it through one NAT
address, raise BIGA_READ_RATE_LIMIT or set it to 0.
HTTP mode on a LAN
http mode skips the CA but sends owner tokens and access keys in clear text.
Use it only on a network you would trust with them.
No internet at all
Build the images on a connected machine and carry them over:
docker compose build
docker save biga/server:local biga/web:local postgres:17-alpine caddy:2-alpine \
| gzip > biga-images.tar.gz
# on the air-gapped host
gunzip -c biga-images.tar.gz | docker load
docker compose up -dNothing is pulled (BIGA_PULL_POLICY defaults to never). Use internal or
http mode, since Let's Encrypt needs the internet.
A VPN
Treat a Tailscale, WireGuard or ZeroTier network like an intranet. Bind to the host's VPN address so the server answers only over the VPN, and use internal mode with that address or the VPN's DNS name:
./scripts/setup.sh --mode internal --domain 100.101.102.103 --bind 100.101.102.103With Tailscale, the MagicDNS name (studio.tail1234.ts.net) works as the
domain too.
Behind a proxy you already run
When nginx, Traefik or a load balancer already owns ports 80 and 443, run the
stack in http mode on a loopback port and proxy to it:
./scripts/setup.sh --mode http --domain biga.example.com --bind 127.0.0.1 --http-port 8088Set BIGA_PUBLIC_URL to the public https:// address, and set
BIGA_UPSTREAM_PROXIES to the address Caddy sees your proxy from (through a
loopback port that is the Docker bridge gateway; docker network inspect biga_edge shows it). The proxy must stream server-sent events and accept
large uploads:
location / {
proxy_pass http://127.0.0.1:8088;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off; # live updates
proxy_read_timeout 1h; # long-lived streams
client_max_body_size 64m; # snapshot uploads
}Behind Cloudflare's proxy, keep public mode and set BIGA_UPSTREAM_PROXIES to
Cloudflare's published IP ranges, or every visitor looks like Cloudflare.
Ports
| Port | Protocol | Purpose |
|---|---|---|
| 80 | TCP | Redirect to HTTPS, Let's Encrypt validation; everything in http mode |
| 443 | TCP | HTTPS |
| 443 | UDP | HTTP/3 (optional; clients fall back to TCP) |
BIGA_HTTP_PORT and BIGA_HTTPS_PORT change the host side. Public mode needs
the defaults, because Let's Encrypt validates on port 80.
Troubleshooting
| Symptom | Likely cause |
|---|---|
| Caddy logs certificate errors | DNS does not point here yet, or port 80 is blocked or forwarded elsewhere. See docker compose logs caddy. |
certificate signed by unknown authority | Internal mode without the CA on that device. Run ./scripts/trust-ca.sh. |
The CLI says cannot reach … | Firewall, a missing forward, or the wrong BIGA_API_URL. Try curl -v with the same URL on the same machine. |
401 access_key_required | Set BIGA_ACCESS_KEY to one of the server's keys. |
| Viewers stuck on "Reconnecting" | A proxy in front is buffering the event stream. Turn buffering off. |
| Every client has the same address in the logs | IPv6 without compose.ipv6.yaml, Docker Desktop, or a proxy missing from BIGA_UPSTREAM_PROXIES. |
| Caddy answers 502 after adding an override file | The stack was not recreated. Run docker compose down, then up again. |
| Works on IPv4 but not IPv6 | No AAAA record, or a router or host firewall dropping inbound IPv6. |
Docker Desktop
Fine for trying biga out, but it runs containers in a VM: Caddy sees every client as the VM's gateway, and inbound IPv6 is limited. Deploy on Linux.