# How to Self-Host a croc Relay Server: Complete Setup Guide

> Learn how to self-host a croc relay server with this complete setup guide. Build your own secure file transfer infrastructure independent of the public relay.

- Repository: [Zack/croc](https://github.com/schollz/croc)
- Tags: how-to-guide
- Published: 2026-07-26

---

**Running `croc relay` starts a stand-alone TCP server that forwards byte streams between clients, letting you host your own secure file-transfer infrastructure independent of the public relay.**

The **croc** project by schollz/croc enables secure file transfers through a command-line interface. While it defaults to public relay servers, you can self-host a croc relay server to maintain complete control over your data routing and eliminate dependency on third-party infrastructure.

## What the croc Relay Does

The croc relay acts as a **TCP-to-WebSocket bridge** that forwards an opaque byte stream between a sender and receiver. When you execute the relay command, the CLI invokes the `relay` action defined in [`src/cli/cli.go`](https://github.com/schollz/croc/blob/main/src/cli/cli.go) (lines 25-33), which parses flags and initiates listeners. The core TCP server logic resides in [`src/tcp/tcp.go`](https://github.com/schollz/croc/blob/main/src/tcp/tcp.go) (lines 1-80), where the `tcp.Run` function spawns listeners for each configured port.

## Relay Configuration Options

The relay behavior is controlled through several flags parsed in [`src/cli/cli.go`](https://github.com/schollz/croc/blob/main/src/cli/cli.go):

| Flag | Default | Description |
|------|---------|-------------|
| `--host` | empty (binds all) | Hostname or IP address |
| `--ports` | `9009,9010,9011,9012,9013` | Comma-separated port list |
| `--port` | `9009` | Base port when `--ports` is omitted |
| `--transfers` | `5` | Number of data ports (control port added automatically) |
| `--debug` | `false` | Enable verbose logging |

The first port in the list serves as the **control channel**, while remaining ports handle actual file-transfer streams.

## Step-by-Step Self-Hosting Guide

### 1. Prepare Your Server and Firewall

Select a VPS or dedicated server with a public IP. Open the chosen ports in your firewall—the first port handles control signals, while the others manage data transfers. For the default configuration, allow inbound TCP on ports 9009 through 9013.

### 2. Start the Relay Process

Execute the relay binary with your desired host and port configuration:

```bash
croc relay --host 0.0.0.0 --ports 9009,9010,9011,9012,9013

```

This command triggers the `relay(c *cli.Context)` function in [`src/cli/cli.go`](https://github.com/schollz/croc/blob/main/src/cli/cli.go) (lines 34-49), which builds the port slice. It then calls `tcp.Run(debugString, host, portStr, determinePass(c))` for each port (lines 60-66), creating TCP listeners. The first listener (port 9009) manages control connections, while the others await data streams.

### 3. Configure Clients to Use Your Relay

Direct croc clients to your self-hosted relay using the `--relay` flag or the `CROC_RELAY` environment variable:

```bash

# Sender command

croc send --relay my.relay.example.com:9009 myfile.txt

# Receiver command

croc --relay my.relay.example.com:9009

```

The client automatically negotiates one of the available data ports advertised through the control channel.

### 4. Implement Password Protection

Add authentication to prevent unauthorized use via the `--pass` flag or `CROC_PASS` environment variable. The `determinePass` function in [`src/cli/cli.go`](https://github.com/schollz/croc/blob/main/src/cli/cli.go) (lines 79-87) processes these values and passes them to `tcp.Run`, requiring clients to present the same password to connect.

```bash
croc relay --host 0.0.0.0 --ports 9009,9010,9011,9012,9013 --pass your-secret-password

```

### 5. Deploy as a Persistent Service

**Systemd Service:**
Create a systemd unit file to run the relay continuously:

```ini
[Unit]
Description=croc Relay
After=network.target

[Service]
ExecStart=/usr/local/bin/croc relay --host 0.0.0.0 --ports 9009,9010,9011,9012,9013
Restart=on-failure
User=nobody
Group=nobody

[Install]
WantedBy=multi-user.target

```

**Docker Container:**
Build a lightweight container using this Dockerfile:

```dockerfile
FROM golang:1.22-alpine AS builder
WORKDIR /src
RUN apk add --no-cache git
RUN git clone https://github.com/schollz/croc.git .
RUN go build -mod=readonly -o /croc ./main.go

FROM alpine:latest
COPY --from=builder /croc /usr/local/bin/croc
EXPOSE 9009 9010 9011 9012 9013
ENTRYPOINT ["croc","relay","--host","0.0.0.0","--ports","9009,9010,9011,9012,9013"]

```

## Key Implementation Files

Understanding the source structure helps with customization:

- **[`src/cli/cli.go`](https://github.com/schollz/croc/blob/main/src/cli/cli.go)**: Defines the `relay` command action, parses flags including `--host`, `--ports`, and `--pass`, and orchestrates `tcp.Run` calls (lines 25-33, 60-66).
- **[`src/tcp/tcp.go`](https://github.com/schollz/croc/blob/main/src/tcp/tcp.go)**: Implements the TCP server logic that accepts connections and forwards the opaque byte stream (lines 1-80).
- **[`src/webrelay/webrelay.go`](https://github.com/schollz/croc/blob/main/src/webrelay/webrelay.go)**: Optional web interface and WebSocket bridge for browser-based transfers (lines 59-66), not required for basic relay functionality.
- **[`src/models/constants.go`](https://github.com/schollz/croc/blob/main/src/models/constants.go)**: Contains `DEFAULT_RELAY` and `DEFAULT_RELAY6` constants (lines 18-22) that clients use when no custom relay is specified.

## Summary

- The croc relay in [`src/cli/cli.go`](https://github.com/schollz/croc/blob/main/src/cli/cli.go) creates a stand-alone TCP server using `tcp.Run` from [`src/tcp/tcp.go`](https://github.com/schollz/croc/blob/main/src/tcp/tcp.go) to bridge file transfers.
- The first port specified serves as the control channel; subsequent ports handle data streams.
- Deploy the relay with `croc relay --host 0.0.0.0 --ports 9009,9010,9011,9012,9013` and point clients to it via `--relay` or `CROC_RELAY`.
- Secure your infrastructure using the `--pass` flag, processed by `determinePass` in [`src/cli/cli.go`](https://github.com/schollz/croc/blob/main/src/cli/cli.go).
- Run the binary as a systemd service or Docker container for production deployments.

## Frequently Asked Questions

### Do I need the web relay component to run a basic croc relay?

No. The [`src/webrelay/webrelay.go`](https://github.com/schollz/croc/blob/main/src/webrelay/webrelay.go) file provides an optional web UI and WebSocket bridge for browser-based transfers, but the core relay functionality in [`src/tcp/tcp.go`](https://github.com/schollz/croc/blob/main/src/tcp/tcp.go) operates independently as a pure TCP server.

### How many ports should I open for the croc relay?

You need one control port plus one port per concurrent transfer. The `--transfers` flag defaults to 5, meaning you should open 6 ports total (control plus 5 data ports). The first port in your `--ports` list always serves as the control channel.

### Can I use environment variables instead of command-line flags?

Yes. You can set `CROC_RELAY` on client machines to specify the relay address, and `CROC_PASS` to set the relay password. The `determinePass` function in [`src/cli/cli.go`](https://github.com/schollz/croc/blob/main/src/cli/cli.go) (lines 79-87) checks for the `CROC_PASS` environment variable when the `--pass` flag is not provided.

### What is the difference between the control port and data ports?

The first port in your `--ports` list functions as the control channel where clients negotiate connections and receive the list of available data ports. The remaining ports handle the actual file-transfer byte streams. This separation allows the relay to manage multiple concurrent transfers without blocking the control signal path.