Skip to main content

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:

configs/lyno.yaml
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
SettingAffectsSymptom when missing
sip.external_hostContact and Via headers in signallingCalls set up and then time out; the far end sends to a private address
media.external_ipThe connection address in SDPSignalling 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

PortProtocolPurpose
sip.bind_port (default 5060)UDP, and TCP if enabledSIP signalling
media.rtp_port_minrtp_port_maxUDPRTP 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

SymptomLikely cause
Phone registers, calls to it never ringFirewall 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 waymedia.external_ip unset, or the RTP range not forwarded
Calls connect, audio one way onlyRTP forwarded in one direction only, or a codec mismatch — check the logs for a bridge error
Inbound calls rejected, outbound fineThe carrier's source address is not in INBOUND FROM
Everything works locally, nothing works from outsidesip.external_host unset

Run with -debug and watch the Logs tab while placing a call; the addresses being advertised appear there.