Skip to content

Behind a reverse proxy

The most common production topology runs h2corn behind a reverse proxy that handles browser-facing TLS:

browser/client  →  reverse proxy (TLS edge)  →  h2corn (h2c)

The proxy takes care of ALPN negotiation, TLS termination, and public-edge hardening. h2corn runs the application side of the connection on h2c — cleartext HTTP/2 over TCP or a Unix socket inside the trust boundary.

When a separate proxy isn't a good fit for your environment, h2corn can terminate TLS itself instead — see Direct TLS.

Why h2c upstream?

Keeping the proxy → app hop on HTTP/2 avoids the HTTP/1.1 downgrade surface that request smuggling and HTTP/2 downgrading research repeatedly targets. If your proxy can speak h2c upstream, prefer that over an HTTP/1.1 fallback.

Proxy headers and PROXY protocol

h2corn accepts two kinds of trust hop metadata, both opt-in and gated by --forwarded-allow-ips:

  • --proxy-headers trusts standard Forwarded and X-Forwarded-* headers from peers in --forwarded-allow-ips. These carry request metadata such as scheme, host, and the original client address.
  • --proxy-protocol v1|v2 parses HAProxy's PROXY protocol on inbound connections. It carries transport-level peer information on the connection itself, useful when you want the original source IP for connection-level metrics or per-IP limits.

In most deployments, proxy headers alone are enough. Add PROXY protocol when the upstream is explicitly configured to send it.

Caddy

Caddy speaks h2c upstream natively with its reverse_proxy directive.

Caddyfile
example.com {
    reverse_proxy h2c://127.0.0.1:8000
}

Pair it with:

h2corn hello:app \
  --bind 127.0.0.1:8000 \
  --proxy-headers \
  --forwarded-allow-ips 127.0.0.1,::1,unix \
  --no-http1

HAProxy

HAProxy speaks HTTP/2 upstream with proto h2 and can layer PROXY protocol v2 on the same connection — see the HAProxy HTTP guide for the full directive set.

haproxy.cfg
global
    log stdout format raw daemon

defaults
    mode http
    timeout connect 5s
    timeout client 30s
    timeout server 30s

frontend public_https
    bind :443 ssl crt /etc/haproxy/certs/example.pem alpn h2,http/1.1
    default_backend h2corn_backend

backend h2corn_backend
    server app1 127.0.0.1:8000 check proto h2 send-proxy-v2

Pair it with:

h2corn hello:app \
  --bind 127.0.0.1:8000 \
  --proxy-protocol v2 \
  --proxy-headers \
  --forwarded-allow-ips 127.0.0.1,::1,unix \
  --no-http1

Other proxies

h2corn works with any reverse proxy that speaks h2c upstream — Caddy and HAProxy are simply the two that do it cleanly today. If you're evaluating an alternative that cannot, pick one that can rather than falling back to HTTP/1.1 between the proxy and the application.