[](https://aossie.org/)
---
ThruBox Server
A simple, self-hostable relay server that acts as a **dumb encrypted mailbox**. Any application can use it to pass encrypted data between users. The server never sees plaintext ā all encryption/decryption happens client-side.
---
## š Features
- **Encrypted Message Relay**: Store and retrieve opaque encrypted payloads via simple REST endpoints
- **Self-Hostable**: Single binary, embedded SQLite, zero external dependencies
- **Configurable TTL**: Auto-purge messages after N days, or set to 0 for permanent storage
- **Rate Limiting**: Built-in IP-based rate limiting to prevent abuse
- **API Key Auth**: Optional API key authentication for private relays
- **Docker Ready**: Dockerfile and Docker Compose included
---
## š» Tech Stack
### Backend
- Go 1.22+
- SQLite (embedded, WAL mode) via `mattn/go-sqlite3`
- `net/http` (standard library)
### Infrastructure
- Docker + Docker Compose
- GitHub Actions CI/CD
---
## šļø Architecture Diagram
```mermaid
graph TD
Client["Client / SDK"] -->|HTTP| MW0["CORS"]
MW0 --> MW1["API Key Middleware"]
MW1 --> MW2["Rate Limiter"]
MW2 --> Mux["net/http ServeMux"]
Mux -->|"POST /api/messages"| H1["Create Message"]
Mux -->|"GET /api/messages/:address"| H2["Get by Address"]
Mux -->|"GET /health"| H4["Health Check"]
H1 & H2 --> Store["Store Interface"]
Store --> SQLite["SQLiteStore (WAL mode)"]
BG["Hourly Purge Goroutine"] --> Store
```
---
## š API Endpoints
| Method | Endpoint | Description |
|--------|----------|-------------|
| `POST` | `/api/messages` | Store a new encrypted message |
| `GET` | `/api/messages/:address` | Fetch messages for a wallet address (optional `?after=&limit=`) |
| `GET` | `/health` | Server health check |
### Send a Message
```bash
curl -X POST http://localhost:3000/api/messages \
-H "Content-Type: application/json" \
-d '{"to": "0xRecipient", "from": "0xSender", "payload": "encrypted_base64_data"}'
```
Returns `201 Created` with the stored message:
```json
{
"id": "3f0cā¦e91a",
"seq": 1,
"to": "0xRecipient",
"from": "0xSender",
"payload": "encrypted_base64_data",
"createdAt": "2026-10-26T10:00:00Z",
"expiresAt": "2026-11-02T10:00:00Z"
}
```
- **`id`** is the hex SHA-256 of `to`, `from` and `payload`, so the same payload sent to two recipients gets two IDs. Sending an identical message again is safe: nothing new is stored, and the server answers `200 OK` with the message it already has.
- **`seq`** is the message's position in the recipient's mailbox: 1, 2, 3, ⦠per address. Numbers only go up and are never reused, even after a message expires.
### Fetch Messages
```bash
# Everything in the mailbox
curl http://localhost:3000/api/messages/0xRecipient
# Only messages newer than the last one you handled
curl "http://localhost:3000/api/messages/0xRecipient?after=42"
# At most 100 at a time
curl "http://localhost:3000/api/messages/0xRecipient?after=42&limit=100"
```
Messages come back oldest first, ordered by `seq`. To avoid downloading the same messages on every poll, a client remembers the highest `seq` it has handled and passes it as `after`. The cursor belongs to the client, so each device reading the same mailbox keeps its own.
| Query parameter | Meaning |
|-----------------|---------|
| `after` | Only return messages with a `seq` greater than this. Default `0` (everything). |
| `limit` | Return at most this many messages, from `1` to `1000`. Omitted, everything matching is returned. If you get a full page, ask again with `after` set to the last `seq` you received. |
---
## š Repository Links
1. [ThruBox Server](https://github.com/AOSSIE-Org/ThruBox-Server) ā This repository (relay server)
2. [ThruBox Client](https://github.com/AOSSIE-Org/ThruBox-Client) ā TypeScript SDK
---
## š Getting Started
### Prerequisites
- Go 1.22+ with CGo enabled (required for SQLite)
- GCC (for compiling go-sqlite3)
- Docker (optional, for containerized deployment)
### Installation
#### 1. Clone the Repository
```bash
git clone https://github.com/AOSSIE-Org/ThruBox-Server.git
cd ThruBox-Server
```
#### 2. Install Dependencies
```bash
go mod download
```
#### 3. Build and Run
```bash
go build -o relay-server ./cmd/relay
./relay-server
```
The server starts on `http://localhost:3000` with a SQLite database that auto-creates at `./data/relay.db`.
#### 4. Docker (Alternative)
```bash
docker compose up -d
```
ThruBox reads `config.yaml` from the working directory. Set `RELAY_CONFIG_PATH`
to load it from anywhere else:
```bash
RELAY_CONFIG_PATH=/etc/relay/config.yaml ./relay-server
```
The image sets this for you and ships the file at `/etc/relay/config.yaml`, so
you can mount your own over it:
```bash
docker run -v ./my-config.yaml:/etc/relay/config.yaml:ro ghcr.io/aossie-org/thrubox-server
```
If no config file is found at the resolved path the server logs a warning at
startup and runs on built-in defaults rather than failing. Precedence is
**built-in defaults ā the selected YAML file ā environment variables**, so an
environment variable always wins over the file.
> **Exposing to the internet?** See [Exposing to the Internet (Self-Hosting)](#exposing-to-the-internet) for complete step-by-step guides using Nginx reverse proxy or Cloudflare Tunnel.
#### 5. Run Tests
```bash
go test ./...
```
> Tests live beside the code they cover. See `CONTRIBUTING.md` before submitting a PR that adds functionality without tests.
### Configuration
Edit `config.yaml` or use environment variables:
| Setting | YAML Key | Env Variable | Default |
|---------|----------|-------------|---------|
| Config file path | ā | `RELAY_CONFIG_PATH` | `config.yaml` (`/etc/relay/config.yaml` in the Docker image) |
| Server port | `server.port` | `RELAY_SERVER_PORT` | `3000` |
| Server port (fallback) | ā | `PORT` | _unset_ ā used when `RELAY_SERVER_PORT` is unset **or empty** |
| Server host | `server.host` | `RELAY_SERVER_HOST` | `0.0.0.0` |
| Storage path | `storage.path` | `RELAY_STORAGE_PATH` | `./data/relay.db` |
| Message TTL | `messages.ttl_days` | `RELAY_MESSAGES_TTL_DAYS` | `7` (0 = keep forever; the database then only grows) |
| Max payload | `messages.max_payload_size` | `RELAY_MESSAGES_MAX_PAYLOAD_SIZE` | `524288` (500KB) |
| Rate limit | `security.rate_limit` | `RELAY_SECURITY_RATE_LIMIT` | `30` req/min/IP |
| Trusted proxies | `security.trusted_proxies` | `RELAY_SECURITY_TRUSTED_PROXIES` | `0` (ignore `X-Forwarded-For`) ā see [Behind a reverse proxy](#behind-a-reverse-proxy) |
| API key | `security.api_key` | `RELAY_SECURITY_API_KEY` | `` (disabled) |
| CORS origins | `security.allowed_origins` | `RELAY_SECURITY_ALLOWED_ORIGINS` | `` (CORS disabled) |
### Behind a Reverse Proxy
The rate limiter counts requests per client IP. Where that IP comes from depends on `security.trusted_proxies`:
- **`0` (default)** ā the address of whoever opened the connection. `X-Forwarded-For` and `X-Real-IP` are ignored, so a client cannot fake its IP. Use this when clients connect to ThruBox directly.
- **`N`** ā the relay sits behind `N` proxies, and the client IP is the `N`th entry from the **right** of `X-Forwarded-For`. Each proxy appends the address it received the request from, so the rightmost `N` entries were written by your proxies; anything further left came from the client and is never read.
**Set this if you run behind a proxy.** With the default `0`, every request appears to come from the proxy's address, so all your users share one rate-limit bucket and get `429 Too Many Requests` together. This covers Nginx, Cloudflare Tunnel, Docker port publishing behind a proxy, and hosting platforms such as Render, Railway or Fly.io.
A single proxy in front of ThruBox (Nginx, a platform's load balancer) is `trusted_proxies: 1`. If you are unsure how many hops your platform adds, set `rate_limit` low for a moment, make a few requests and read the `ip` in the `rate limit exceeded` log line: it should be your own public IP, not a private or proxy address.
Setting the value higher than the real number of proxies lets clients choose their own IP again, so do not overestimate it.
**Block direct access.** A non-zero `trusted_proxies` is only safe if every request reaches ThruBox through your proxy. A client that can connect to ThruBox directly can send its own `X-Forwarded-For` and pick any IP, which bypasses the rate limit again. When you set `trusted_proxies` above `0`, make sure only the proxy can reach ThruBox: publish the container port on `127.0.0.1` only (`-p 127.0.0.1:3000:3000`, as in the guides below), or firewall the port. Note that the bundled `docker-compose.yml` publishes `3000:3000` on all interfaces; change it to `127.0.0.1:3000:3000` when running behind a proxy. On hosting platforms that route every request through their load balancer (such as Render), there is no direct path to worry about.
### CORS
By default the relay serves **no CORS headers**, so a browser calling it from
another origin is blocked at the preflight. There are two ways to run a
browser client:
**1. Reverse proxy (no relay configuration).** Put the relay behind a
same-origin path in your own app ā a Vercel rewrite, an nginx `location`, a
Vite `server.proxy` entry. The request is never cross-origin, so CORS never
applies. This is the setup the ThruBox client docs assume (see [Option 1: Nginx Reverse Proxy](#nginx-reverse-proxy)).
**2. Direct calls with an origin allowlist.** List the origins you want to
serve and browsers can call the relay directly, no proxy needed:
```yaml
security:
allowed_origins:
- "https://app.example.com"
- "http://localhost:5173"
```
or via the environment, comma-separated:
```bash
RELAY_SECURITY_ALLOWED_ORIGINS="https://app.example.com,http://localhost:5173"
```
When an origin is allowed, the relay answers preflights and returns
`Access-Control-Allow-Origin` for that origin, `Access-Control-Allow-Methods:
GET, POST, OPTIONS`, and `Access-Control-Allow-Headers: Content-Type`
ā plus `X-API-Key` when `security.api_key` is set.
Notes:
- Entries must be full origins (`https://host[:port]`), with no path. A bare
hostname or a URL with a path is rejected at startup rather than silently
never matching.
- `"*"` allows any origin. It cannot be combined with specific origins, and it
is a poor fit for a relay with no API key configured ā anything on the web
can then read and write messages from a browser.
- Credentialed CORS is not supported. The relay authenticates with the
`X-API-Key` header, not cookies, so `Access-Control-Allow-Credentials` is
never sent.
- An allowlisted origin still has to satisfy `security.api_key` and the rate
limiter. CORS controls which origins a browser will let read a response; it
is not authentication.
> **Deploying to a managed platform?** Render, Railway, Heroku and Cloud Run
> inject a `PORT` variable and expect the process to bind to it. ThruBox reads
> it automatically, so no extra configuration is needed. `RELAY_SERVER_PORT`
> still takes precedence whenever it is set to a non-empty value, so setting it
> to `""` (as an empty `docker-compose` entry does) falls through to `PORT`.
---
## š Exposing to the Internet (Self-Hosting)
When self-hosting ThruBox Server with Docker, the container runs locally and listens on port `3000` by default (as configured by `server.port` in `config.yaml` or `RELAY_SERVER_PORT`). To make your relay accessible to clients and client SDKs over the public internet, you should place it behind either a **reverse proxy** or a **secure tunnel**.
```text
Internet Requests (HTTPS)
ā
ā¼
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā Option 1: Nginx Reverse Proxy ā (Public IP, ports 80/443, Certbot SSL)
ā OR ā
ā Option 2: Cloudflare Tunnel ā (No open inbound ports, outbound tunnel)
āāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāā
ā HTTP (localhost:3000)
ā¼
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā Local ThruBox Server (Docker) ā (Container listening on port 3000)
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
```
> [!IMPORTANT]
> **Port Configuration & Security**:
> - **Default Port**: All examples below use port `3000`, the default configured by `server.port` in `config.yaml` or the `RELAY_SERVER_PORT` environment variable. If you configure a different port, you must update:
> 1. The Docker container port mapping (e.g., `-p 127.0.0.1::` or `ports: - "127.0.0.1::"`).
> 2. The reverse proxy / tunnel upstream targets (e.g., `proxy_pass http://127.0.0.1:;` in Nginx, `service: http://localhost:` in `config.yml`, or `URL: relay:` in Docker Compose).
> - **Local Binding Only**: Never expose the raw ThruBox container port directly to the public internet (`0.0.0.0:3000`). Bind the port strictly to `127.0.0.1:3000:3000` (or `127.0.0.1::`) on your host so only local services (such as Nginx or `cloudflared`) can communicate with it.
---
### Step 0: Run ThruBox Server Locally on Docker
1. In your `docker-compose.yml`, restrict the port binding to `127.0.0.1`:
```yaml
services:
relay:
build: .
ports:
# Port 3000 is the default (server.port / RELAY_SERVER_PORT).
# If using another port, update both host and container port values.
- "127.0.0.1:3000:3000"
volumes:
- relay-data:/data
environment:
- RELAY_STORAGE_PATH=/data/relay.db
restart: unless-stopped
volumes:
relay-data:
```
Or run the container using `docker run`:
```bash
# Note: 3000 is the default port. If configured differently via RELAY_SERVER_PORT, update -p 127.0.0.1::.
docker run -d \
--name thrubox-relay \
-p 127.0.0.1:3000:3000 \
-v relay-data:/data \
ghcr.io/aossie-org/thrubox-server:latest
```
2. Confirm the server is running and healthy on `localhost:3000` (or your configured port):
```bash
curl http://127.0.0.1:3000/health
```
Expected response:
```json
{"status":"ok","timestamp":"2026-10-05T13:14:15Z"}
```
Choose one of the two deployment methods below:
- **[Option 1: Nginx Reverse Proxy](#nginx-reverse-proxy)**: Best if you have a public IP address and can open/forward inbound ports `80` and `443`.
- **[Option 2: Cloudflare Tunnel](#cloudflare-tunnel)**: Best if you are behind CGNAT, on a home network, or cannot/prefer not to open inbound router ports.
---
### Option 1: Nginx Reverse Proxy
Nginx acts as a front-facing web server that accepts HTTPS connections from the internet, handles TLS encryption/decryption, and forwards requests to the local ThruBox container on `http://127.0.0.1:3000`.
#### Prerequisites for Nginx
- A Linux server with Docker and ThruBox Server running locally on `http://127.0.0.1:3000`.
- A registered domain or subdomain (e.g., `relay.example.com`) with a DNS `A` or `AAAA` record pointing to your server's public IP address.
- Inbound ports `80` (HTTP) and `443` (HTTPS) open on your firewall and forwarded on your router (if hosting from a local/home network).
#### 1. Install Nginx
```bash
# Debian / Ubuntu
sudo apt update && sudo apt install -y nginx
# RHEL / Fedora / AlmaLinux
sudo dnf install -y nginx
```
Enable and start Nginx:
```bash
sudo systemctl enable --now nginx
```
#### 2. Create Nginx Configuration
Create a new configuration file for ThruBox:
- **Debian / Ubuntu**: `/etc/nginx/sites-available/thrubox.conf`
- **RHEL / Fedora**: `/etc/nginx/conf.d/thrubox.conf`
```nginx
server {
listen 80;
listen [::]:80;
# Replace with your actual domain or subdomain
server_name relay.example.com;
# Allow request payloads matching or exceeding ThruBox max_payload_size (default: 500KB)
client_max_body_size 10M;
location / {
# 3000 is the default port (server.port / RELAY_SERVER_PORT).
# Update this upstream target if running on a custom port (e.g. http://127.0.0.1:).
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
# Forward request metadata for logging and accurate rate limiting
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# Overwrite X-Forwarded-For with $remote_addr to prevent clients from spoofing their IP to bypass rate limits
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
# Proxy send/read timeouts aligned with the server's 15s read/write timeouts (server idle timeout is 60s)
proxy_connect_timeout 60s;
proxy_send_timeout 15s;
proxy_read_timeout 15s;
}
}
```
**Key Configuration Directives Explained**:
- `server_name relay.example.com;`: Tells Nginx which domain name this server block handles. Replace `relay.example.com` with your actual domain or subdomain.
- `client_max_body_size 10M;`: Sets the maximum allowed HTTP request body size. Nginx's default is `1M`. Since ThruBox accepts encrypted payloads up to `messages.max_payload_size` (default: 500KB, configurable), setting this prevents Nginx from rejecting valid large payloads with `413 Request Entity Too Large`.
- `proxy_pass http://127.0.0.1:3000;`: Proxies incoming requests to the ThruBox Docker container. Note that `3000` is the default port configured by `server.port` or `RELAY_SERVER_PORT`; if you configure another port, update this upstream target (`http://127.0.0.1:`).
- `proxy_set_header X-Real-IP $remote_addr;` and `proxy_set_header X-Forwarded-For $remote_addr;`: Passes the real client IP address. Overwriting `X-Forwarded-For` with `$remote_addr` (rather than appending via `$proxy_add_x_forwarded_for`) leaves a single, trustworthy entry. ThruBox only reads it when told it is behind a proxy, so also start ThruBox with `RELAY_SECURITY_TRUSTED_PROXIES=1` (or `security.trusted_proxies: 1`); otherwise every request appears to come from Nginx and all clients share one rate-limit bucket. See [Behind a Reverse Proxy](#behind-a-reverse-proxy).
- `proxy_set_header Host` and `X-Forwarded-Proto`: Preserves the original requested host and protocol (HTTP vs HTTPS).
- `proxy_connect_timeout 60s;`, `proxy_send_timeout 15s;`, and `proxy_read_timeout 15s;`: Sets timeouts for connecting to the backend container (60s connection establishment) and transmitting/reading requests (15s send/read, aligned with ThruBox Server's 15s read/write timeouts; server idle timeout is 60s).
#### 3. Enable and Test Configuration
On Debian / Ubuntu, enable the site by linking it into `sites-enabled`:
```bash
sudo ln -s /etc/nginx/sites-available/thrubox.conf /etc/nginx/sites-enabled/
```
Test the configuration for syntax errors:
```bash
sudo nginx -t
```
Reload Nginx to apply changes:
```bash
sudo systemctl reload nginx
```
#### 4. Configure HTTPS / SSL with Let's Encrypt (Certbot)
Securing relay traffic with TLS is critical to ensure that opaque encrypted payloads and metadata are transmitted securely over HTTPS:
1. Install Certbot and its Nginx plugin:
```bash
# Debian / Ubuntu
sudo apt install -y certbot python3-certbot-nginx
# RHEL / Fedora / AlmaLinux
sudo dnf install -y certbot python3-certbot-nginx
```
2. Obtain and automatically install the SSL certificate:
```bash
sudo certbot --nginx -d relay.example.com
```
Certbot will verify your domain, obtain the certificate, update your Nginx configuration with SSL directives, and set up automatic HTTP-to-HTTPS redirection.
3. Verify automatic renewal:
```bash
sudo certbot renew --dry-run
```
#### 5. Verify the Deployment
Test that your server is reachable over the internet:
```bash
curl -i https://relay.example.com/health
```
Expected response:
```http
HTTP/2 200
content-type: application/json
{"status":"ok","timestamp":"2026-10-05T13:14:15Z"}
```
#### Troubleshooting Nginx
- **`502 Bad Gateway`**: Nginx cannot reach the backend. Check if the ThruBox container is running (`docker ps`) and responds locally (`curl http://127.0.0.1:3000/health`). If using RHEL/Fedora/CentOS with SELinux, run `sudo setsebool -P httpd_can_network_connect 1` to allow Nginx to connect to network sockets.
- **`413 Request Entity Too Large`**: The uploaded message payload exceeds Nginx's `client_max_body_size`. Increase `client_max_body_size` in your server block and reload Nginx.
- **Connection Timed Out / Unreachable**: Verify that ports `80` and `443` are allowed by your firewall (`sudo ufw allow 80/tcp && sudo ufw allow 443/tcp` on Ubuntu, or `sudo firewall-cmd --permanent --add-service=http --add-service=https && sudo firewall-cmd --reload` on RHEL/Fedora).
- **Configuration Syntax Errors**: Run `sudo nginx -t` to pinpoint syntax issues. Check `/var/log/nginx/error.log` for runtime errors.
---
### Option 2: Cloudflare Tunnel (`cloudflared`)
[Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/) creates an outbound-only connection between your local environment and Cloudflare's global edge network.
**Why use Cloudflare Tunnel?**:
- **Zero open inbound ports**: You do not need to open ports `80` or `443` on your firewall or router.
- **Works behind CGNAT & Dynamic IPs**: Ideal for home networks, residential ISPs, or cloud VPS instances without a static public IP.
- **Automatic SSL & DDoS Protection**: Edge SSL certificates and DDoS mitigation are managed automatically by Cloudflare.
#### Prerequisites for Cloudflare Tunnel
- A free [Cloudflare account](https://dash.cloudflare.com/).
- A domain managed by Cloudflare (nameservers pointed to Cloudflare).
- ThruBox Server running locally on `http://localhost:3000` (or your configured port).
You can configure Cloudflare Tunnel either via the **`cloudflared` CLI on the host** or using **Docker Compose**.
#### Method A: Host CLI Setup
##### 1. Install `cloudflared`
Install `cloudflared` following the [official installation documentation](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/):
```bash
# Debian / Ubuntu (x86_64)
curl -L --output cloudflared.deb https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
sudo dpkg -i cloudflared.deb
# macOS (Homebrew)
brew install cloudflared
# Windows (winget)
winget install --id Cloudflare.cloudflared
```
##### 2. Authenticate `cloudflared`
```bash
cloudflared tunnel login
```
This command outputs an authentication link. Open it in your browser, log in to Cloudflare, and authorize your domain zone. The command will download a certificate to `~/.cloudflared/cert.pem` (or `%USERPROFILE%\.cloudflared\cert.pem` on Windows).
##### 3. Create a Tunnel
```bash
cloudflared tunnel create thrubox-tunnel
```
This outputs a **Tunnel ID** (a UUID like `a1b2c3d4-e5f6-7890-abcd-ef1234567890`) and creates the credentials file at `~/.cloudflared/.json` (or `%USERPROFILE%\.cloudflared\.json` on Windows).
##### 4. Create the Configuration File
Create the configuration file at `~/.cloudflared/config.yml` (Linux/macOS) or `%USERPROFILE%\.cloudflared\config.yml` (Windows). Replace ``, ``, and `relay.example.com` with your actual values:
```yaml
tunnel:
# Path to tunnel credentials file (use the path matching your OS):
# Linux: /home//.cloudflared/.json
# macOS: /Users//.cloudflared/.json
# Windows: C:\Users\\.cloudflared\.json
credentials-file: /home//.cloudflared/.json
ingress:
- hostname: relay.example.com
# Note: 3000 is the default ThruBox port (server.port / RELAY_SERVER_PORT).
# Update this upstream target if running on a custom port (e.g., http://localhost:).
service: http://localhost:3000
- service: http_status:404
```
##### 5. Route DNS to the Tunnel
Associate your domain or subdomain with the tunnel:
```bash
cloudflared tunnel route dns thrubox-tunnel relay.example.com
```
This automatically creates a CNAME DNS record in Cloudflare pointing `relay.example.com` to your tunnel.
##### 6. Test and Run the Tunnel
Run the tunnel in your console to verify connectivity:
```bash
cloudflared tunnel run thrubox-tunnel
```
Test access by sending a request in a separate terminal:
```bash
curl https://relay.example.com/health
```
Once verified, stop the foreground process with `Ctrl+C`.
##### 7. Install as a Persistent Background Service
Install `cloudflared` as a system service so it automatically runs on boot:
**Linux (systemd)**:
```bash
sudo cloudflared --config /home//.cloudflared/config.yml service install
sudo systemctl enable --now cloudflared
```
**macOS (launchd)**:
```bash
sudo cloudflared --config /Users//.cloudflared/config.yml service install
sudo launchctl start com.cloudflare.cloudflared
```
**Windows (Windows Service)**:
Run in an Administrator Command Prompt or PowerShell:
```powershell
cloudflared --config "C:\Users\\.cloudflared\config.yml" service install
Start-Service cloudflared
```
---
#### Method B: Containerized Setup (Docker Compose)
If you prefer a completely containerized deployment without installing packages on the host system:
1. Go to the [Cloudflare Zero Trust Dashboard](https://one.dash.cloudflare.com/) > **Networks** > **Tunnels** > **Create a tunnel**.
2. Name your tunnel (e.g. `thrubox`) and select **Cloudflared**.
3. In the setup instructions, copy the tunnel token from the Docker command (`--token `).
4. In the **Public Hostname** tab, configure:
- **Subdomain / Domain**: e.g., `relay.example.com`
- **Service Type**: `HTTP`
- **URL**: `relay:3000` (default port; if you configured another `server.port` or `RELAY_SERVER_PORT`, update this to `relay:`)
5. Update your `docker-compose.yml`:
```yaml
services:
relay:
build: .
volumes:
- relay-data:/data
environment:
- RELAY_STORAGE_PATH=/data/relay.db
restart: unless-stopped
cloudflared:
image: cloudflare/cloudflared:latest
restart: unless-stopped
command: tunnel --no-autoupdate run --token ${CLOUDFLARE_TUNNEL_TOKEN}
depends_on:
- relay
volumes:
relay-data:
```
6. Set your token in a `.env` file in the same directory:
```bash
CLOUDFLARE_TUNNEL_TOKEN=eyJh...your_cloudflare_token_here...
```
7. Start your services:
```bash
docker compose up -d
```
In this setup, `cloudflared` communicates directly with `relay:3000` (or `relay:` if using a custom port) over the private Docker bridge network. The ThruBox port does not even need to be mapped to the host!
#### Troubleshooting Cloudflare Tunnel
- **`Error 1033 (Argo Tunnel error)`**: `cloudflared` is running, but cannot establish a connection to `http://localhost:3000` (or `http://relay:3000`). Confirm that the ThruBox container is running and healthy.
- **SSL/TLS Redirect Loops**: In the Cloudflare dashboard under **SSL/TLS**, set your encryption mode to **Full** (or **Flexible** if proxying to HTTP). Avoid "Off".
- **DNS Record Issues**: Check the **DNS** records in your Cloudflare dashboard to ensure the CNAME for `relay.example.com` points to `.cfargotunnel.com` with proxy enabled (orange cloud).
- **Inspect Service Logs**:
- Host service: `sudo journalctl -u cloudflared -f`
- Docker container: `docker logs -f cloudflared`
---
### š Post-Deployment Security Checklist
Once your ThruBox Server is exposed to the internet:
1. **Protect Private Relays with an API Key**:
If your server is intended for private use, enable API key authentication by setting `security.api_key` in `config.yaml` or through the environment variable:
```bash
RELAY_SECURITY_API_KEY="your-secret-api-key"
```
Clients will be required to send this value in the `X-API-Key` HTTP header.
2. **Verify Rate Limiting & Client IP Handling**:
ThruBox has built-in IP rate limiting (default: 30 requests/minute/IP, configurable via `security.rate_limit` or `RELAY_SECURITY_RATE_LIMIT`). By default it limits by the connecting address and ignores `X-Forwarded-For` and `X-Real-IP`. Behind a proxy that address is the proxy's, so set `security.trusted_proxies` / `RELAY_SECURITY_TRUSTED_PROXIES` to the number of proxies in front of ThruBox (see [Behind a Reverse Proxy](#behind-a-reverse-proxy)):
- **Nginx**: With the configuration above, set `trusted_proxies: 1`. Nginx overwrites `X-Forwarded-For` with the connecting address, and ThruBox reads that one entry.
- **Cloudflare Tunnel**: Cloudflare appends the address of the client that connected to its edge to `X-Forwarded-For`, and `cloudflared` passes the header on, so `trusted_proxies: 1` reads Cloudflare's entry, the client address Cloudflare itself saw, and ignores anything the client sent. This only holds if ThruBox is reachable solely through the tunnel (see **Block direct access** in [Behind a Reverse Proxy](#behind-a-reverse-proxy)). Confirm it with the log check described in [Behind a Reverse Proxy](#behind-a-reverse-proxy). You can also enforce rate limits at the edge with Cloudflare WAF / Rate Limiting Rules.
3. **Configure CORS If Calling Directly from Browsers**:
If web applications hosted on other domains will call your relay directly, configure `security.allowed_origins` in `config.yaml` or set `RELAY_SECURITY_ALLOWED_ORIGINS` (see [CORS](#cors)). If you serve your frontend and relay under the same domain using reverse proxy path routing, CORS configuration is not needed.
---
## š Contributing
ā Don't forget to star this repository if you find it useful! ā
Thank you for considering contributing to this project! Contributions are highly appreciated and welcomed. To ensure smooth collaboration, please refer to our [Contribution Guidelines](./CONTRIBUTING.md).
---
## ⨠Maintainers
See [MAINTAINERS.md](./MAINTAINERS.md) for the full list of Mentors and Maintainers for this repository.
---
## š License
This project is licensed under the GNU General Public License v3.0.
See the [LICENSE](LICENSE) file for details.
---
## šŖ Thanks To All Contributors
Thanks a lot for spending your time helping ThruBox grow. Keep rocking š„
[](https://github.com/AOSSIE-Org/ThruBox-Server/graphs/contributors)
Ā© 2025 AOSSIE