This page defines the implemented edge contract. Manifest additions, managed TLS delivery, Supervisor relays, protocol policy, private APIs, Envoy/xDS, and the MySQL gateway exist in source with component and local integration tests. The port map and client examples do not establish public deployment or verified connectivity. Check the returned endpoint and protocol readiness before attempting a connection.
Four external ports#
Ports identify protocols, never tenants. Every tenant shares the applicable protocol port; SNI hostnames or platform-minted credentials select the tenant. No additional external TCP port is permitted. Runtime ports and internal Unix sockets are independent of this map.
| External port | Protocol | Tenant selection and TLS |
|---|---|---|
| 443 | HTTPS; Redis/Valkey TLS; MongoDB TLS; other client-first TLS protocols | TLS ClientHello SNI selects the route. Private TCP TLS passes through to the runtime. HTTP continues through the HTTP routing and access layer. |
| 5432 | PostgreSQL | SSLRequest-aware negotiation followed by SNI selection; the stream passes through byte for byte, so TLS exists only when the database itself has ssl enabled. |
| 3306 | MySQL and MariaDB | A peer MySQL gateway handles the server-first handshake, requires TLS, authenticates platform-minted credentials, and routes by the site-unique username. |
| 5671 | AMQPS | Client-first TLS and SNI selection; TLS passes through to the runtime. |
External application traffic uses TLS only. Plaintext RESP, AMQP, and database sessions are never externally offered. A schema protocol value alone does not prove TLS capability: for example, a MongoDB service without server TLS remains internal-only. There is no external plaintext Redis listener or per-tenant port.
Routing mechanisms#
Client-first TLS through Envoy#
Envoy inspects the ClientHello on 443 and 5671, chooses an SNI filter chain, checks the source allowlist, and forwards the unmodified TLS stream to the selected Supervisor endpoint relay. xDS supplies endpoint and grant state. Missing or unknown SNI and unauthorized sources fail closed before wake. Use the assigned service hostname rather than an IP address; verify its certificate on the client.
PostgreSQL SSLRequest-aware negotiation#
PostgreSQL normally sends an eight-byte SSLRequest before a TLS ClientHello.
The Envoy C module consumes that negotiation before SNI selection, then sends
an independent SSLRequest on the backend leg; a generic TLS inspector alone
is insufficient. The SSLRequest is negotiation,
not permission to skip authentication. Database credentials are always
required; TLS exists only when the database itself has ssl enabled, because
the edge relays the stream byte for byte and never terminates it. A client
without SNI cannot select a tenant on the shared listener.
MySQL gateway#
MySQL sends its server greeting before the client sends a TLS ClientHello.
The gateway on 3306 therefore terminates client TLS, verifies platform-minted
credentials, and selects the endpoint by the unique routing username. It is a
peer of Envoy, not an Envoy listener. The private backend is plaintext by design;
protocol: mysql rejects the platform backend tls: {} marker.
MySQL/MariaDB/WordPress template defaults render u_{{ site.token }} using the persisted
site ID. Connect with that resolved username from the owner connection panel,
not a shared app username or the literal placeholder. Credential generation, gateway snapshot consumption, and revocation enforcement
are implemented and tested. Public listener activation still requires deployment
verification. Custom usernames
require a valid unique gateway binding before external access can be enabled.
Grants and allowlists#
The current owner API accepts POST /sites/{slug}/tcp-access with a non-public
manifest service name and a duration of 1–1440 minutes. It derives the source IP
from the request and returns hostname, port, connection, cidr,
expires_at, operation_id, and grant_id, plus edge_state,
protocol_state, protocol, and nullable routing_username. Connect only from
the reported CIDR. A grant is network authorization; it does not replace
database authentication or TLS verification.
Source responses use mysql://<routing_username>@<hostname>:3306 for MySQL and
tcp://<hostname>:<port> for other protocols. They report
configured_unverified / unverified; allocation or configuration is not a
successful protocol probe. The source policy selects services.<name>.protocol,
uses the fixed port map above, and retains the legacy private database@5432
inference for compatibility. tcp, redis, mongodb, and amqp require the
tls: {} marker for an external grant. Runtime TLS capability must still be
verified. Service names must also form a safe endpoint identity for provisioning.
Omitting protocol is intended to leave a new private service internal-only.
Current validation normalizes omission to tcp; the policy can admit that
normalized service if it has tls: {}. Omission alone therefore does not prove
isolation. Declare the protocol explicitly and withhold grants for internal
services. Strict omitted-protocol isolation remains a policy gap.
Envoy/gateway admission and Supervisor relay checks enforce
grant CIDRs and expiry before wake. Revocation and credential rotation invalidate cached authorization. The relay bounds established connections by
the grant deadline. HTTP public / authenticated access is a separate policy.
Private control-plane interfaces — implemented in source#
These routes are outside /v/api/v1, use dedicated internal bearer credentials,
and are not customer or agent PAT interfaces:
| Method and route | Contract |
|---|---|
GET /_internal/edge/state |
Full endpoint replacement snapshot: protocol, external port/mechanism, node, socket_path / internal_port, PROXY-v2 requirement, grant CIDRs/expiry, draining, and available. |
POST /_internal/edge/authorize |
Accepts source_ip, sni, and listener_port; returns allowed, endpoint identity, and a bounded authorization lifetime. Denies stale, draining, missing, or unauthorized routes without waking them. |
POST /_internal/edge/endpoints/{endpoint_id}/drain |
Marks draining and increments the revision. It does not itself withdraw an Envoy cluster, quiesce a relay, or checkpoint. |
GET /_internal/gateway/credentials |
Full credential replacement snapshot: username, password verifier, endpoint/socket identity, revocation state, and active grant deadlines. The default response is verifier-only; an explicit HTTPS-only include_backend_password=1 pull supplies the password required for independent backend authentication. |
Edge state/authz/drain require SITE_EDGE_API_TOKEN; credential sync requires
SITE_MYSQL_GATEWAY_API_TOKEN. Empty or incorrect tokens fail closed. Snapshots
include replace: true, a content revision, cache_ttl_seconds, and
valid_until; consumers must evict absent entries and expired snapshots.
The configured TTL is capped at five seconds and bounded by grant expiry.
Rotation, expiry, drain, and purge affect the next snapshot. The gateway evicts
stale/failed snapshots and revokes sessions. Envoy removes draining resources;
controller ACK and connection grace-period checks must precede Supervisor
quiescence in the lifecycle coordinator.
Connect with protocol clients#
These examples apply only after the relevant edge stage and endpoint have been
verified. Set TCP_HOST to the returned service hostname and CA_FILE to a
trusted CA bundle. Use the owner-revealed credentials; never send a control-plane
PAT to an application endpoint. Keep passwords out of URLs and shell history.
| Protocol | Client configuration |
|---|---|
| HTTP | Open https://<site>.<namespace>.serverl3ss.com on 443; follow the site's HTTP access policy. |
| PostgreSQL | Use the service hostname, 5432, and SNI. The stream passes through byte for byte: enable ssl with the delivered service certificate on the database for TLS; the seeded PostgreSQL template currently serves plaintext, so sslmode=prefer negotiates down. |
| MySQL/MariaDB | Use 3306, the resolved routing username, and VERIFY_IDENTITY. |
| Redis/Valkey | Use 443, TLS, SNI, and ACL user app; the URI scheme is rediss. |
| AMQP | Use amqps://<username>@<service-host>:5671 with hostname verification and application credentials. |
| MongoDB / generic TCP | Use 443 only with client-first TLS, SNI, certificate verification, and an approved binding; otherwise keep the service internal. |
psql "host=$TCP_HOST port=5432 dbname=app user=app"
# With ssl enabled on the database:
psql "host=$TCP_HOST port=5432 dbname=app user=app sslmode=verify-full sslrootcert=$CA_FILE"
mysql --host="$TCP_HOST" --port=3306 --user="$ROUTING_USERNAME" \
--ssl-mode=VERIFY_IDENTITY --ssl-ca="$CA_FILE" --password app
redis-cli --tls --sni "$TCP_HOST" --cacert "$CA_FILE" \
-h "$TCP_HOST" -p 443 --user app --askpass PING
MariaDB clients must use their equivalent CA and server-certificate verification
options. A Redis/Valkey server must enable its TLS port, disable plaintext
(--port 0), load its service certificate/key, and authenticate the app ACL
user. The seeded ACL disables default and removes @dangerous commands.
Reconnect and awake time#
Client connections do not survive instance recycle. Checkpoint/restore, snapshots, restart, node moves, and idle scale-to-zero require clients to reconnect. Configured durable state persists, but a socket and an in-flight transaction are not durable state. Use connection-pool retries with bounded backoff and resolve ambiguous write outcomes before retrying.
Client keepalives hold an instance awake; that awake time is billable. Tune pool lifetime, idle connections, and keepalives to the workload's cost and latency needs. Supervisor relays own connection idle timeouts. Edge active health checks against wake-gated runtimes are forbidden because they would continually wake the workload.
Implementation and metering status#
| Stage | Repository status |
|---|---|
| Protocol/TLS manifest fields and template defaults | Implemented in the validator and seed source; existing database rows need the normal seed rollout. |
| Internal per-endpoint Supervisor relays | Implemented in source: trusted-peer PROXY v2 admission, wake, dial, relay, connection limits, and byte events. |
| Managed per-service TLS delivery | Issuance and delivery implemented/tested for gVisor mounts, Firecracker disk material, and K3s Secrets; real Redis TLS integration coverage. Public deployment must be verified separately. |
| Protocol policy, internal endpoint persistence, edge state/authz/credential APIs | Implemented in source with additive migration 0100_edge_endpoint_access_policy; edge/gateway consumers are implemented; public deployment must be verified separately. |
| Network byte ingestion | Implemented in source; scoped to node/endpoint and deduplicated by node, Supervisor stream, and sequence. Public site usage exposes integer network_bytes totals. |
| Envoy/xDS and MySQL gateway | Implemented and component-tested, including optional real Envoy/libpq and MySQL server/CLI oracles. Envoy is absent from the 3306 path. |
| Public four-port activation | Deployment/drain source and repeatable protocol harnesses exist; this document does not establish an activated public rollout. |
The Supervisor is the authoritative billing meter. Edge counters are for
reconciliation only and must not independently charge usage. Relay source emits
network_bytes with received/sent byte counts. Django ingestion records their
sum as bytes with directional counters and duration in ledger metadata, once
per node/stream/sequence. Its metered event set includes wake_minutes,
requests, storage_gb_hours, and network_bytes. Ingestion is a recorded fact,
not proof of deployment, complete coverage, or a new pricing rule. The public site usage response includes network_bytes as an integer.
This page supersedes the historical dedicated-port and TCP pool designs.