tnl.dev :: docs
self-hosting tnld.
understand the deployment shapes and responsibilities of a tnl server operator.
A tnl server includes PostgreSQL and one or more tnld processes. Start with the standalone role
for a small deployment; use separate control, ingress, and relay services when those roles need
independent capacity or failure boundaries.
choose a deployment shape
| TNLD_MODE | responsibility | PostgreSQL |
|---|---|---|
| standalone | control, ingress, two logical relay services | required |
| control | state, placement, certificates, administration | required |
| ingress | visitor policy, relay selection, forwarding | never |
| relay | publisher connections and visitor streams | never |
Standalone composes the complete service in one process while preserving two logical relay services. A split deployment runs a control service, an ingress service, and at least two independently addressable relay services. Only control and standalone receive database, storage key, hosted authority, DNS provider, or ACME account credentials.
required configuration
| role | required settings |
|---|---|
| standalone | database, domains, ACME, login token, storage key |
| control | standalone control settings plus cluster secret |
| ingress | control hostname, cluster secret, ingress ID |
| relay | control hostname, cluster secret, service and relay IDs, addresses |
TNLD_CONTROL_HOSTNAME is a canonical hostname without a scheme, path, or port; HTTPS on port 443
is implied. Every ingress and relay creates a process run ID at startup. Configured process
identities remain stable across runs.
Listen addresses, limits, timing, metrics, a non-default ACME directory, and static public certificate overrides are advanced settings. There is no private certificate authority or custom client root configuration to distribute.
migrate and protect postgresql
tnld migrate is the only migration path and reads only TNLD_DATABASE_DIRECT_URL:
TNLD_DATABASE_DIRECT_URL='postgresql://tnl_migrate:...@database.example/tnl?sslmode=require' \
tnld migrateServing control and standalone processes read only the pooled TNLD_DATABASE_URL. They never run
migrations and require the exact schema version supported by their binary. Ingress and relay
processes must never receive either URL.
Control and standalone also require TNLD_STORAGE_KEY, the canonical unpadded base64url encoding
of exactly 32 random bytes. It encrypts recoverable ACME and certificate material in PostgreSQL.
Back it up separately in the deployment secret store; losing the current and previous keys makes
the encrypted material unrecoverable.
Rotate by deploying the new TNLD_STORAGE_KEY with the old value in
TNLD_STORAGE_KEY_PREVIOUS, waiting for control to re-encrypt stored secrets, and then removing the
previous value in a second rollout.
plan addresses and dns
The server domain is an infrastructure suffix. The managed deployment domain contains public route namespaces. They are independent:
TNLD_SERVER_DOMAIN=tnl.example.com
TNLD_MANAGED_DEPLOYMENT_DOMAIN=tunnels.example.com| hostname | destination |
|---|---|
| control.tnl.example.com | control service |
| ingress.tnl.example.com | ingress address |
| relay.tnl.example.com | standalone relay listener |
| relay-a.tnl.example.com | split relay service A |
| relay-b.tnl.example.com | split relay service B |
| route hostnames beneath tunnels.example.com | ingress address |
Public route DNS always targets the ingress address. Relay placement never changes route DNS.
TNLD_RELAY_SERVICE_ID selects the split service label and stable relay address.
Control obtains exact public certificates through ACME. Relay transport TLS uses an exact-hostname WebPKI certificate managed by control, and publishers verify it with system trust roots. Route TLS is separately publicly trusted and terminates at the publisher.
For automated split relay certificates, control needs the Route 53 zone containing the server domain. Route DNS automation additionally needs the managed-domain zone and at least one stable ingress IPv4 or IPv6 address. DNS credentials belong only on control. Static certificate files and operator-managed route records remain advanced alternatives.
create the bootstrap token
Generate a bootstrap management token and store it as a deployment secret:
tnld login-tokenProvide it to control and standalone as TNLD_LOGIN_TOKEN. It authenticates one built-in
administrator identity and permanent personal team. Use OIDC for multiple people and retain the
token for operator recovery.
configure cluster authentication
A split deployment shares one high-entropy TNLD_CLUSTER_SECRET among control, ingress, and relay
processes. It authenticates private process coordination at
control.<server-domain>:9443; restrict that listener and internal relay addresses to the deployment
network. The cluster secret is not a user credential and does not authorize the public control API.
For rolling rotation, deploy the new value in TNLD_CLUSTER_SECRET and the old value in
TNLD_CLUSTER_SECRET_PREVIOUS, update every split process, and then remove the previous value.
Standalone does not accept a cluster secret because its components communicate in process.
Each ingress and relay lease is valid only while its exact configured identity, process run ID, and lease revision are current. Restarted or expired processes cannot continue using stale state.
run standalone with compose
The reference
Compose file runs migration and
then one standalone process. Create deploy/.env, replace every placeholder, and include at least:
TNLD_MODE=standalone
TNLD_DATABASE_URL=postgresql://tnl_runtime:...@pool.example/tnl?sslmode=require
TNLD_DATABASE_DIRECT_URL=postgresql://tnl_migrate:...@database.example/tnl?sslmode=require
TNLD_SERVER_DOMAIN=tnl.example.com
TNLD_MANAGED_DEPLOYMENT_DOMAIN=tunnels.example.com
TNLD_ACME_EMAIL=operator@example.com
TNLD_ACME_ACCEPT_TERMS=true
TNLD_LOGIN_TOKEN=tnl_login_...
TNLD_STORAGE_KEY=...Pin TNL_IMAGE by digest, then start and probe control:
docker compose pull
docker compose up -d
curl --fail https://control.tnl.example.com/v1/readyThe image runs as a non-root user with a read-only root filesystem. No daemon state volume or certificate mount is required because PostgreSQL owns durable state.
Authenticate with the bootstrap token and publish:
tnl login https://control.tnl.example.com --token
tnl publish 3000run split services
The reference
split Compose file separates
control, ingress, and two relay services. Give ingress TNLD_INGRESS_ID. Give every relay process a
TNLD_RELAY_SERVICE_ID, unique TNLD_RELAY_ID, stable public TNLD_RELAY_ADDRESS, and reachable
TNLD_INTERNAL_RELAY_ADDRESS.
Expose public TCP 443 for control and ingress and public TCP and UDP 443 for every relay service. Restrict private TCP 9443 and internal relay addresses. Ingress and relay containers need no PostgreSQL, storage key, hosted authority, administrator, DNS provider, or ACME account credentials.
Each route session maintains two connection slots assigned to distinct relay services. Initial routability requires the route certificate and both publisher connections. Afterward, one ready publisher connection remains routable while the publisher replenishes toward two.
operate safely
Probe /v1/health for HTTP serving and /v1/ready for control readiness. Scrape
TNLD_METRICS_LISTEN only over a private network. Alert on control errors, certificate renewal
failures, expired ingress or relay leases, insufficient ready publisher connections, capacity
rejection, and file descriptor pressure.
Maintenance controls independently gate route creation, route-session creation, and certificate issuance:
tnl admin maintenance list
tnl admin maintenance disable route_session_creationDrain a concrete relay lease before planned removal. Use the exact process run ID and lease revision
reported by tnl admin relays list:
tnl admin relays drain relay-a-1 \
--relay-run-id relay_run_0123456789abcdef \
--relay-lease-revision 42 \
--deadline 30sDraining rejects new work and removes the process from selection while admitted visitor streams finish until the deadline. Established streams are never migrated or replayed.
Back up PostgreSQL and TNLD_STORAGE_KEY independently, test restoration, and run migration before
rolling control, ingress, and relay services in that order. Production images must remain
digest-pinned.
Use the repository's self-hosting runbook, observability guide, and release verification guide for the complete operational contracts.