Skip to content
Cloud & Infrastructure
Skill

/portal-relay-diagnose

Find which layer of a self-hosted Portal relay is failing - container and process, open ports, embedded DNS and NS/glue delegation, DNSSEC and the parent DS, TLS and SNI routing, the QUIC UDP backhaul and TCP/UDP lease ports, or approval and source policy - with bounded

From plugin
portal-tunnel
3039 skills

Claude Code

$ npx -y skills add gosuda/portal-tunnel --skill portal-relay-diagnose --agent claude-code

SKILL.md

portal-relay-diagnose.SKILL.md
name: portal-relay-diagnose
description: Find which layer of a self-hosted Portal relay is failing - container and process, open ports, embedded DNS and NS/glue delegation, DNSSEC and the parent DS, TLS and SNI routing, the QUIC UDP backhaul and TCP/UDP lease ports, or approval and source policy - with bounded read-only checks, then report the cause and the fix. Use when a relay operator says the relay, its DNS, its certificates, its tunnels, or its game ports do not work. Do not use when the user is a publisher or visitor of one tunnel (portal-expose or portal-connect), for approving or banning leases (portal-relay-admin), or for first deployment (portal-relay).
license: MIT

Diagnose a Portal relay

Work from the bottom layer up and stop at the first layer that fails: a DNS check means nothing while the container is down. Every check here is read-only and bounded (`curl --max-time 10`, `dig +time=3 +tries=1`, `--tail 200` on logs). Do not restart, recreate, or reconfigure anything during diagnosis; propose the fix and let the operator approve it.

Run checks from two places when possible, because a check on the relay host passes through no cloud firewall:

  • **Host**: a shell on the relay machine.
  • **Outside**: any other network, for example the agent's own environment.

Say which place each result came from.

1. Process and container (host)

  • `docker compose ps portal` is running and not restarting.
  • `docker compose logs portal --tail 200`: look for bind errors (`address already in use` on `443` or `53`), `permission denied` on `IDENTITY_PATH` (the image runs as uid/gid `65532`), embedded DNS start failures, and the `ds_record` line.
  • `sudo ss -tulpn | grep -E ':(443|53) '`: the relay owns `443/tcp`, `53/tcp`, `53/udp`, and `443/udp` when UDP leases are on. Another process on `53` is often `systemd-resolved`.

2. Ports (outside)

  • `curl -fsS --max-time 10 https://<relay>/api/healthz`. A timeout with the container healthy points to a cloud firewall or host firewall, not to Portal.
  • `dig +time=3 +tries=1 @<relay IP> <relay domain> SOA` and the same with `+tcp`: one passing and one failing means only one of `53/udp` and `53/tcp` is open.
  • Compare with the provider's firewall rules. OCI images also reject inbound in host iptables; Lightsail and Hetzner use their own firewalls in front of the host.

3. DNS delegation (outside)

1. `dig @<relay IP> test.<relay domain> A` answers: the embedded DNS works. 2. `dig +short NS <relay domain> @1.1.1.1` returns `ns.<relay domain>`, and `dig +short A ns.<relay domain> @1.1.1.1` (and `AAAA`) returns the relay address: the parent delegation and glue are right. 3. If 1 passes and 2 fails, the problem is at the parent zone: a missing NS, a wrong glue address (common after a public IP changed), a proxied glue record, or propagation still in progress. Read the parent's records with `dig @<parent NS> <relay domain> NS +norec`. 4. An empty AAAA on an IPv4-only relay is expected.

4. DNSSEC (outside)

  • `dig DS <relay domain> @1.1.1.1` compared with the `ds_record` log line: key tag, algorithm `13`, digest type `2`, and digest must match exactly.
  • `dig +dnssec <relay domain> SOA @1.1.1.1`: `SERVFAIL` from a validating resolver with a DS present, while `dig +cd` succeeds, means a DS mismatch or a replaced `dnssec-csk.json`. The fix is to publish the DS from the current key or to restore the old key file, never to delete the key.
  • No DS at the parent means DNSSEC is simply not chained yet; it is not a failure.

5. TLS and SNI (outside)

  • `openssl s_client -connect <relay IP>:443 -servername <relay domain> </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer -dates` for the relay's own name.
  • A tunnel hostname that fails the TLS handshake while the relay name works means no routable lease for that name (offline, pending approval, denied), not a certificate problem. Check it in layer 7.
  • With `ACME_DNS_PROVIDER` set to an external provider, look for provider authentication errors in the logs.

6. Transport (outside, only when enabled)

  • UDP leases need `443/udp` published and open, because the QUIC backhaul uses the public `PORTAL_URL` port.
  • TCP/UDP lease ports: the Docker published range, the host firewall, and the cloud firewall must all cover the same `MIN_PORT`-`MAX_PORT` range. Probe one allocated port only, for example `nc -vz -w 5 <relay IP> <port>`. Never scan the range.
  • Error codes from a publisher: `udp_disabled`, `tcp_port_disabled` (lease type off), `*_capacity_exceeded` (`max_leases` reached), and `*_port_exhausted` (range too small).

7. Policy (needs portal-relay-admin)

  • `lease_rejected`: the lease waits for approval in `manual` mode. `lease_denied`: the identity is denied or banned.
  • `source_not_allowed`: the publisher's address is outside `TUNNEL_SOURCE_CIDRS`. Behind a proxy that also needs `TRUST_PROXY_HEADERS` and `TRUSTED_PROXY_CIDRS`.
  • Read the lease state through `portal-relay-admin`; do not change policy as part of diagnosis.

Report

Give the first failing layer, the evidence (the command, where it ran, and its output), the most likely cause, and the fix as a proposal. List the layers that passed and the checks that could not run (for example no shell on the host). Do not print `ADMIN_TOKEN`, provider tokens, or the contents of `IDENTITY_PATH`.

Read more
@gosuda
Ships withportal-tunnel

Expose local services through self-hosted or public relays.No port forwarding. No inbound firewall rules. No manual DNS setup. No accounts.

Stars on GitHub

Momentum

stars in total
303
Star history of gosuda/portal-tunnel on GitHub, a chart by star-history.com

Other skills on portal-tunnel.

portal-connect

portal-connect

Deprecated alias of the portal-connect skill, which moved to the portal-tunnel plugin. Use when the user asks to reach, call,…

portal-expose

portal-expose

Deprecated alias of the portal-expose skill, which moved to the portal-tunnel plugin. Use when the user asks to expose, preview,…

portal-relay

portal-relay

Deprecated alias of the portal-relay skill, which moved to the portal-relay plugin. Use when the user asks to run, self-host, or…

portal-relay-admin

portal-relay-admin

Operate a running Portal relay through its admin and policy API - sign in with the relay ADMIN_TOKEN without exposing it, list…

portal-relay-aws

portal-relay-aws

Create an AWS host for a Portal relay - an EC2 instance with a security group and an Elastic IP, or a Lightsail instance with its…

More skills from other plugins.

container

container

Use when running, building, or managing Linux containers on macOS, or when a task involves Docker, docker compose, Lima, Colima,…

@apple@appleInstall Skill
agent-advisor

agent-advisor

Entry point for AI-agent work on AWS: pick a runtime, plan a migration for existing workloads, and build an executable POC — one…

@aws@awsInstall Skill
agents-sdk
Auto-invoked

agents-sdk

Build, debug, or review Cloudflare Agents SDK applications using the agents package.

@cloudflare@cloudflareInstall Skill

Questions about portal-relay-diagnose.

Which coding agents can use portal-relay-diagnose?
portal-relay-diagnose installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.