# No domain, just an IP

Source: https://docs.gryt.chat/docs/host/no-domain

Open three ports, hand out your address, done. The way a Minecraft server works.

You don't need a domain name to host Gryt. You don't need a certificate, a
reverse proxy, or a tunnel. Open a few ports, tell people your IP, and they
connect.

This is the setup most people actually want for a LAN party or a server for
friends, and it's the least documented, because the rest of these guides are
about the harder cases.

<Callout type="warn" title="Desktop app only">
  People joining this way need the [desktop app](https://docs.gryt.chat/docs/use/installation).
  A browser at app.gryt.chat can't connect to a plain IP, and the reason is
  worth understanding before you plan around it. See [Why the browser is blocked](#why-the-browser-is-blocked)
  at the end.
</Callout>

## Open these ports

Three, on the machine running the server, forwarded through your router if
people are joining from outside your network.

| Port | Protocol | What it is |
|------|----------|------------|
| `5000` | TCP | The server itself. Chat, logins, everything except voice |
| `5005` | TCP | Voice signalling. Which isn't the voice itself |
| `3478` | UDP | The actual voice and video, `ICE_UDP_MUX_PORT` |

Voice doesn't travel over the same connection as the chat, and it's UDP, not
TCP. If you forward the first two and stop there, everything works until
somebody joins a voice channel — and then nobody can hear anybody.

It's one port, not a range, and 3478 everywhere: the SFU, the CLI and the
compose files all use it. Change it with `ICE_UDP_MUX_PORT` and forward whatever
you pick.

3478 is the IANA STUN port, which is the reason to keep it. A corporate or school
firewall has most likely opened outbound UDP 3478 already, because Microsoft
Teams requires 3478–3481 and Zoom uses it too. You're borrowing a hole somebody
else's software already argued for.

## Tell the server its own address

The server has no way of knowing what address the outside world reaches it on,
so you tell it. Two lines in your `.env`:

```bash
# The address people will type. Your public IP for internet, LAN IP for a party.
ICE_ADVERTISE_IP=203.0.113.10
SFU_PUBLIC_HOST=ws://203.0.113.10:5005
```

Then restart:

```bash
docker compose up -d
```

Without these, voice connects to whatever the server thinks it is, which on a
machine behind a router is a private address nobody outside can reach. Chat will
work fine and voice will fail — a confusing way to find out.

Finding the values:

```bash
curl -4 ifconfig.me          # public IP
hostname -I | awk '{print $1}'   # LAN IP
```

<Callout type="warn" title="Not 0.0.0.0">
  `0.0.0.0` means "every interface" when you're deciding what to *listen* on,
  which is why `HOST=0.0.0.0` is correct. These two are different: they're
  addresses handed to other people's clients, and to a client `0.0.0.0` means
  its own machine. It goes out as an ICE candidate nobody can send to, and as
  an SFU address that points each person at their own computer. Loopback and
  `localhost` fail the same way. Put the address people type to reach you.
</Callout>

## Hand out the address

`203.0.113.10:5000` — that's the whole thing. People paste it into **Add a
server** in the desktop app.

The first person to join a new server becomes its owner. After that it's
invite-only, so make an invite in **Server settings → Invites** and share the
link.

Hosting for people in the same room, and tired of handing out codes? Turn on
**Allow anyone on LAN to join** in **Server settings → Overview**. Anyone on a
private IP range walks in without an invite. Anyone from outside still needs
one.

## If people are on your LAN and elsewhere at once

Both values take a comma-separated list, and the client pings each one and uses
whichever answers fastest. So a machine that's both `192.168.1.100` at the
party and `203.0.113.10` from outside:

```bash
ICE_ADVERTISE_IP=203.0.113.10,192.168.1.100
SFU_PUBLIC_HOST=ws://203.0.113.10:5005,ws://192.168.1.100:5005
```

People in the room get the LAN path and near-zero latency. People at home get
the public one.

## Why the browser is blocked

Whether a browser can reach a plain IP decides whether this setup fits you.

`app.gryt.chat` is served over HTTPS, and a browser refuses to let an HTTPS page
open a plain connection to anything except localhost. That's a browser rule
about page origins — not something Gryt configures, and no server setting turns
it off. The connection is blocked before it reaches you.

The desktop app runs its own copy of the client from `http://127.0.0.1`, which
isn't an HTTPS page, so the rule never applies. It also counts as a trusted
origin for microphone access, which a plain IP in a browser doesn't. Both of
those come for free and neither is something you configure.

So if browser access matters to you, you need a real hostname and a real
certificate, which means one of:

- [Cloudflare Tunnel](https://docs.gryt.chat/docs/host/cloudflare-tunnel), if you have a domain
- [Tailscale](https://docs.gryt.chat/docs/host/tailscale), which issues certificates for its own
  hostnames without you owning anything

A self-signed certificate for something like `dev.lan` isn't a way around this.
No certificate authority issues for made-up domain endings, so every device
would have to install yours by hand. At a LAN party that means asking a room
full of people to trust you with all of their traffic.

## When voice doesn't work

Chat working and voice not working is almost always UDP.

```bash
# Is the SFU alive?
curl http://203.0.113.10:5005/health

# Is UDP actually getting through? Run on another machine.
nc -vzu 203.0.113.10 3478
```

Then check that `ICE_ADVERTISE_IP` says the address people actually reach you
on, rather than the machine's own idea of itself. Getting that wrong looks
exactly like a firewall problem.

More in [troubleshooting](https://docs.gryt.chat/docs/use/troubleshooting).

## Networks Gryt can't get through

Some can't be fixed from your end, and it's worth knowing which before you
spend an evening on your router.

Gryt connects people directly. There's no relay in the middle, on purpose: a
relay would put an extra hop between two people who could have talked directly
and charge latency and bandwidth for it on every packet of the call. That's the
trade this project makes, and it's why the port you forward matters so much.

The cost of that trade is that some networks can't carry a Gryt call at all:

**Mobile data, usually.** Carriers put subscribers behind carrier-grade NAT,
where the phone has no address anybody outside can reach. Neither end can be
reached directly and there's nothing to forward. This is the common one, and it
isn't something a server setting fixes.

**Networks that block outbound UDP.** Some corporate and school networks allow
only TCP out. Voice is UDP and there's no TCP fallback, so it doesn't connect.
Chat and everything else still work, because those are a WebSocket.

The client says so rather than sitting on a spinner: when it has tried every
path and found none, it says the network may not allow voice through. If you see
that on mobile data, that's what happened, and switching to Wi-Fi is the fix.

Everything else — a home connection, an office that permits UDP, anyone on the
same LAN — works, provided the port is forwarded.
