Skip to main content

Overview

rs-tunnel enforces quotas to prevent resource exhaustion and ensure fair usage. The primary quota is a limit on the number of active tunnels per user.

Maximum Active Tunnels

Configuration

From apps/api/src/config/env.ts:37:
MAX_ACTIVE_TUNNELS: z.coerce.number().int().positive().default(5)
The default limit is 5 active tunnels per user.
This limit can be configured via the MAX_ACTIVE_TUNNELS environment variable.

Setting the Quota

In your .env file:
MAX_ACTIVE_TUNNELS=10
Or as an environment variable:
export MAX_ACTIVE_TUNNELS=10
Changing MAX_ACTIVE_TUNNELS requires restarting the API server for the new value to take effect.

Quota Enforcement

When Quotas Are Checked

Quotas are enforced when a user attempts to create a new tunnel. From apps/api/src/services/tunnel.service.ts:35-36:
const activeCount = await this.repository.countActiveTunnels(input.userId);
assertWithinTunnelLimit(activeCount, this.env.MAX_ACTIVE_TUNNELS);

Quota Assertion Logic

From apps/api/src/services/quota.ts:
import { AppError } from '../lib/app-error.js';

export function assertWithinTunnelLimit(activeCount: number, maxActive: number): void {
  if (activeCount >= maxActive) {
    throw new AppError(
      409,
      'TUNNEL_LIMIT_REACHED',
      `Maximum of ${maxActive} active tunnels reached.`,
      { activeCount, maxActive },
    );
  }
}
1

Count active tunnels

Query the database for the number of active tunnels belonging to the user.
2

Check quota

If the active count is >= the maximum allowed, throw an error.
3

Allow creation

If under the limit, proceed with tunnel creation.

What Counts as Active?

From the tunnel service implementation, a tunnel is considered active if its status is in the ACTIVE_STATES set:
const ACTIVE_STATES = new Set(['active', 'stopping']);
Only tunnels with status active or stopping count toward the quota.
Stopped or failed tunnels do not count against the quota. Users can create new tunnels once old ones are fully stopped.

Error When Quota Exceeded

When a user exceeds their quota, the API returns a 409 Conflict error:

Error Response

{
  "statusCode": 409,
  "code": "TUNNEL_LIMIT_REACHED",
  "message": "Maximum of 5 active tunnels reached.",
  "details": {
    "activeCount": 5,
    "maxActive": 5
  }
}

Error Fields

FieldDescription
statusCodeHTTP status code (409)
codeError code identifier
messageHuman-readable error message including the limit
details.activeCountCurrent number of active tunnels
details.maxActiveMaximum allowed tunnels
The error message dynamically includes the configured limit, so users know exactly how many tunnels are allowed.

CLI Error Handling

When the CLI receives a quota error, it should:
  1. Display the error message to the user
  2. Suggest stopping an existing tunnel to free up quota
  3. Optionally show a list of active tunnels
Example CLI output:
✖ Failed to create tunnel: Maximum of 5 active tunnels reached.

You currently have 5 active tunnels. Stop an existing tunnel to create a new one:

  rs-tunnel list
  rs-tunnel stop <tunnel-id>

Checking Current Usage

Users can check their current tunnel usage with the list command:
rs-tunnel list
From apps/api/src/services/tunnel.service.ts:113-137, the list endpoint returns all active tunnels with their status and lease information.

Example Response

[
  {
    "id": "tunnel_abc123",
    "hostname": "my-app.tunnel.example.com",
    "slug": "my-app",
    "status": "active",
    "requestedPort": 3000,
    "createdAt": "2026-03-05T10:00:00Z",
    "lease": {
      "lastHeartbeatAt": "2026-03-05T10:05:00Z",
      "expiresAt": "2026-03-05T10:06:00Z"
    },
    "stoppedAt": null,
    "lastError": null
  }
]
Include the --all flag to list inactive tunnels (stopped, failed) that don’t count against the quota.

Freeing Up Quota

To free up quota, users must stop active tunnels:
rs-tunnel stop <tunnel-id>
Or stop by hostname:
rs-tunnel stop my-app.tunnel.example.com
Once a tunnel is stopped, its status changes to stopped and it no longer counts toward the quota.

Per-User vs Global Quotas

The current implementation enforces per-user quotas. Each user gets their own limit of MAX_ACTIVE_TUNNELS.

Per-User Quota

Each user can have up to N active tunnels.Pros: Fair usage, prevents one user from exhausting resourcesCurrent implementation

Global Quota

All users share a global pool of tunnels.Pros: Simpler for small deploymentsNot currently implemented

Future Enhancements

Potential quota features for future implementation:

Custom Per-User Limits

Allow different users to have different quota limits based on role or subscription tier.

Bandwidth Quotas

Limit total bandwidth usage per user per month.

Request Rate Limits

Limit requests per second through tunnels.

Quota Notifications

Notify users when they’re approaching their quota limit.

Monitoring Quota Usage

You can monitor quota usage and quota-exceeded errors through:

Audit Logs

Tunnel creation attempts are logged:
await this.repository.createAuditLog({
  userId: user.id,
  action: 'tunnel.created',
  metadata: {
    tunnelId: dbTunnel.id,
    slug,
    hostname,
  },
});
Quota-exceeded errors can be tracked by monitoring failed tunnel creation attempts.

Database Queries

Query active tunnel counts per user:
SELECT user_id, COUNT(*) as active_count
FROM tunnels
WHERE status IN ('active', 'stopping')
GROUP BY user_id
ORDER BY active_count DESC;

Application Metrics

Consider instrumenting the quota check with metrics:
metrics.increment('quota.check');
if (activeCount >= maxActive) {
  metrics.increment('quota.exceeded');
  throw new AppError(...);
}
Set up alerts when quota exceeded errors spike, which may indicate users need higher limits or there’s an issue with tunnel cleanup.

Troubleshooting

Issue: User can’t create tunnels despite seeing fewer than max

Cause: Tunnels in stopping state count toward the quota. Solution: Wait for tunnels to fully stop (lease expires, reaper cleans up) or manually verify tunnel status in the database.

Issue: Quota limit not updating after configuration change

Cause: API server needs to be restarted to load new environment variables. Solution: Restart the API server after changing MAX_ACTIVE_TUNNELS.

Issue: Quota exceeded error shows wrong limit

Cause: Multiple API instances with different configurations. Solution: Ensure all API instances use the same environment configuration.

Build docs developers (and LLMs) love