{"record":{"id":"9f67b7dc220e040e","repo":"BeyondDimension/SteamTools","slug":"tcp-port-httpproxyport-is-already-occupied-by-ot","errorCode":null,"errorMessage":"TCP port {httpProxyPort} is already occupied by other processes.","messagePattern":"TCP port (.+?) is already occupied by other processes\\.","errorType":"exception","errorClass":"ApplicationException","httpStatus":null,"severity":"error","filePath":"src/BD.WTTS.Client.Plugins.Accelerator.ReverseProxy/Extensions/KestrelServerOptionsExtensions.cs","lineNumber":32,"sourceCode":"    {\n        options.Limits.MaxRequestBodySize = null;\n        options.Limits.MinResponseDataRate = null;\n        options.Limits.MinRequestBodyDataRate = null;\n    }\n\n    /// <summary>\n    /// 监听 Http 代理\n    /// </summary>\n    /// <param name=\"options\"></param>\n    [MethodImpl(MethodImplOptions.AggressiveInlining)]\n    public static void ListenHttpProxy(this KestrelServerOptions options)\n    {\n        var reverseProxyConfig = options.ApplicationServices.GetRequiredService<IReverseProxyConfig>();\n        var httpProxyPort = reverseProxyConfig.HttpProxyPort;\n\n        if (!IReverseProxyConfig.IsAvailableTcp(httpProxyPort))\n        {\n            throw new ApplicationException(\n                $\"TCP port {httpProxyPort} is already occupied by other processes.\");\n        }\n\n        options.Listen(IReverseProxyService.Constants.Instance.ProxyIp, httpProxyPort, listen =>\n        {\n            listen.Protocols = HttpProtocols.Http1AndHttp2AndHttp3;\n            var proxyMiddleware = options.ApplicationServices.GetRequiredService<HttpProxyMiddleware>();\n            var tunnelMiddleware = options.ApplicationServices.GetRequiredService<TunnelMiddleware>();\n\n            listen.UseFlowAnalyze();\n            listen.Use(next => context => proxyMiddleware.InvokeAsync(next, context));\n            listen.UseTls();\n            listen.Use(next => context => tunnelMiddleware.InvokeAsync(next, context));\n        });\n\n        options.GetLogger().LogInformation(\n            \"Listened http://{ProxyIp}:{httpProxyPort}, HTTP proxy service startup completed.\",\n            IReverseProxyService.Constants.Instance.ProxyIp, httpProxyPort);","sourceCodeStart":14,"sourceCodeEnd":50,"githubUrl":"https://github.com/BeyondDimension/SteamTools/blob/c16ffa08e03b192d23ada290c4969e77f9201f3d/src/BD.WTTS.Client.Plugins.Accelerator.ReverseProxy/Extensions/KestrelServerOptionsExtensions.cs#L14-L50","documentation":"Thrown during Kestrel server startup when the configured HTTP proxy port is already bound by another process. ListenHttpProxy resolves the configured HttpProxyPort and probes it with IsAvailableTcp before calling options.Listen; if the probe fails the listener is never created and the app fails fast rather than hitting an OS bind error later.","triggerScenarios":"Calling ListenHttpProxy (during reverse-proxy startup) when another process — a previous app instance that did not release the socket, a different proxy, or a system service — is already listening on the configured HttpProxyPort.","commonSituations":"App was killed and the socket is still in TIME_WAIT or held by a zombie process; another accelerator/proxy (Clash, v2ray, Fiddler) occupies the same port; the port was changed in config to one already in use; developer is running two instances.","solutions":["Free the port: find and stop the process holding it (e.g. netstat/Get-NetTCPConnection + taskkill) then restart.","Change the configured HttpProxyPort to a free port and restart the service.","Ensure no second instance of the application is running before startup.","If caused by TIME_WAIT, wait briefly or enable SO_REUSEADDR on the conflicting listener."],"exampleFix":"// before: fixed port collision\nvar httpProxyPort = reverseProxyConfig.HttpProxyPort; // e.g. 8888, already in use\n\n// after: fall back to a free port when the configured one is busy\nvar httpProxyPort = reverseProxyConfig.HttpProxyPort;\nif (!IReverseProxyConfig.IsAvailableTcp(httpProxyPort))\n    httpProxyPort = IReverseProxyConfig.GetAvailableTcpPort(httpProxyPort + 1);","handlingStrategy":"validation","validationCode":"// Probe the port BEFORE configuring Kestrel and pick an alternative if busy.\nint ResolveProxyPort(IReverseProxyConfig cfg)\n{\n    var port = cfg.HttpProxyPort;\n    if (!IReverseProxyConfig.IsAvailableTcp(port))\n    {\n        Log.Warning($\"Configured proxy port {port} busy; searching for a free one.\");\n        port = IReverseProxyConfig.GetAvailableTcpPort(cfg.HttpProxyPort + 1);\n    }\n    return port;\n}","typeGuard":null,"tryCatchPattern":"try { options.ListenHttpProxy(); }\ncatch (ApplicationException ex) when (ex.Message.Contains(\"already occupied\"))\n{\n    // Surface to the user with the port number and offer to change it / kill the holder.\n    Log.Error(TAG, ex, \"Proxy port conflict during startup.\");\n    throw;\n}","preventionTips":["Pin the proxy port to a rarely-used high port and document it; ensure no other tool uses it.","On shutdown, explicitly stop the Kestrel listener so the socket is released (avoid TIME_WAIT).","Detect a stale previous instance at startup (pid file / named mutex) and refuse/replace it before binding.","Add a startup health check that logs which process holds the configured port."],"tags":["network","kestrel","port","configuration"],"backgroundTag":null,"analyzedSha":"c16ffa08e03b192d23ada290c4969e77f9201f3d","analyzedAt":"2026-08-13T11:52:20.410Z","schemaVersion":2},"datasetVersion":"2026-08-13T14:17:21.547Z"}