Running behind NAT
SIP puts IP addresses inside its payload, so any address translation between two endpoints has to be accounted for somewhere. Lyno handles the common case automatically and needs two settings for the uncommon one.
Phones behind NAT — nothing to do
Endpoints are dialled at the source address observed during REGISTER, not
the address advertised in their Contact header. A phone on a home router
advertising 192.168.1.42 is still reachable, because Lyno remembers where the
registration actually came from.
This is why a softphone on a mobile network works without any STUN or TURN configuration on the PBX side.
The PBX behind NAT — two settings
When Lyno itself sits behind NAT — a cloud VM with a mapped public address, or Docker bridge networking — it must advertise the address the outside world sees rather than the one it is bound to:
sip:
bind_host: 0.0.0.0 # what to listen on
bind_port: 5060
external_host: 203.0.113.10 # what to advertise in SIP
media:
external_ip: 203.0.113.10 # what to advertise in SDP
rtp_port_min: 10000
rtp_port_max: 20000
| Setting | Affects | Symptom when missing |
|---|---|---|
sip.external_host | Contact and Via headers in signalling | Calls set up and then time out; the far end sends to a private address |
media.external_ip | The connection address in SDP | Signalling works, audio is one-way or absent |
Both take a host — an IP address, or a hostname that resolves to the public address.
Ports to open
| Port | Protocol | Purpose |
|---|---|---|
sip.bind_port (default 5060) | UDP, and TCP if enabled | SIP signalling |
media.rtp_port_min–rtp_port_max | UDP | RTP audio |
The RTP range must be forwarded one-to-one — no port rewriting. Each call leg uses a couple of ports, so size the range for peak concurrency: a range of 200 ports comfortably covers a busy small deployment, and the default 10000–20000 covers anything.
Setting only one of rtp_port_min / rtp_port_max is a validation error, as is
a maximum that is not greater than the minimum.
Carrier-side considerations
Trunk source addresses are what authorise inbound calls, so the firewall in front of the PBX should allow SIP from your carrier's signalling addresses and RTP from their media addresses. Those are often different ranges — check the carrier's documentation rather than assuming.
The INBOUND FROM column on the dashboard's Trunks tab lists the addresses
Lyno will accept a trunk call from. If a carrier sends from an address that is
not there, the call is treated as an unauthenticated endpoint call and refused.
Add the address to the trunk's inbound.match_ips.
Diagnosing
| Symptom | Likely cause |
|---|---|
| Phone registers, calls to it never ring | Firewall dropping SIP from the PBX to the phone, or a NAT mapping that expired — lower the phone's registration interval |
| Calls connect, no audio either way | media.external_ip unset, or the RTP range not forwarded |
| Calls connect, audio one way only | RTP forwarded in one direction only, or a codec mismatch — check the logs for a bridge error |
| Inbound calls rejected, outbound fine | The carrier's source address is not in INBOUND FROM |
| Everything works locally, nothing works from outside | sip.external_host unset |
Run with -debug and watch the Logs tab while placing a call; the addresses
being advertised appear there.