Configuration: forward proxy

This page documents directives for configuring Ferron to act as an HTTP forward proxy, accepting requests from clients and forwarding them to external destinations. It supports both HTTP CONNECT tunneling (for HTTPS/WebSocket) and HTTP/1.x absolute URI forwarding.

forward_proxy

proxy.example.com {
    forward_proxy {
        allow_domains "example.com" "*.example.com"
        allow_ports 80 443
        deny_ips "127.0.0.0/8" "169.254.169.254/32"

        connect_method
        http_version "1.1"
    }
}
Nested directiveArgumentsDescriptionDefault
allow_domains<string>...Allowed destination domains. Supports * wildcards. If empty, all domains are denied (deny-by-default).none (deny all)
allow_ports<int>...Allowed destination ports.80, 443
deny_ips<CIDR>...Denied destination IP ranges, applied after DNS resolution.Loopback, RFC 1918, link-local, cloud metadata (see below)
connect_method<bool> or bareEnable HTTP CONNECT tunneling. When disabled, CONNECT requests are rejected with 403.true
http_version1.0 or 1.1HTTP version used for upstream connections.1.1

Default denied IP ranges

When no deny_ips is specified, the following ranges are denied by default:

RangeDescription
127.0.0.0/8IPv4 loopback
::1/128IPv6 loopback
10.0.0.0/8RFC 1918 private network
172.16.0.0/12RFC 1918 private network
192.168.0.0/16RFC 1918 private network
169.254.0.0/16Link-local
100.64.0.0/10Shared address space (RFC 6598)
192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24Documentation ranges (RFC 5737)
fd00::/8IPv6 unique local addresses
169.254.169.254/32Cloud metadata endpoint

Security model

The forward proxy uses a deny-by-default model:

  1. Domain control: If allow_domains is not set, all destination domains are denied.
  2. Port control: Only explicitly allowed ports are permitted (defaults to 80 and 443).
  3. IP blocking: After DNS resolution, the final IP is checked against the deny list.
Note

DNS resolution happens at connect time — the resolved IP is validated against the deny list to prevent DNS rebinding attacks.

Request handling

CONNECT tunneling

When a client sends an HTTP CONNECT request, Ferron:

  1. Validates the destination against ACLs (domain, port, IP)
  2. Establishes a TCP connection to the target
  3. Returns 200 Connection Established to the client
  4. Bidirectionally forwards raw TCP data between client and target

HTTP forwarding

When a client sends an HTTP request with an absolute URI, Ferron:

  1. Validates the destination against ACLs
  2. Connects to the target host
  3. Rewrites the request URI to path-only form
  4. Forwards the request via HTTP/1.1
  5. Returns the upstream response to the client

Only http scheme is supported. Requests with https scheme are rejected with 400.

Examples

Basic forward proxy

proxy.example.com {
    forward_proxy {
        allow_domains "example.com" "*.example.com" "api.service.internal"
        allow_ports 80 443
    }
}

Forward proxy with explicit IP denylist

proxy.example.com {
    forward_proxy {
        allow_domains "*.corp.example.com"
        allow_ports 80 443 8080
        deny_ips "10.0.0.0/8" "172.16.0.0/12"
    }
}

Forward proxy without CONNECT

proxy.example.com {
    forward_proxy {
        allow_domains example.com
        allow_ports 80
        connect_method false
    }
}
Note

For HTTPS forwarding, clients must use CONNECT tunneling. Direct https:// URLs in HTTP requests are not supported.

Observability

Metrics

MetricTypeAttributesDescription
ferron.forward_proxy.requestsCounterferron.forward_proxy.mode ("connect" or "request"), ferron.forward_proxy.result (outcome), http.response.status_code, error.type (optional)Forward-proxy requests by mode and outcome

Logs

  • ERROR: logged when a CONNECT upgrade fails, no upgrade future is produced, the backend TCP connection fails, the HTTP/1 handshake fails, or the request to the backend fails. The message includes the target address and error details.
  • WARN: logged when a CONNECT or HTTP request is denied by ACL (port or domain), when CONNECT method is disabled, when the request is malformed, when an unsupported scheme is used, when TCP_NODELAY cannot be set, when DNS resolution fails, or when a CONNECT tunnel encounters an error.
  • INFO: logged when a CONNECT tunnel closes normally. The message includes byte counts for each direction.

Structured logs

Description (summary)LevelAttributes
Forward proxy config errorERRORerror.message (string) — configuration error details
Forward proxy CONNECT upgrade failedERRORforward_proxy.target (string) — target address, error.type (string), error.message (string)
Forward proxy connection to target failedERRORforward_proxy.target (string) — target address, error.type (string), error.message (string)
Forward proxy CONNECT tunnel errorWARNforward_proxy.target (string) — target address, error.type (string), error.message (string)
Forward proxy CONNECT tunnel closedINFOforward_proxy.target (string) — target address, forward_proxy.bytes.client_to_backend (int) — bytes sent, forward_proxy.bytes.backend_to_client (int) — bytes received
Forward proxy: upstream connect failedERRORupstream.address (string) — target address, error.type (string), error.message (string)
Forward proxy: HTTP/1 handshake failedERRORerror.type (string), error.message (string)
Forward proxy: request to backend failedERRORerror.type (string), error.message (string)
Forward proxy: port denied by ACLWARNnetwork.destination.port (int) — denied port, error.type (string)
Forward proxy: domain denied by ACLWARNnetwork.destination.name (string) — denied domain, error.type (string)
Forward proxy: DNS resolution failedWARNdns.name (string) — hostname that failed resolution, error.type (string)
Forward proxy: resolved IP deniedWARNdns.name (string) — hostname, error.type (string)
Forward proxy: CONNECT disabledWARNerror.type (string)
Forward proxy: bad CONNECT requestWARNerror.type (string)
Forward proxy: unsupported schemeWARNurl.scheme (string) — the unsupported scheme, error.type (string)
Forward proxy: missing hostWARNerror.type (string)

Best practices

The following best-practice checks are reported by ferron doctor for directives on this page.

  • allow_domains "*" — Allowing proxying to any public domain defeats the purpose of a forward proxy. Restrict to destinations your clients actually need.
  • Custom deny_ips without loopback and metadata ranges — Overriding the default deny list without including 127.0.0.0/8, ::1, and 169.254.169.254/32 can expose internal services through the proxy.