Skip to content

fix(web): check free ports on all addresses, not only 127.0.0.1 - #523

Closed
frankl7800 wants to merge 5 commits into
sitepark:mainfrom
frankl7800:fix/port-fix
Closed

frankl7800 wants to merge 5 commits into
sitepark:mainfrom
frankl7800:fix/port-fix

Conversation

@frankl7800

Copy link
Copy Markdown

Problem

Starting a second styleguide while another one is running fails:

✘ listen EADDRINUSE: address already in use :::3001

The port had just been reported as free.

Cause

findPorts() checked candidate ports at a single address while the server occupies all of them:

getPort({ port: ..., host: '127.0.0.1' })   // check: loopback only
this._server.listen(ports.server)           // bind: no host = wildcard

BrowserSync gets the sync port without a host as well, so it binds just as broadly. The check was the narrowest possible, the bind the widest.

On Windows a process may bind 127.0.0.1:3001 while another one holds 0.0.0.0:3001 — the two only collide once the wildcard bind happens. On Linux and macOS the loopback bind already conflicts, which is why this surfaced on Windows.

Change

Drop the host option. get-port has two modes:

if (options.host || options.port === 0) {
    return checkAvailablePort(options);   // with host: a single test
}
for (const host of hosts) { ... }         // without: every address

Without host it tests undefined (Node's default bind), 0.0.0.0 and every interface address — exactly the set the server can occupy. Check and bind agree again. const ip existed only for those four call sites and goes with them.

The check gets stricter, not looser: a port is only handed out when it is free everywhere, which is what the wildcard bind needs. No port that used to work becomes unavailable. findPorts has a single caller, server.start(), so this only affects dev servers — the build path never touches it.

Testing

Linked the fix into DHPol, Coburg and Wuppertal and started all three styleguides in turn with yarn start (pnpm start for Coburg). Each one picked its own port triplet and stayed up; no EADDRINUSE.

DHPol

    ┌──────────────────────────────────────────┐
    │ Fractality web UI server is running!     │
    │──────────────────────────────────────────│
    │ Local URL:      http://localhost:3000    │
    │ Network URL:    http://192.168.1.70:3000 │
    │ BrowserSync UI: http://localhost:3002    │
    │──────────────────────────────────────────│
    │ Use ^C to stop the server.               │
    └──────────────────────────────────────────┘

Coburg

[fractal] ┌──────────────────────────────────────────┐
[fractal] │ Fractality web UI server is running!     │
[fractal] │──────────────────────────────────────────│
[fractal] │ Local URL:      http://localhost:3003    │
[fractal] │ Network URL:    http://192.168.1.70:3003 │
[fractal] │ BrowserSync UI: http://localhost:3005    │
[fractal] │──────────────────────────────────────────│
[fractal] │ Use ^C to stop the server.               │
[fractal] └──────────────────────────────────────────┘

Wuppertal

    ┌──────────────────────────────────────────┐
    │ Fractality web UI server is running!     │
    │──────────────────────────────────────────│
    │ Local URL:      http://localhost:3006    │
    │ Network URL:    http://192.168.1.70:3006 │
    │ BrowserSync UI: http://localhost:3008    │
    │──────────────────────────────────────────│
    │ Use ^C to stop the server.               │
    └──────────────────────────────────────────┘

Note for the release

Downstream styleguides that serve through fractal-serve cannot start in parallel until this ships, so this needs a @fractality/web release and a bump in @fractality/fractality.

flegge and others added 5 commits September 23, 2026 16:53
- add .project to .gitignore
- drop host option from getPort calls in findPorts
- express and BrowserSync bind the wildcard address, so a loopback-
  only check could report ports as free that are taken on 0.0.0.0
- on Windows the subsequent listen() then failed with EADDRINUSE
- without a host, get-port checks every local interface, matching
  the set of addresses the server actually binds
- add .classpath, .project and .settings to gitignore
- they are IDE-generated and differ per workstation
@Schleuse

Copy link
Copy Markdown
Collaborator

Thanks for tracking this down. The diagnosis is spot on: the port check on 127.0.0.1 didn't match the wildcard bind.

I'd rather not drop host completely, though. That hardcodes "always bind every interface" and leaves no way to run a loopback-only dev server. #527 keeps a single host instead: a new web.server.host option is used for the port check, the express listen(), BrowserSync's listen and the URLs. When it's unset, the behaviour matches this PR (every local address is checked and the wildcard address is bound). I've tested it on Windows with styleguides running in parallel and got no EADDRINUSE.

The .gitignore entries (with .project listed twice) and the indentation changes in examples/*/docs/01-index.md look unrelated to the fix. If they're wanted, they should go in a separate PR.

@Schleuse

Copy link
Copy Markdown
Collaborator

Closing as #527 fixed this without removal of host option.

@Schleuse Schleuse closed this Sep 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants