apache/pulsar · critical · IOException
Failed to bind Pulsar Proxy on port ${servicePort}
Error message
Failed to bind Pulsar Proxy on port ${servicePort} What it means
ProxyService.start attempts to bind a Netty ServerBootstrap to the configured servicePort. If the bind (or the sync wait) throws — port already in use, insufficient privileges, or the port is not present in config — it wraps the cause in an IOException with the concrete port so the operator knows which listener failed. The proxy cannot serve clients without this listener, so startup aborts.
Source
Thrown at pulsar-proxy/src/main/java/org/apache/pulsar/proxy/server/ProxyService.java:356
// brokerClientPurpose=true: this lookup transport reuses tlsFactoryClassName to carry the
// brokerClientTlsFactoryClassName selection, so a custom by-name factory is wrapped to resolve the
// transport's CLIENT_DEFAULT request under the fixed BROKER_CLIENT purpose (matches the direct path
// above, which requests BROKER_CLIENT).
this.lookupClientTlsFactory = ClientTlsFactorySupport.resolveClientTlsFactory(
lookupClientConf, statsExecutor, statsExecutor, openTelemetry.getOpenTelemetry(), true);
}
bootstrap.childHandler(new ServiceChannelInitializer(this, proxyConfig, false, null));
// Bind and start to accept incoming connections.
if (proxyConfig.getServicePort().isPresent()) {
try {
listenChannel = bootstrap.bind(proxyConfig.getBindAddress(),
proxyConfig.getServicePort().get()).sync().channel();
log.info()
.attr("localAddress", listenChannel.localAddress())
.log("Started Pulsar Proxy at");
} catch (Exception e) {
throw new IOException("Failed to bind Pulsar Proxy on port " + proxyConfig.getServicePort().get(), e);
}
}
if (proxyConfig.getServicePortTls().isPresent()) {
this.sslContextRefresher = Executors
.newSingleThreadScheduledExecutor(
new DefaultThreadFactory("proxy-ssl-context-refresher"));
ServerBootstrap tlsBootstrap = bootstrap.clone();
this.tlsServiceChannelInitializer = new ServiceChannelInitializer(this, proxyConfig, true,
sslContextRefresher);
tlsBootstrap.childHandler(this.tlsServiceChannelInitializer);
listenChannelTls = tlsBootstrap.bind(proxyConfig.getBindAddress(),
proxyConfig.getServicePortTls().get()).sync().channel();
log.info()
.attr("localAddress", listenChannelTls.localAddress())
.log("Started Pulsar TLS Proxy on");
}
View on GitHub (pinned to 820761864e)
Solutions
- Check what occupies the port (e.g. ss -ltnp or netstat) and stop the conflicting process, or change servicePort in the proxy config to a free port
- Ensure servicePort is present and valid in ProxyConfiguration; note bind() is only attempted when servicePort is present
- Verify the bindAddress configured for the proxy resolves to a local interface and you have permission to bind (root/CAP_NET_BIND_SERVICE for ports <1024)
- In containers, fix duplicate port mappings/hostPort conflicts so only one process binds the port
Example fix
// before (proxy.conf) servicePort=6650 # already held by another proxy // after servicePort=6651
Defensive patterns
Strategy: validation
Validate before calling
int port = proxyConfig.getServicePort().orElseThrow(
() -> new IllegalArgumentException("servicePort is required"));
try (ServerSocket ss = new ServerSocket()) {
ss.bind(new InetSocketAddress(proxyConfig.getBindAddress(), port));
} catch (IOException e) {
throw new IllegalStateException("Port " + port + " unavailable before proxy start: " + e.getMessage());
} Type guard
boolean servicePortConfigured(ProxyConfiguration cfg) {
return cfg.getServicePort().isPresent();
} Try / catch
try {
proxyService.start();
} catch (IOException e) {
if (e.getCause() instanceof BindException) {
LOG.error("Port {} in use; stop the conflicting process or change servicePort",
proxyConfig.getServicePort());
}
throw e;
} Prevention
- Check port availability with ss -ltnp before starting the proxy
- Give each proxy instance a unique servicePort per host
- Use unprivileged ports (>1024) or grant CAP_NET_BIND_SERVICE
- In Kubernetes, avoid hostPort collisions across pods on the same node
When it happens
Trigger: Calling proxyService.start() and the bootstrap.bind(bindAddress, servicePort) inside the try block throws, typically BindException: Address already in use because another Pulsar proxy or process holds the port, or servicePort is absent from config (Optional.get() on an empty port), or binding to a privileged port (<1024) without permissions.
Common situations: Running two proxy instances on the same host (test environment left running in background); another service occupying the default port 6650; container port collisions in Kubernetes with multiple replicas on one node; missing servicePort entry after config refactoring; IPv6/IPv4 bind-address mismatch.
Related errors
- Failed to bind extension `${extensionName}` on ${address}
- Failed to start HTTP server on ports ${ports}
- Failed to resolve localhost name.
- Failed to load the extension for extension name `${extension
- Failed to start zookeeper :${e.getMessage()}
AI-assisted analysis of apache/pulsar@820761864e (2026-09-06).
Data as JSON: /api/errors/20b3a67afd3dbc5a.
Report an issue: GitHub.