How to Configure `ip` and `announceAddress` Correctly in mediasoup for WebRTC and ICE

In mediasoup, ip controls local interface binding on the server, while announceAddress publishes the reachable address to clients through ICE candidates. Mixing them up can break WebRTC connectivity, especially in NAT, containerized, and 0.0.0.0 scenarios. Keywords: mediasoup, WebRTC, ICE.

The technical specification snapshot clarifies the deployment context

Parameter Description
Core language JavaScript / C++
Key protocols WebRTC, ICE, SDP, UDP, TCP
Typical components WebRtcServer, listenInfos
Configuration focus ip, announceAddress
Applicable scenarios Public internet deployment, NAT, Docker/K8s, multi-NIC hosts
Reference popularity Original article shows about 330 reads
Core dependencies mediasoup, browser WebRTC stack, signaling channel

ip and announceAddress serve two different layers of responsibility

ip determines which local network interface mediasoup listens on for packets. It is the binding address from the operating system network stack’s perspective. This address must actually exist on the server, or you can use 0.0.0.0 to listen on all interfaces.

announceAddress determines which external address the server writes into ICE candidates. It is the connection target seen by the client. The client does not care which internal NIC your process binds to. It only cares whether the address exposed in SDP/ICE is truly reachable.

You can remember the difference in one sentence

ip means “where I receive packets,” while announceAddress means “where you should connect.” The former is host-facing, and the latter is client-facing.

Parameter Responsibility layer Typical values Required Consequence of misconfiguration
ip Local binding layer 127.0.0.1, 192.168.1.100, 0.0.0.0 Yes The server cannot listen correctly
announceAddress Client addressing layer Public IP, domain name, load balancer address Conditionally required ICE candidates become unreachable and media fails
const listenInfos = [
  {
    protocol: 'udp',
    ip: '192.168.1.100', // The actual private address bound by the server
    announceAddress: '203.0.113.10', // Tell clients to connect to the public address
    port: 44444
  }
];

This configuration expresses the most common deployment intent: listen on a private interface and publish a public entry point externally.

You can omit announceAddress in a direct public internet deployment

If the server binds directly to a public IP and that same address is reachable by clients, then ip already matches the external address and you can leave announceAddress unset.

This is the simplest scenario because ICE candidates can directly include the public address, and the client can complete connectivity checks using that candidate.

A single public NIC is the easiest deployment model

const listenInfos = [
  {
    protocol: 'udp',
    ip: '203.0.113.10', // Bind directly to the public IP
    port: 44444
  }
];

This configuration fits environments where the server has a stable public IP and no additional address translation.

Private network deployments behind NAT or firewalls must explicitly announce an external address

When mediasoup runs on a private address, such as a cloud internal network, a datacenter NAT environment, or behind a home router, clients cannot directly access 192.168.x.x or 10.x.x.x addresses. In this case, you must use announceAddress to publish the public entry point.

If you omit this field, the server will generate ICE candidates that expose the private address. The browser will then try to connect to it directly, but public networks cannot route to private ranges, so ICE checks eventually fail.

In NAT scenarios, candidate reachability matters more than successful listening

const listenInfos = [
  {
    protocol: 'udp',
    ip: '192.168.1.100', // Bind to the private NIC
    announceAddress: '203.0.113.10', // Publish the public address behind NAT
    port: 44444
  }
];

This configuration only works when the gateway has already completed port mapping, for example 203.0.113.10:44444 -> 192.168.1.100:44444.

When ip is 0.0.0.0, announceAddress is effectively required

0.0.0.0 means listen on all local interfaces. It works well in multi-NIC, containerized, and complex network environments. However, this address is valid only for server-side binding and must never be exposed to clients as a connection target.

For that reason, once you set ip to 0.0.0.0, you should also provide a clear public IP, domain name, or load balancer entry point as announceAddress.

0.0.0.0 solves listening scope, not external reachability

const listenInfos = [
  {
    protocol: 'udp',
    ip: '0.0.0.0', // Listen on all interfaces
    announceAddress: 'sfu.example.com', // Expose a stable entry point to clients
    port: 44444
  }
];

This configuration is common in Docker, Kubernetes, dual-NIC hosts, and SFU services exposed through a domain name.

announceAddress directly affects how ICE candidates are generated

In mediasoup’s actual behavior, the candidate address does not blindly use the local bound IP. Instead, it prefers announceAddress when present. That is exactly why it directly affects the candidate lines in SDP.

If the announced address is wrong, the client will still connect to the wrong destination even if the UDP port is genuinely listening. The symptom is usually successful signaling but no audio or video.

The candidate construction logic can be abstracted as follows

function buildIceCandidate(listenInfo) {
  let candidateIp = listenInfo.ip; // Use the local bound address by default
  const candidatePort = listenInfo.port;

  if (listenInfo.announceAddress) {
    candidateIp = listenInfo.announceAddress; // If an announced address exists, publish it externally instead
  }

  return `candidate:1 1 ${listenInfo.protocol.toUpperCase()} 100 ${candidateIp} ${candidatePort} typ host`;
}

This pseudocode shows that the address the client ultimately tries to connect to depends primarily on the announced address, not on the listening semantics.

During troubleshooting, you should verify candidate addresses before checking only whether the port is open

Many deployment issues do not happen because mediasoup failed to start. They happen because SDP exposes the wrong address. During debugging, first inspect the remote candidate received by the browser and confirm whether the IP or domain name is the actual entry point reachable by the client.

If signaling works, DTLS never establishes, and media traffic never flows, you should usually check these three items first:

  1. Whether you used a private ip.
  2. Whether you forgot announceAddress.
  3. Whether the NAT port mapping matches the announced port.

The safest production configuration guidance is straightforward

  1. Single public host: set ip directly to the public IP.
  2. NAT or container environment: set ip to the actual bound address and announceAddress to the public entry point.
  3. Multi-NIC environment: prefer 0.0.0.0 with an explicit announceAddress.
  4. When using a domain name: ensure DNS resolves correctly and the entry layer forwards UDP/TCP properly.

The FAQ answers the most common configuration questions

1. Why can the browser join the room when ip is set to 192.168.x.x, but there is no audio or video?

Because signaling success does not mean media connectivity success. If the remote ICE candidate exposes a private address, public clients cannot reach it, ICE fails, and no media stream is established.

2. Can announceAddress be a domain name?

Yes. As long as the domain can be resolved by the client and ultimately points to a genuinely reachable public entry point, you can use it as the announced external address.

3. What configuration is recommended for Docker or Kubernetes?

In most cases, set ip: '0.0.0.0' to listen on container interfaces, then use announceAddress to publish the host public IP, load balancer address, or service domain name. This is usually the safer approach.

The references align with mediasoup ICE address announcement behavior

This article reconstructs the topic based on the original source material. Its core conclusions are consistent with mediasoup’s address announcement behavior during WebRTC ICE negotiation. The references include technical articles on mediasoup protocol interactions and deployment experience summaries.

AI Readability Summary: This article focuses on listenInfos configuration in mediasoup WebRtcServer, explains the distinct roles of ip for local binding and announceAddress for client addressing, and uses public internet, NAT, and 0.0.0.0 deployment scenarios to show common failure causes and reliable configuration patterns.