Setting Up Reverse Proxy Routes
You have a service that you want to reach from the internet under your own
domain — e.g. Nextcloud on the NAS as cloud.example.com, or Home
Assistant as home.example.com, or an SSH server on
ssh.example.com:22. All three work through the route wizard, but with
slightly different settings.
This guide shows the three typical scenarios and walks step by step through the 6-stage wizard.
The three scenarios
| Scenario | When? | Target-Kind | Route type |
|---|---|---|---|
| A: Direct to the peer | The target device runs GateControl client + WireGuard | peer |
http or l4 |
| B: Via home gateway into the LAN | Target device has nothing installed (typical for NAS WebUIs, router admin, IoT) | gateway |
http or l4 |
| C: L4 port forward | Any TCP/UDP service, no HTTP (SSH, Minecraft, MQTT) | peer or gateway |
l4 |
In all three cases you work through the same wizard, but at Step 1 (target) and Step 2 (transport) you choose different options.
Preparation: set the DNS record
Regardless of the scenario — before creating the route, the DNS record must be in place.
- In the DNS panel of your domain provider, create an A record:
- Name:
cloud(orhome,ssh, …) - Value: the public IPv4 of your GateControl server
- TTL: 300 (5 min, so you can correct it quickly later)
- Name:
- If the provider offers a proxy mode (Cloudflare):
- For HTTP routes: proxy off. Caddy fetches the cert directly from Let's Encrypt — that won't work with Cloudflare proxy in front, because LE then sees the wrong IP.
- For L4 routes: proxy off. Cloudflare only proxies HTTP/HTTPS.
- Wait for propagation (usually 1–5 min):
must return your server IP.dig +short cloud.example.com
At the end of Step 1 the wizard runs a DNS check
(POST /api/v1/routes/check-dns). That's a warning if it doesn't
match, not a hard stopper. Meaning: you can save even with wrong DNS,
but the cert won't come.
Scenario A: Direct peer (HTTP)
Example: Nextcloud runs on your home NAS, and the NAS has the
GateControl client installed (WG peer, gets the IP 10.8.0.12).
Nextcloud listens on http://10.8.0.12:80 (or another port).
- Routes → + Add → wizard opens.
- Step 1 — Target
- Route type: HTTP
- Domain:
cloud.example.com - Target-Kind: Peer (default)
- Select peer:
home-nas - Target port:
80
- Step 2 — Transport
- Backend HTTPS: off (Nextcloud speaks HTTP internally).
- If the backend is HTTPS (e.g. Synology DSM :5001): tick it + Accept self-signed on.
- Step 3 — Authentication
- None, Basic-Auth, or Route-Auth (email+password with passkey).
- Nextcloud has its own auth → no extra auth needed.
- Step 4 — Access
- IP whitelist/blacklist (CIDR list).
- Peer ACL: only specific peers may access.
- Default: open to all. For an internally accessible Nextcloud that's enough — Nextcloud has its own auth.
- Step 5 — Reliability
- Uptime monitoring, rate limiting, request mirroring, circuit breaker, retry. All optional. For the start: leave off.
- Step 6 — Review
- Go through everything once more, Save.
Caddy loads the new config, registers the Let's Encrypt cert
(HTTP-01 challenge, port 80 must be free), and after ~30 seconds you
open https://cloud.example.com in the browser.
Scenario B: Via home gateway into the LAN (HTTP)
Example: Synology DSM on 192.168.1.10:5001 (HTTPS with
self-signed cert), no GateControl client installed. You have a
home gateway container running, see first-gateway-setup.md.
Prerequisite: the home gateway peer is online, visible under Peers & Clients → Home Gateways.
- Routes → + Add.
- Step 1 — Target
- Route type: HTTP
- Domain:
dsm.example.com - Target-Kind: Gateway
- Select gateway peer:
homeserver-nuc - Target LAN host:
192.168.1.10(the IP/hostname in the LAN of the gateway, not a VPN IP) - Target LAN port:
5001
- Step 2 — Transport
- Backend HTTPS: on (DSM speaks HTTPS).
- Accept self-signed: on (DSM has a self-signed cert in the default config).
- Step 3 — Authentication
- DSM has its own auth → Route-Auth and Basic-Auth not needed. If you want to, put a Basic-Auth in front.
- Step 4 — Access
- IP whitelist is sensible (e.g. only your home IP + office IP). DSM otherwise gets constantly probed by scanners.
- Step 5 — Reliability
- For gateway routes, uptime monitoring can be useful to separate gateway outages from route outages.
- Step 6 — Save.
How it works internally: Caddy sends the request to the WG IP of the
gateway, the gateway container receives the request, reads out the
X-Gateway-Target header and proxies it to 192.168.1.10:5001 in the
LAN. TLS terminates at Caddy on the server, the gateway→LAN hop goes
either HTTP (if Backend HTTPS off) or HTTPS (if on).
Scenario C: L4 port forward
Example: SSH access to a LAN box. Publicly as ssh.example.com:22.
- Routes → + Add.
- Step 1 — Target
- Route type: L4
- Domain:
ssh.example.com(optional — L4 routes without domain also work, but are then only reachable via server-IP:port) - L4 listen port:
22(the port on the GateControl server that the client targets from outside) - L4 protocol: TCP
- Target-Kind:
peerorgateway - For
gateway: Target LAN host192.168.1.20, Target LAN port22
- Step 2 — Transport
- TLS mode (three options):
none— pure TCP/UDP proxy, no cert. Correct for SSH, Minecraft, MQTT-plain, MySQL etc.passthrough— Caddy does not terminate TLS, passes the TLS session straight through to the backend. Only useful if the backend itself has a valid cert, e.g. Nginx in the LAN with its own LE.terminate— Caddy terminates TLS at the server with a Let's Encrypt cert and then speaks plain TCP to the backend. Only for HTTPS-like protocols; pointless for SSH/Minecraft.
- TLS mode (three options):
- Step 3 — Authentication
- For L4 no server-side auth plugin is available (same as with SSH — the auth is done by the backend protocol itself).
- Step 4 — Access
- IP filter urgently recommended for SSH. Peer ACL only applies to clients with a WG tunnel, so it doesn't help with publicly reachable L4 routes.
- Step 5 — Reliability
- L4 has no mirror, no retry, no rate limit — the options are greyed out. Uptime monitoring works (TCP connect check).
- Step 6 — Save.
Test:
ssh -p 22 user@ssh.example.com
Adding auth in front (optional)
If the backend has no auth of its own (e.g. a Grafana without login, an internal wiki) you can put Route-Auth in front:
- Basic-Auth: simple user/password, HTTP auth dialog in the browser.
- Route-Auth (email+password): server shows a GateControl login page, user has to log in with their admin credentials or a separate route user. Passkey flow available.
You'll find both in Step 3 of the wizard. Further reading ../AUTHENTICATION.md.
Troubleshooting
502 Bad Gateway
The most common error page when a fresh route doesn't work.
Check steps:
- Backend reachable? From the GateControl host:
curl -v http://10.8.0.12:80 # Scenario A curl -vk https://192.168.1.10:5001 # Scenario B (test from the gateway!) - For gateway routes: gateway container online? Heartbeat fresh? (See first-gateway-setup.md — Troubleshooting.)
- Firewall on the backend host: many NAS firewalls block foreign
subnets by default. The gateway brings the request from
10.8.0.0/24, that must be allowed. Alternative: add the gateway host to the LAN firewall allowlist. - Port correct? Typos in
target_lan_port/target_portdon't throw 502 at config level directly, but silently on connect failure.
DNS error in the wizard check
The wizard shows "DNS does not point to server IP".
Possible causes:
- Record not yet propagated → wait 5 min, check
dig +short …. - Cloudflare proxy on → proxy off (grey out the orange cloud).
- Wrong A record (CNAME instead of A can work, but must ultimately resolve to your IP).
- Multiple IPs in the A record → only one of them is the server.
The wizard is tolerant here: the warning doesn't block save. But as long as DNS is wrong, no cert will come.
Certificate doesn't come
See the detailed guide in certificates.md. Short:
- Port 80 reachable on the server? (ACME HTTP-01)
- DNS correct? (point 1)
- Let's Encrypt rate limit hit? (50 certs / week / registered domain)
- Caddy logs:
docker logs gatecontrol 2>&1 | grep -i "acme\|tls.obtain"
Route to Synology DSM: "backend broken" but DSM runs
DSM with self-signed cert on :5001: Backend HTTPS on and
Accept self-signed on. Otherwise Caddy fails at the
certificate verify. Not to be confused: self_signed_accept is a
per-route flag, not a global switch.
L4 route with tls_mode=terminate won't get a cert
L4 TLS works differently from HTTP TLS in Caddy — see section "L4 route with TLS terminate" in certificates.md.
Further reading
- ../HTTP-VS-L4-ROUTING.md — deeper distinction HTTP vs. L4.
- ../BACKEND-HTTPS.md — Backend HTTPS subtleties.
- ../AUTHENTICATION.md — Route-Auth, Basic-Auth, Passkey.
- ../IP-ACCESS-CONTROL.md + ../PEER-ACCESS-CONTROL.md
- configuring-rdp.md — specialised RDP wizard.
- certificates.md — debugging TLS issues.