Skip to main content
rs-tunnel uses environment variables for all configuration. Create a .env file in the repository root or apps/api/.env.
Copy .env.example to .env and update the values:
cp .env.example .env

Core API Configuration

API_BASE_URL
string
required
Public base URL where the API is accessible.Examples:
  • Local development: http://localhost:8080
  • Production: https://api.yourdomain.com
This URL must be accessible by CLI clients. For production, use HTTPS.
PORT
number
default:"8080"
Port the API server listens on.
PORT=8080
DATABASE_URL
string
required
PostgreSQL connection string.Format: postgres://user:password@host:port/databaseExamples:
# Local development
DATABASE_URL=postgres://postgres:postgres@localhost:5432/rs_tunnel

# Docker compose (internal network)
DATABASE_URL=postgres://postgres:postgres@postgres:5432/rs_tunnel

# Production (managed database)
DATABASE_URL=postgres://user:[email protected]:5432/rs_tunnel?sslmode=require
The database must exist before running migrations. Ensure PostgreSQL user has CREATE, ALTER, and DROP permissions.
JWT_SECRET
string
required
Secret key for signing access tokens. Minimum 16 characters.Generate a secure secret:
openssl rand -base64 32
Never commit this to version control. Use environment-specific secrets management.
REFRESH_TOKEN_SECRET
string
required
Secret key for signing refresh tokens. Minimum 16 characters. Must be different from JWT_SECRET.Generate a secure secret:
openssl rand -base64 32

Access Policy Configuration

ALLOWED_EMAIL_DOMAIN
string
default:"@example.com"
Email domain restriction for user authentication. Only emails ending with this domain can authenticate.Examples:
ALLOWED_EMAIL_DOMAIN=@yourcompany.com
ALLOWED_EMAIL_DOMAIN=@acme.io
The @ prefix is optional. The API normalizes it automatically: yourcompany.com@yourcompany.com
Legacy compatibility: RIPSEED_EMAIL_DOMAIN is accepted as a fallback.
ALLOWED_SLACK_TEAM_ID
string
required
Slack workspace team ID. Only users from this workspace can authenticate.Format: Starts with T (e.g., T01234567AB)
ALLOWED_SLACK_TEAM_ID=T01234567AB
Find your team ID in the Slack URL: https://app.slack.com/client/T01234567AB/...
Legacy compatibility: RIPSEED_SLACK_TEAM_ID is accepted as a fallback.

Slack OAuth Configuration

SLACK_CLIENT_ID
string
required
OAuth client ID from your Slack app configuration.Found in Slack AppBasic InformationApp Credentials.
SLACK_CLIENT_ID=1234567890.1234567890123
SLACK_CLIENT_SECRET
string
required
OAuth client secret from your Slack app configuration.Found in Slack AppBasic InformationApp Credentials.
SLACK_CLIENT_SECRET=abcdef1234567890abcdef1234567890
Keep this secret secure. Never log or expose it in client-side code.
SLACK_REDIRECT_URI
string
required
OAuth callback URL that Slack redirects to after authentication.Must exactly match the redirect URL configured in your Slack app.Examples:
# Local development
SLACK_REDIRECT_URI=http://localhost:8080/v1/auth/slack/callback

# Production
SLACK_REDIRECT_URI=https://api.yourdomain.com/v1/auth/slack/callback
The path must be /v1/auth/slack/callback. Only the base URL changes between environments.

Cloudflare Configuration

CLOUDFLARE_ACCOUNT_ID
string
required
Cloudflare account identifier.Found in Cloudflare DashboardYour DomainAPI section (right sidebar).
CLOUDFLARE_ACCOUNT_ID=abc123def456ghi789
CLOUDFLARE_ZONE_ID
string
required
Cloudflare zone (domain) identifier.Found in Cloudflare DashboardYour DomainAPI section (right sidebar).
CLOUDFLARE_ZONE_ID=xyz789abc123def456
CLOUDFLARE_API_TOKEN
string
required
API token with Tunnel and DNS edit permissions.Required permissions:
  • ZoneDNSEdit
  • AccountCloudflare TunnelEdit
Create at My ProfileAPI TokensCreate Token.
CLOUDFLARE_API_TOKEN=your-api-token-here
Use least-privilege principle. Only grant Tunnel + DNS permissions. Never grant Global API Key access.
CLOUDFLARE_BASE_DOMAIN
string
default:"tunnel.example.com"
Base domain for tunnel hostnames.All tunnels will be created as subdomains: <slug>.<CLOUDFLARE_BASE_DOMAIN>Examples:
CLOUDFLARE_BASE_DOMAIN=tunnel.yourdomain.com
CLOUDFLARE_BASE_DOMAIN=dev.acme.io
With CLOUDFLARE_BASE_DOMAIN=tunnel.yourdomain.com, a tunnel with slug my-app becomes:
https://my-app.tunnel.yourdomain.com
This domain must be managed by the Cloudflare zone specified in CLOUDFLARE_ZONE_ID.

Behavior Controls

JWT_ACCESS_TTL_MINUTES
number
default:"15"
Access token time-to-live in minutes.
JWT_ACCESS_TTL_MINUTES=15
Shorter TTL = better security, more frequent refresh token usage.
REFRESH_TTL_DAYS
number
default:"30"
Refresh token time-to-live in days.
REFRESH_TTL_DAYS=30
Users must re-authenticate after this period.
MAX_ACTIVE_TUNNELS
number
default:"5"
Maximum number of concurrent active tunnels per user.
MAX_ACTIVE_TUNNELS=5
This is enforced server-side. Users attempting to create more tunnels will receive a 409 Conflict error.
HEARTBEAT_INTERVAL_SEC
number
default:"20"
How often (in seconds) CLI clients must send heartbeats to keep tunnels alive.
HEARTBEAT_INTERVAL_SEC=20
If a client stops heartbeating, the tunnel lease expires after LEASE_TIMEOUT_SEC.
LEASE_TIMEOUT_SEC
number
default:"60"
Seconds without a heartbeat before a tunnel lease is considered expired.
LEASE_TIMEOUT_SEC=60
Should be significantly larger than HEARTBEAT_INTERVAL_SEC to account for network delays.
REAPER_INTERVAL_SEC
number
default:"30"
How often (in seconds) the cleanup worker checks for expired leases.
REAPER_INTERVAL_SEC=30
Expired tunnels are automatically stopped and DNS records are removed.

CLI Configuration

RS_TUNNEL_API_URL
string
default:"http://localhost:8080"
Client-side variable for the CLI. Points to the API base URL.Set globally for CLI usage:
export RS_TUNNEL_API_URL=https://api.yourdomain.com
Or use the --domain flag:
rs-tunnel login --email [email protected] --domain https://api.yourdomain.com
The CLI also saves this to ~/.rs-tunnel/config.json after first use.
RS_TUNNEL_API_BASE_URL
string
default:"http://localhost:8080"
Legacy alias for RS_TUNNEL_API_URL. Still supported for backward compatibility.Prefer using RS_TUNNEL_API_URL instead.

Example Configuration Files

Local Development

.env
# Shared
API_BASE_URL=http://localhost:8080

# API
DATABASE_URL=postgres://postgres:postgres@localhost:5432/rs_tunnel
PORT=8080
JWT_SECRET=your-jwt-secret-min-16-chars
REFRESH_TOKEN_SECRET=your-refresh-secret-min-16-chars
SLACK_CLIENT_ID=1234567890.1234567890123
SLACK_CLIENT_SECRET=abcdef1234567890abcdef1234567890
SLACK_REDIRECT_URI=http://localhost:8080/v1/auth/slack/callback
ALLOWED_EMAIL_DOMAIN=@yourcompany.com
ALLOWED_SLACK_TEAM_ID=T01234567AB
CLOUDFLARE_ACCOUNT_ID=abc123def456ghi789
CLOUDFLARE_ZONE_ID=xyz789abc123def456
CLOUDFLARE_API_TOKEN=your-cloudflare-token
CLOUDFLARE_BASE_DOMAIN=tunnel.yourdomain.com
JWT_ACCESS_TTL_MINUTES=15
REFRESH_TTL_DAYS=30
MAX_ACTIVE_TUNNELS=5
HEARTBEAT_INTERVAL_SEC=20
LEASE_TIMEOUT_SEC=60
REAPER_INTERVAL_SEC=30

# CLI
RS_TUNNEL_API_URL=http://localhost:8080

Production

.env
# Shared
API_BASE_URL=https://api.yourdomain.com

# API
DATABASE_URL=postgres://user:[email protected]:5432/rs_tunnel?sslmode=require
PORT=8080
JWT_SECRET=${SECRET_JWT_SECRET}
REFRESH_TOKEN_SECRET=${SECRET_REFRESH_TOKEN}
SLACK_CLIENT_ID=${SLACK_CLIENT_ID}
SLACK_CLIENT_SECRET=${SLACK_CLIENT_SECRET}
SLACK_REDIRECT_URI=https://api.yourdomain.com/v1/auth/slack/callback
ALLOWED_EMAIL_DOMAIN=@yourcompany.com
ALLOWED_SLACK_TEAM_ID=T01234567AB
CLOUDFLARE_ACCOUNT_ID=${CLOUDFLARE_ACCOUNT_ID}
CLOUDFLARE_ZONE_ID=${CLOUDFLARE_ZONE_ID}
CLOUDFLARE_API_TOKEN=${CLOUDFLARE_API_TOKEN}
CLOUDFLARE_BASE_DOMAIN=tunnel.yourdomain.com
JWT_ACCESS_TTL_MINUTES=15
REFRESH_TTL_DAYS=30
MAX_ACTIVE_TUNNELS=10
HEARTBEAT_INTERVAL_SEC=20
LEASE_TIMEOUT_SEC=60
REAPER_INTERVAL_SEC=30
In production, use a secrets manager (AWS Secrets Manager, HashiCorp Vault, etc.) instead of hardcoding sensitive values.

Environment Variable Validation

The API validates all environment variables at startup using Zod schemas (see apps/api/src/config/env.ts:16). Validation rules:
  • API_BASE_URL: Must be a valid URL
  • DATABASE_URL: Required, non-empty string
  • JWT_SECRET and REFRESH_TOKEN_SECRET: Minimum 16 characters each
  • PORT: Positive integer (default: 8080)
  • SLACK_REDIRECT_URI: Must be a valid URL
  • Email domain normalization: Automatically adds @ prefix if missing
If validation fails, the API will not start and will output detailed error messages.

Troubleshooting

Cause: DATABASE_URL is not loaded or incomplete.Solution:
  1. Verify .env file exists in repo root or apps/api/.env
  2. Check DATABASE_URL format: postgres://user:password@host:port/database
  3. Ensure no extra whitespace or quotes around the value
Cause: SLACK_REDIRECT_URI doesn’t match Slack app configuration.Solution:
  1. Go to Slack app settings → OAuth & Permissions
  2. Verify redirect URL exactly matches SLACK_REDIRECT_URI
  3. Include protocol (http:// or https://) and path (/v1/auth/slack/callback)
Cause: JWT_SECRET or REFRESH_TOKEN_SECRET is too short.Solution: Generate new secrets with at least 16 characters:
openssl rand -base64 32
Cause: User’s email doesn’t match ALLOWED_EMAIL_DOMAIN.Solution:
  1. Verify ALLOWED_EMAIL_DOMAIN is set correctly (e.g., @yourcompany.com)
  2. Ensure user email ends with this domain
  3. Check for typos in the domain name

Next Steps

Docker Setup

Configure Docker Compose for PostgreSQL and API

Database Migration

Run Drizzle ORM migrations to set up the schema

Build docs developers (and LLMs) love