CallMeTechie
DE Login
Home Products Blog About Contact

Routes & HTTPS

v1.x · Updated 3 weeks ago

HTTP vs. L4 Routing

GateControl supports two fundamentally different routing types:

  • HTTP Routing (Layer 7): Caddy analyzes the HTTP request, matches by domain name, and forwards to the backend. Full access to all HTTP features.
  • L4 Routing (Layer 4): Caddy forwards raw TCP/UDP traffic without inspecting the content. Matching by port, not by domain.

HTTP Routing

Client  →  https://app.example.com  →  Caddy (matches domain "app.example.com")  →  Backend 10.8.0.3:8080
Client  →  https://api.example.com  →  Caddy (matches domain "api.example.com")  →  Backend 10.8.0.4:3000
                                         ↑ Both on port 443, distinguished by domain

L4 Routing

Client  →  server.example.com:25565  →  Caddy (matches port 25565)  →  Backend 10.8.0.5:25565
Client  →  server.example.com:2222   →  Caddy (matches port 2222)   →  Backend 10.8.0.6:22
                                         ↑ Different ports, domain doesn't matter

HTTP Routing (Layer 7) in Detail

HTTP routing is the default mode. Caddy terminates TLS, reads HTTP headers, and matches based on the Host header.

How it works:

  1. Client connects to port 443 (or 80)
  2. TLS handshake — Caddy selects the correct certificate via SNI
  3. Caddy reads the HTTP Host header
  4. Match on configured domain → forward to backend
  5. Full HTTP processing: header manipulation, compression, auth, etc.

All route features are available:

  • Force HTTPS with Let's Encrypt
  • Backend HTTPS
  • Compression (Gzip/Zstd)
  • Rate Limiting
  • Basic Auth / Route Auth
  • Peer ACL
  • IP Access Control
  • Request Mirroring
  • Retry on Error
  • Circuit Breaker
  • Custom Headers
  • Load Balancing (multiple backends)
  • Uptime Monitoring (HTTP Check)
  • Sticky Sessions

L4 Routing (Layer 4) in Detail

L4 routing forwards raw TCP or UDP traffic. Caddy opens a dedicated port and tunnels traffic to the backend.

Protocol: TCP or UDP

ProtocolUse Cases
TCPSSH, Minecraft, SMTP, databases, most services
UDPDNS, game servers (some), VoIP, WireGuard

Listen Port

The port Caddy opens on the GateControl server. This is the port clients connect to.

  • Not the target port (the port on the backend)
  • Can be the same or different from the target port
  • Must be available on the GateControl server

TLS Mode

ModeDescriptionCaddy Behavior
NoneNo TLSCaddy forwards raw TCP/UDP traffic
PassthroughTLS pass-throughCaddy matches via SNI, forwards encrypted traffic without decrypting
TerminateTLS terminationCaddy decrypts TLS (with LE certificate), then forwards unencrypted TCP to backend

Target Port vs. Listen Port

FieldApplies toDescription
Target PortAll routes (HTTP + L4)The port on the backend peer where the service runs
Listen PortL4 routes onlyThe port Caddy opens on the GateControl server

Example 1: Same ports

Listen Port 25565 (GateControl)  →  Target Port 25565 (Minecraft on Peer 10.8.0.4)
Client connects to: server.example.com:25565

Example 2: Different ports

Listen Port 8022 (GateControl)  →  Target Port 22 (SSH on Peer 10.8.0.2)
Client connects to: server.example.com:8022
SSH command: ssh -p 8022 user@server.example.com

Example 3: Multiple services, different ports

Listen Port 25565  →  Target Port 25565 (Minecraft on 10.8.0.4)
Listen Port 2222   →  Target Port 22 (SSH on 10.8.0.2)
Listen Port 5433   →  Target Port 5432 (PostgreSQL on 10.8.0.3)

HTTP routes don't have a listen port — all HTTP routes share ports 80/443 and are distinguished by domain.

TLS Modes in Detail

None — No TLS

Client  ──TCP/UDP──→  Caddy:25565  ──TCP/UDP──→  Backend:25565
         unencrypted                unencrypted
  • No TLS handshake, no SNI
  • Simplest setup, no certificate needed
  • Use cases: Minecraft, game servers, DNS, plain SMTP, databases in VPN

Passthrough — TLS Pass-through

Client  ──TLS──→  Caddy:443  ──TLS──→  Backend:443
         encrypted            encrypted (same connection)
  • Caddy only reads the SNI (Server Name) from the TLS ClientHello
  • The TLS tunnel is not broken — end-to-end encryption
  • The backend must have its own valid certificate
  • Use cases: Backend with its own LE certificate, strict E2E encryption requirements

Terminate — TLS Termination

Client  ──TLS──→  Caddy:993  ──TCP──→  Backend:143
         encrypted             unencrypted
         (Let's Encrypt)
  • Caddy terminates TLS with a Let's Encrypt certificate
  • Traffic to the backend is unencrypted (but within the VPN)
  • Use cases: TLS for services that don't natively support it, IMAPS/SMTPS in front of plaintext backend

Blocked Ports

PortUsage
80Caddy HTTP (ACME Challenge + Redirect)
443Caddy HTTPS (HTTP routes)
2019Caddy Admin API
3000GateControl Web UI
51820WireGuard VPN

Use Cases

Minecraft Server (TCP, Port 25565, TLS: None)

Player connects to: mc.example.com:25565
L4 Route: Listen Port 25565 → Peer "Gaming Server" Target Port 25565
Protocol: TCP, TLS: None

SSH Access (TCP, Port 2222 → 22, TLS: None)

ssh -p 2222 admin@server.example.com
L4 Route: Listen Port 2222 → Peer "Home Server" Target Port 22
Protocol: TCP, TLS: None

Port 22 is not used as listen port to avoid conflicts with the GateControl server's SSH.

Database (TCP, Port 5433 → 5432, TLS: None)

psql -h server.example.com -p 5433 -U myuser mydb
L4 Route: Listen Port 5433 → Peer "DB Server" Target Port 5432
Protocol: TCP, TLS: None

Game Server (UDP)

Player connects to: game.example.com:27015
L4 Route: Listen Port 27015 → Peer "Game Server" Target Port 27015
Protocol: UDP, TLS: None

Feature Comparison: HTTP vs. L4

FeatureHTTP (Layer 7)L4 (Layer 4)
Routing methodDomain-basedPort-based
HTTPS / Let's EncryptYesOnly with TLS Terminate
Compression (Gzip/Zstd)YesNo
Rate LimitingYesNo
Custom HeadersYesNo
Basic AuthYesNo
Route AuthYesNo
Peer ACLYesNo
IP Access ControlYesNo
Request MirroringYesNo
Retry on ErrorYesNo
Circuit BreakerYesNo
Uptime MonitoringHTTP CheckTCP Check
Multiple BackendsYes (Load Balancing)No
Sticky SessionsYesNo
WebSocketYes (automatic)Yes (as TCP)
ProtocolHTTP/HTTPSTCP / UDP

WebSocket with HTTP Routes: WebSocket connections start as a regular HTTP request with a special Connection: Upgrade header. Caddy automatically detects this header and switches the connection to a persistent WebSocket connection. No additional configuration is needed — this works out-of-the-box with every HTTP route.

WebSocket with L4 Routes: Since L4 forwards the raw TCP stream without inspecting its content, WebSocket also works here — Caddy only sees TCP packets and forwards them 1:1.

Setup

Create HTTP Route (UI)

  1. Route Type: HTTP (default)
  2. Enter domain (e.g. app.example.com)
  3. Select target peer
  4. Enter target port (e.g. 8080)
  5. Configure features (HTTPS, Auth, etc.)
  6. Save

Create L4 Route (UI)

  1. Route Type: Switch to L4
  2. Enter domain (required for TLS Passthrough/Terminate, optional for TLS None)
  3. Select target peer
  4. Target Port enter (port on the backend)
  5. Protocol: Select TCP or UDP
  6. Listen Port enter (port on the GateControl server)
  7. TLS Mode select (None, Passthrough, Terminate)
  8. Save

Via API

# Create HTTP route
curl -X POST https://gatecontrol.example.com/api/v1/routes \
  -H "Authorization: Bearer gc_..." \
  -H "Content-Type: application/json" \
  -d '{"route_type":"http","domain":"app.example.com","peer_id":1,"target_port":8080,"https_enabled":true}'

# Create L4 route (Minecraft)
curl -X POST https://gatecontrol.example.com/api/v1/routes \
  -H "Authorization: Bearer gc_..." \
  -H "Content-Type: application/json" \
  -d '{"route_type":"l4","domain":"mc.example.com","peer_id":2,"target_port":25565,"l4_protocol":"tcp","l4_listen_port":"25565","l4_tls_mode":"none"}'

Important Notes

  • L4 routes use exclusive ports. Each L4 route (without TLS) needs its own listen port.
  • Multiple L4 routes with TLS (Passthrough or Terminate) can share the same port — Caddy distinguishes them via SNI.
  • Port ranges are possible (e.g. 25565-25575 for multiple Minecraft servers).
  • UDP routes don't support TLS (TLS runs over TCP).
  • L4 routes have no HTTP features.

Force HTTPS

Enables automatic TLS encryption with Let's Encrypt certificates — HTTP requests are redirected to HTTPS via 301, certificates are automatically renewed.

How does it work?

Without Force HTTPS:

Client  →  http://app.example.com:80   →  Caddy  →  Backend
             ↑ unencrypted, data in plaintext

With Force HTTPS:

Client  →  http://app.example.com:80   →  301 Redirect → https://...
Client  →  https://app.example.com:443 →  Caddy (TLS)  →  Backend
             ↑ encrypted with Let's Encrypt certificate

When https_enabled is active:

  1. Listener: Caddy listens on port :443 instead of :80
  2. TLS Certificate: Caddy uses the ACME HTTP-01 challenge
  3. HTTP → HTTPS Redirect: Caddy automatically redirects all HTTP requests via 301 to HTTPS
  4. Auto-Renewal: Caddy renews certificates automatically (30 days before expiry)

TLS Configuration (if email is set):

{
  "apps": {
    "tls": {
      "automation": {
        "policies": [{
          "issuers": [{
            "module": "acme",
            "email": "admin@example.com"
          }]
        }]
      }
    }
  }
}

Custom ACME CA: Use the GC_CADDY_ACME_CA environment variable to configure an alternative ACME CA.

Configure ACME Email

# In docker-compose.yml or .env
GC_CADDY_EMAIL=admin@example.com

# Optional alternative ACME CA
GC_CADDY_ACME_CA=https://acme-staging-v02.api.letsencrypt.org/directory

Troubleshooting

ProblemCauseSolution
Certificate not issuedDNS not pointing to serverCheck A record, use dig or nslookup
ACME challenge failedPort 80 blockedCheck firewall/router, open port 80
Too many certificatesLet's Encrypt rate limitWait 1 hour, then retry
Certificate expiredCaddy could not renewCheck Caddy logs, verify DNS and port 80

Important Notes on Force HTTPS

  • DNS must point correctly. The domain must point to the GateControl server's public IP via A/AAAA record.
  • Ports 80 and 443 must be open.
  • No Cloudflare Proxy. Use DNS Only (grey cloud).
  • Mind rate limits. Max 50 certificates per registered domain per week.
  • Certificates are stored in /data/caddy/ and survive container restarts.

Backend HTTPS

Connects Caddy to the backend via HTTPS — for services that use self-signed certificates and enforce HTTPS (e.g. Synology DSM, Proxmox, UniFi Controller).

Without Backend HTTPS (backend enforces HTTPS):

Client  →  Caddy  →  http://10.8.0.3:5001  →  Backend rejects HTTP  ✕

With Backend HTTPS:

Client  →  Caddy (Let's Encrypt)  →  https://10.8.0.3:5001  →  Backend (Self-Signed)  ✓
           ↑ valid certificate        ↑ insecure_skip_verify: true

Technical Details

{
  "handler": "reverse_proxy",
  "upstreams": [{ "dial": "10.8.0.3:5001" }],
  "transport": {
    "protocol": "http",
    "tls": { "insecure_skip_verify": true }
  }
}

Typical Use Cases

ServicePortDescription
Synology DSM5001NAS Web UI with self-signed certificate
Proxmox VE8006Hypervisor Web UI
UniFi Controller8443Network management
Portainer9443Docker management UI

Important Notes on Backend HTTPS

  • Only enable when the backend enforces HTTPS. If HTTP also works, it's unnecessary.
  • insecure_skip_verify trusts any certificate — acceptable in VPN context.
  • Backend HTTPS only affects the Caddy → backend connection.
  • With load balancing: all backends must support HTTPS.
  • Only available for HTTP routes, not L4.

Compression

Compresses HTTP responses with Gzip and Zstd — reduces transferred data by 60-80% for text content.

Without Compression:

Client  ←  500 KB HTML  ←  Caddy  ←  500 KB HTML  ←  Backend

With Compression:

Client  ←  120 KB gzip  ←  Caddy (compresses)  ←  500 KB HTML  ←  Backend
           76% saved

Algorithms

AlgorithmBrowser SupportCompressionSpeed
ZstdChrome 123+, Firefox 112+BetterFaster
GzipAll browsersGoodStandard

Caddy selects Zstd when the client supports it, otherwise Gzip.

Typical Savings

Content-TypeUncompressedGzipZstdSavings
HTML100 KB25 KB20 KB75-80%
CSS200 KB35 KB28 KB82-86%
JavaScript500 KB120 KB95 KB76-81%
JSON1 MB150 KB110 KB85-89%
PNG (image)300 KB295 KB295 KB~2%

Caddy JSON Configuration

{
  "handler": "encode",
  "encodings": {
    "zstd": {},
    "gzip": {}
  }
}

Important Notes on Compression

  • Not recommended for already compressed content. Images (JPEG, PNG, WebP), videos, archives, and fonts (WOFF2) are already compressed.
  • Caddy only compresses when the client sends Accept-Encoding.
  • If the backend already serves compressed responses (Content-Encoding: gzip), Caddy does not double-compress.
  • Only available for HTTP routes, not L4.

Cookie Settings

We use cookies to improve your experience. Essential cookies are always active.

Privacy Policy
ESC
↑↓ navigate open esc close