Skip to content

Ports and access ​

The short rule: nkt is never exposed to the internet. The host and hub UI listens on loopback; the hub reaches the hosts over SSH on its own; everything else is internal ports that need not be opened to the internet.

Summary ​

PortListens onWho connectsExpose?
8077/tcphost and hub, 127.0.0.1your browser (tunnel or proxy); the hub — over SSHno, only via an SSH tunnel or a TLS proxy
8443/tcphub in Docker (published to 8077)your browseronly behind a TLS proxy
8446/tcphost and hub, the host part of NKT_ADDRyour browser — Kubernetes port forwardssame as the UI
22/tcpmanaged hoststhe hubfrom the hub's address
8078/tcpmanaged hosts (fallback channel)the hubfrom the hub's address
3142/tcpmanaged hosts, 127.0.0.1apt and containerd on the host (reverse SSH forward from the hub)no
443/tcp, 80/tcpthe VPS with nkt-edgeby edge role: GitHub, GitLab, CI (webhooks), n8n and scripts (API), Slack (callbacks); 80 — certbotyes — that's their purpose
8444/tcpthe VPS with nkt-edgethe hub (tunnel)from the hub's address; may be open to all — login by token
51820/udpcluster hosts on WireGuardneighboring cluster hostsbetween the cluster hosts
6443, 80, 443a host with a cluster (DNAT)cluster clientsoptional, "expose"

8077: UI and API ​

Both nkt and the hub listen on 127.0.0.1:8077 by default (NKT_ADDR), and the session cookie is HTTPS-only (NKT_COOKIE_SECURE=true). Ways to reach it:

  • SSH tunnel — ssh -L 8077:127.0.0.1:8077 -L 8446:127.0.0.1:8446 user@host and http://127.0.0.1:8077; NKT_COOKIE_SECURE=false in the env file. The second -L is only needed for Kubernetes port forwards.
  • nkt's own HTTPS — NKT_TLS_ENABLED=true (self-signed or your certificate) and NKT_ADDR=0.0.0.0:8077 if you open it on a LAN.
  • A reverse proxy with TLS — nginx or Caddy in front of 127.0.0.1:8077.

On a managed host the nkt API listens on loopback only; the hub reaches it through an SSH tunnel. The port is the hub's NKT_HUB_HOST_API_PORT (8077 by default) or the host's own "API port" field if 8077 is taken there.

DANGER

Access to nkt equals root on the host; access to the hub equals root on every host. Don't expose a TLS proxy to the internet without separate authentication (VPN, SSO, an address allow-list).

An nginx example (UI and API, including WebSocket):

nginx
server {
    listen 443 ssl;
    server_name nkt.example.com;
    ssl_certificate     /etc/letsencrypt/live/nkt.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/nkt.example.com/privkey.pem;

    # allow 203.0.113.0/24; deny all;   # restrict who may connect

    location / {
        proxy_pass http://127.0.0.1:8077;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_read_timeout 1h;
        client_max_body_size 0;      # image and file uploads
    }
}

nkt accepts state-changing requests only from its own origin (Sec-Fetch-Site/Origin headers vs Host), so the proxy must pass the original Host. Other allowed origins — NKT_CORS_ORIGINS.

8446: Kubernetes port forwards ​

"Open in browser" on a pod or service runs kubectl port-forward and opens the application on a separate port — it has its own origin, and the pod's application doesn't see nkt's cookies. The address is NKT_FORWARD_ADDR, by default the same host as in NKT_ADDR and port 8446; TLS is the same as the UI's.

  • Through an SSH tunnel, forward 8446 too (see above).
  • Behind a proxy, set up a separate name or port for forwards and tell nkt how the browser sees them: NKT_FORWARD_PUBLIC_URL=https://fwd.example.com.
  • NKT_FORWARD_ADDR=off turns the separate port off: forwards go through the UI's path in a sandbox (Content-Security-Policy: sandbox). Simple pages work, applications relying on JavaScript and cookies — not always.

In Docker Compose and the Kubernetes manifest port 8446 is already published.

Hub → hosts: 22 and 8078 ​

The hub connects to the hosts itself — the hub needs no incoming connections and no public address.

  • SSH (22) — installation, updates, the tunnel to the host API. On the host it's enough to allow SSH from the hub's address.
  • Fallback channel (8078) — for when SSH is unavailable (sshd down, stale credentials): the hub dials this port at the same address as SSH; TLS with a pinned fingerprint and a per-host token. The port is NKT_HUB_TUNNEL_PORT. If closed, the host's "Channel" column is gray. A firewall that closes all incoming ports closes this one too.

Package cache: 3142 ​

The hub keeps a reverse SSH forward to the host: on the host 127.0.0.1:3142 (NKT_HUB_APTCACHE_PORT) leads to the hub's caching proxy. The port lives only on the host's loopback, nothing has to be opened. See Package cache.

A way in for a hub behind NAT: webhooks, API, bots ​

What reaches the hub from outside, and how to do without exposing it:

WhatPath on the hubWithout exposing the hub
a pipeline webhookPOST /api/hub/hooks/{id}repository or registry polling; an nkt-edge with the "webhooks" role
API tokens (n8n, CI, scripts)/api/hub/…, /api/hosts/…an nkt-edge with the "API" role, signed requests only
Slack commands and buttonsPOST /api/hub/callbacks/slack/…an nkt-edge with the "callbacks" role
the Telegram bot, outgoing webhooks, Slack alerts—no way in needed: the hub reaches out itself (443 to Telegram, Slack, recipients)
outside checks of sites—an nkt-edge with the "outside checks" role: the hub asks the edge through the tunnel

The edge accepts its roles on 443 and passes them to the hub over a tunnel the hub keeps itself (8444 on the VPS).

If the hub is already behind a proxy and you want to take webhooks directly, expose only these paths (/api/hub/callbacks/ if you need Slack):

nginx
location /api/hub/hooks/ {
    proxy_pass http://127.0.0.1:8077;
    proxy_set_header Host $host;
    client_max_body_size 1m;
}
location /api/hub/callbacks/ {
    proxy_pass http://127.0.0.1:8077;
    proxy_set_header Host $host;
    client_max_body_size 1m;
}
location / { deny all; }    # the rest — only from a VPN or an allow-list

The hub rejects a webhook without a valid signature (401), and a replay of the same delivery too.

Clusters ​

  • WireGuard between cluster hosts — UDP 51820 between them.
  • Expose — DNAT from host ports (6443, 80, 443 by default, any can be changed or cleared) to the control plane. Without it the cluster is reachable only from the host's network.