Tutorial

Remote Access Without Port Forwarding:
Complete 2026 Guide

Almost every machine you might want to reach — a home server, a cloud VM, a box on someone else’s corporate LAN — sits behind NAT. It has no public address, no listening port you can hit, and frequently a firewall that permits nothing except outbound HTTPS. Reaching one of these boxes without reconfiguring the router, standing up a VPN, or buying a static IP is a problem most operators bump into eventually.

The clean answer is GG-Socket: a relay you run yourself that lets both endpoints connect outward, so no inbound port has to be open on either machine you care about.

Why Reaching Inward Fails

Conventional SSH assumes the target owns a routable IP and exposes port 22. That assumption collapses across the common cases:

  • Carrier-grade NAT — providers pile thousands of subscribers behind one public address. You cannot forward a port because the address is not uniquely yours to begin with.
  • Office and guest networks — inbound is blocked at the edge; only outbound 443 and DNS are let through.
  • Rotating addresses — even a genuinely public IP wanders. Dynamic DNS papers over it but introduces its own failure modes.
  • Private cloud topologies — many deployments front instances behind balancers and never expose them directly at all.

The fix is not to punch another hole inbound. It is to reverse the direction: have the box dial out to a rendezvous point, and dial that same point yourself from wherever you are.

How GG-Socket Realizes That Pattern

GG-Socket is the rendezvous point. It is a small Go process with a simple job:

  1. It accepts TCP connections over TLS on port 443.
  2. From each connection it reads a secret key and the peer’s role (target or client).
  3. It parks the first peer to arrive for a given secret, and when a second peer shows up with the same secret and the opposite role, it pairs them and splices their streams.

Both halves are outbound, so neither endpoint needs a public address or an open port. The relay terminates TLS and forwards encrypted application bytes blindly — it never interprets the traffic it carries.

What You Need

  • One VPS with a public address (any inexpensive plan is plenty).
  • Your domain pointed at it.
  • Docker and Docker Compose on that VPS.
  • Outbound HTTPS reachability on the two machines you want to connect.

The one-time relay deploy is covered in the Quick Start. After that, every new tunnel is just two peers dialling the same secret.

Step 1: Stand Up the Relay

On the VPS, bring up the stack:

git clone <repo> gg_socket && cd gg_socket
cp notify.env.example notify.env   # edit secrets
docker compose up -d --build

The relay publishes TLS on :443 and exposes health and metrics on the loopback. Confirm it is listening:

curl -s http://127.0.0.1:9001/health
# {"status":"ok","active_connections":0,...}

That address — r.ggsocket.com:443 — is the rendezvous point both peers will dial.

Step 2: Dial In From Both Ends

Agree on a secret, then have each peer connect outward with it. The exact command depends on the client you use; with a generic secret-key client it looks like:

# on the box you want to reach
gs-target  GGK-M1SDZI-MP4V7P-6JCV4T-G9O5AV  -r r.ggsocket.com:443

# from your laptop
gs-client  GGK-M1SDZI-MP4V7P-6JCV4T-G9O5AV  -r r.ggsocket.com:443

The relay sees two connections presenting the same secret, replies matched to each, and begins copying bytes in both directions. Close either side and the session ends.

Both legs are outbound TCP. Neither peer holds a public address or an open port. The only host that must be reachable from the internet is the relay itself.

Step 3: Watch It Work

The relay reports live counters, which is the easiest way to confirm a session formed:

curl -s http://127.0.0.1:9001/metrics | grep -E 'active|sessions|bytes'
# goodsocket_active_connections 2
# goodsocket_sessions_total 1
# goodsocket_bytes_relayed_total 48211

Active connections climbs by two on a match, sessions by one, and bytes as traffic flows. If the numbers look right, the tunnel is up.

Tuning the Limits

Two knobs govern how the relay treats incoming traffic, both set through environment variables in the compose file:

  • GGSOCKET_RELAY_RATE_LIMIT — new connections allowed per source IP per 60-second window (default 20).
  • GGSOCKET_RELAY_MAX_CONNS — the overall concurrent-connection ceiling enforced by a semaphore (default 10,000).

Raise them for busy deployments; leave them be for a personal relay.

When Something Does Not Connect

A peer sits on “waiting” and never pairs

The other peer is not dialling, or is dialling a different secret. Re-check that both sides use the identical key and point at r.ggsocket.com:443. The waiting peer gives up after GGSOCKET_RELAY_WAIT_TTL seconds (default 60).

Connections rejected as rate-limited

A single source IP opened more than the allowed connections in a minute. It clears by itself once the 60-second window rolls over; if it keeps happening, raise GGSOCKET_RELAY_RATE_LIMIT.

Connection refused on 443

The relay container is not running or a firewall is in the way. Check docker compose ps and confirm the host allows inbound 443.

Run your own rendezvous point.

One VPS. One secret. No port forwarding anywhere else.

docker compose up -d --build
Full Quick Start →

Frequently Asked Questions

No. Both peers dial out to the relay over TLS on 443. The relay matches them by secret key and bridges their streams, so neither endpoint has to accept inbound traffic. The only host that needs a public address is the relay itself.
Not to the peers. Because every connection is outbound, the NAT type on either end is irrelevant — even if you share a public address with thousands of others. The relay identifies peers by the secret key they present, not by their source address.
The link to the relay is TLS by default. The relay terminates that TLS to read the secret key for matching, then forwards the application bytes between peers without interpreting them. Anything the peers want kept private, they should encrypt end-to-end themselves.
The relay pairs peers one match at a time per secret: the first waiting peer is handed the next opposite-role peer to arrive. For concurrent independent tunnels, give each its own secret key. Overall concurrency is bounded by the capacity semaphore (GGSOCKET_RELAY_MAX_CONNS, default 10,000).
The session drops when either side’s connection breaks. The peers simply redial the relay with the same secret; once both are present again, a fresh session is spliced. Nothing about the secret changes across the disruption.