# celld --listen vs --internal-listen: What's the Difference and When to Use Each

> Understand the crucial differences between celld --listen and --internal-listen flags. Learn when to use each for secure and efficient Celld operation.

- Repository: [Deno/celld](https://github.com/denoland/celld)
- Tags: deep-dive
- Published: 2026-08-15

---

**`--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](https://github.com/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`](https://github.com/denoland/celld/blob/main/crates/celld/startup.rs), but they populate separate fields in the `Settings` struct at `crates/celld/main/cli.rs:15-17`:

```rust
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**:

1. **Peer-to-peer replication** between `celld` nodes
2. **Unauthenticated operator commands** (health checks, status queries)
3. **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)

```bash
celld --bucket s3://my-fleet

```

- Public listener: `127.0.0.1:8080`
- Internal listener: random loopback port (ephemeral)

### External Worker Access with Secure Internal

```bash
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

```bash
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
- `--advertise` registers the internal listener for peer discovery

## Critical Configuration Rules

Based on the source code in [`crates/celld/startup.rs`](https://github.com/denoland/celld/blob/main/crates/celld/startup.rs) and [`crates/celld/main/cli.rs`](https://github.com/denoland/celld/blob/main/crates/celld/main/cli.rs):

- **Non-loopback `--listen` forces `--internal-listen`** — The daemon will not start with an exposed public address unless internal is explicitly set
- **`--advertise` requires 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`](https://github.com/denoland/celld/blob/main/crates/celld/main/cli.rs) | CLI flag parsing, `Settings` struct definition, help text |
| [`crates/celld/startup.rs`](https://github.com/denoland/celld/blob/main/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

- **`--listen`** sets the worker-facing HTTP endpoint; defaults to `127.0.0.1:8080`
- **`--internal-listen`** sets the peer/operator endpoint; defaults to random loopback port
- Non-loopback public addresses require explicit internal configuration
- Both use the `Listen` enum but populate independent `Settings` fields
- Multi-node deployments need explicit `--internal-listen` with `--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.