Skip to content

Reverse proxy

Posthorn does not terminate TLS. It expects a reverse proxy in front of it, handling:

  • TLS termination
  • HTTP/2 (Posthorn speaks HTTP/1.1)
  • Client IP forwarding via X-Forwarded-For
  • Optional request logging

These are sample configs for the most common proxies. Any HTTP reverse proxy works — Posthorn is just an HTTP service on :8080.

example.com {
# Forward only the form endpoints to Posthorn
@forms path /api/contact /api/feedback /api/newsletter
handle @forms {
reverse_proxy posthorn:8080 {
# Trust X-Forwarded-For from upstream (defaults are usually fine)
}
}
# Everything else (the actual site)
handle {
file_server
}
}

If Posthorn is running on the host (not Docker):

reverse_proxy 127.0.0.1:8080

If you want Posthorn’s rate limiter and logs to see the real client IP — not your reverse proxy’s IP — configure trusted_proxies in the endpoint:

[[endpoints]]
path = "/api/contact"
trusted_proxies = ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]
# ...

When a request arrives from a Classless Inter-Domain Routing (CIDR) range listed in trusted_proxies, Posthorn reads the client IP from the rightmost untrusted IP in X-Forwarded-For. Without trusted_proxies configured, Posthorn uses the connection’s RemoteAddr directly.

If your front door is Cloudflare, list Cloudflare’s published IP ranges. If it’s a docker-network internal proxy, list the docker network’s CIDR.

Always force HTTPS at the reverse-proxy layer. Posthorn submissions include form data that may be personal; never accept them over plaintext.

Most modern reverse proxies do this by default (Caddy auto-redirects HTTP→HTTPS; Traefik with entrypoints.web.http.redirections.entrypoint.to=websecure; nginx needs an explicit return 301 https://... block on the :80 server).

Posthorn does not emit CORS headers. For same-origin forms (form on example.com posting to example.com/api/contact), this is fine — no CORS preflight happens.

For cross-origin forms (form on app.example.com posting to api.example.com/contact), set CORS headers at the reverse proxy:

@forms path /api/contact
handle @forms {
header Access-Control-Allow-Origin "https://app.example.com"
header Access-Control-Allow-Methods "POST, OPTIONS"
header Access-Control-Allow-Headers "Content-Type"
reverse_proxy posthorn:8080
}

The allowed_origins setting in Posthorn is not a CORS policy — it’s an Origin/Referer header check applied to the request itself. CORS is browser-side preflight; it must be set by the proxy (or by Posthorn in a future version).