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

  1. 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
  2. Ensure servicePort is present and valid in ProxyConfiguration; note bind() is only attempted when servicePort is present
  3. 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)
  4. 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

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


AI-assisted analysis of apache/pulsar@820761864e (2026-09-06). Data as JSON: /api/errors/20b3a67afd3dbc5a. Report an issue: GitHub.