celld --listen vs --internal-listen: What's the Difference and When to Use Each
--listen controls the public worker-facing HTTP endpoint, while --internal-listen controls the peer-to-peer replication and operator command endpoint—two separate TCP sockets with distinct defaults and security requirements.
The celld daemon from denoland/celld exposes two independent listener flags that serve different traffic types. Understanding when each is required prevents configuration errors when deploying clusters or exposing nodes externally.
How the Two Listeners Work
Both flags use the same underlying Listen enum defined in crates/celld/startup.rs, but they populate separate fields in the Settings struct at crates/celld/main/cli.rs:15-17:
pub listen: Listen,
pub internal_listen: Listen,
The CLI parsing distinguishes them at crates/celld/main/cli.rs:48-60:
--listen→settings.listen--internal-listen→settings.internal_listen
Default Behaviors
| Flag | Default Value | Behavior |
|---|---|---|
--listen |
127.0.0.1:8080 (or AutoLoopback in managed mode) |
Explicit loopback address unless configured otherwise |
--internal-listen |
LoopbackEphemeral (127.0.0.1:0) |
Random OS-assigned port on loopback |
The Listen enum variants—AutoLoopback, LoopbackEphemeral, and Explicit—determine how each socket binds at startup.
What Each Listener Handles
--listen: Public Worker HTTP Interface
This endpoint receives worker runtime requests—the primary interface for executing JavaScript/TypeScript workloads. By default, it binds to 127.0.0.1:8080 for local development safety.
When you specify a non-loopback address (e.g., 0.0.0.0:8000 or a public IP), celld requires an explicit --internal-listen because:
- The public socket cannot be reused for internal peer traffic
- Internal replication traffic requires different authentication and access controls
--internal-listen: Peer and Operator Interface
This endpoint handles three specific traffic types:
- Peer-to-peer replication between
celldnodes - Unauthenticated operator commands (health checks, status queries)
- Internal cluster coordination
The default LoopbackEphemeral (127.0.0.1:0) is safe for single-node deployments where no external peers connect. For multi-node clusters, you must configure this explicitly.
Configuration Examples
Local Development (Defaults)
celld --bucket s3://my-fleet
- Public listener:
127.0.0.1:8080 - Internal listener: random loopback port (ephemeral)
External Worker Access with Secure Internal
celld --bucket s3://my-fleet \
--listen 0.0.0.0:8000 \
--internal-listen 127.0.0.1:9000
- Workers connect via any interface on port 8000
- Internal traffic stays on loopback, explicitly port 9000
Multi-Node Cluster with Advertisement
celld --bucket s3://my-fleet \
--listen 192.168.10.5:8080 \
--internal-listen 192.168.10.5:9090 \
--advertise celld-node-1.internal:9090
- Both listeners use explicit addresses
--advertiseregisters the internal listener for peer discovery
Critical Configuration Rules
Based on the source code in crates/celld/startup.rs and crates/celld/main/cli.rs:
- Non-loopback
--listenforces--internal-listen— The daemon will not start with an exposed public address unless internal is explicitly set --advertiserequires explicit internal address — You cannot advertise an auto-assigned ephemeral port- Authentication differs — The public listener requires worker authentication; the internal listener accepts unauthenticated operator commands (secured by network binding instead)
Key Source Files
| File | Purpose |
|---|---|
crates/celld/main/cli.rs |
CLI flag parsing, Settings struct definition, help text |
crates/celld/startup.rs |
Listen enum, default logic, socket binding implementation |
crates/celld/main/cli.rs:42-46 |
User-facing help documentation describing flag semantics |
Summary
--listensets the worker-facing HTTP endpoint; defaults to127.0.0.1:8080--internal-listensets the peer/operator endpoint; defaults to random loopback port- Non-loopback public addresses require explicit internal configuration
- Both use the
Listenenum but populate independentSettingsfields - Multi-node deployments need explicit
--internal-listenwith--advertise
Frequently Asked Questions
What happens if I only set --listen to a public IP?
The daemon will fail to start with an error requiring --internal-listen. The source enforces this because peer traffic cannot reuse an externally reachable socket—internal operations must bind separately for security isolation.
Can I use the same port for both listeners?
No. Each flag creates an independent TCP socket. Even if you specify identical addresses, the second bind will fail with "address already in use." Use distinct ports (e.g., 8080 and 9090).
Why does --internal-listen default to a random port?
Single-node deployments don't need deterministic internal addressing—peers don't connect. The ephemeral default avoids port conflicts and simplifies local development. Production clusters require explicit ports for service discovery.
Is --internal-listen authenticated?
According to the help text and implementation, the internal listener accepts unauthenticated operator commands. Security relies on network binding (loopback or private networks) rather than application-layer authentication. This design prioritizes operational simplicity for health checks and debugging.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →