A hardened HTTP/1.1, optional HTTP/2, and WebSocket reverse proxy for Elixir.
Built using Finch (HTTP) and Mint (WebSockets), ReverseIt is designed to work seamlessly within Phoenix/Plug pipelines as a standard Plug module.
Features
- Full HTTP Support: HTTP/1.1 proxying by default, optional HTTP/2 upstreams, and streaming request/response bodies
- Connection Pooling: Uses Finch for automatic connection pooling and reuse across requests
- HTTP/2 Support: Opt-in upstream HTTP/2 support with
protocols: [:http1, :http2] - WebSocket Proxying: Bidirectional WebSocket frame forwarding with full protocol support
- Plug Integration: Works as a standard Plug module in any Phoenix or Plug application
- Header Management: Automatic X-Forwarded-* header injection and hop-by-hop header filtering
- DoS Protection: Configurable request body, header, timeout, response, and WebSocket limits
- Path Manipulation: Strip path prefixes and add backend path prefixes
- Protocol Detection: Automatic detection and routing for HTTP vs WebSocket upgrades
Setup
First, add ReverseIt to your application's supervision tree with a connection pool:
defmodule MyApp.Application do
def start(_type, _args) do
children = [
# Start ReverseIt with a connection pool
{ReverseIt, name: MyApp.ReverseProxy, pool_size: 100},
# ... other children
]
Supervisor.start_link(children, strategy: :one_for_one)
end
endUsage
In a Phoenix Router
defmodule MyAppWeb.Router do
use MyAppWeb, :router
# Regular Phoenix routes
scope "/", MyAppWeb do
get "/", PageController, :index
end
# Proxy API requests to backend service
scope "/api" do
forward "/", ReverseIt,
name: MyApp.ReverseProxy,
backend: "http://backend-api:4000",
strip_path: "/api"
end
# Proxy WebSocket connections
scope "/socket" do
forward "/", ReverseIt,
name: MyApp.ReverseProxy,
backend: "ws://backend-ws:4000"
end
endAs a Plug
defmodule MyApp.ProxyPlug do
use Plug.Router
plug :match
plug :dispatch
forward "/", ReverseIt,
name: MyApp.ReverseProxy,
backend: "http://localhost:4001",
upstream_idle_timeout: 60_000,
protocols: [:http1, :http2]
endConfiguration Options
Supervisor Options (when starting ReverseIt)
:name(required) - Name for the Finch connection pool:pool_size- Max connections per backend (default: 50):pool_count- Number of connection pools (default: 1):connect_timeout- Backend connection timeout in ms (default: 5_000):conn_max_idle_time- Idle timeout for pooled backend HTTP/1 connections (default: 90_000):protocols- Upstream protocols for pooled Finch requests (default: [:http1])
Plug Options (when using as a Plug)
:name(required) - Name of the Finch pool to use:backend(required) - Backend URL (http://, https://, ws://, or wss://):strip_path- Path prefix to strip from incoming requests before proxying:connect_timeout- Backend connection timeout in milliseconds (default: 5_000):pool_timeout- Finch pool checkout timeout in milliseconds (default: 5_000):response_header_timeout- Time to wait for backend response headers in streaming paths (default: 30_000):upstream_idle_timeout- Rolling idle timeout while receiving backend data (default: 55_000):request_body_read_timeout- Rolling timeout while reading client request bodies (default: 55_000):max_request_body_size- Maximum request body size in bytes (default: 100MB,:infinityfor unlimited):request_body_buffer_size- Bytes buffered before switching to request streaming (default: 1MB):max_response_body_size- Maximum response body size in bytes (default: :infinity):max_request_target_bytes- Maximum request path/query bytes (default: 8KB):max_request_header_line_bytes- Maximum single request header bytes (default: 8KB):max_request_header_bytes- Maximum total request header bytes (default: 64KB):max_request_headers- Maximum request header count (default: 100):max_response_header_bytes- Maximum backend response header bytes (default: 64KB):forwarded_headers-:append,:replace, orfalsefor X-Forwarded-* behavior (default: :append):protocols- List of supported upstream protocols (default: [:http1]):max_websocket_frame_size- Maximum WebSocket frame/message size (default: 16MB)
Connection Pooling
ReverseIt uses Finch for HTTP requests, which provides automatic connection pooling:
- Pool Size: 50 connections per backend by default
- Reuse: Connections are automatically reused across requests
- HTTP/2 Support: Upstream HTTP/2 can be enabled with
protocols: [:http1, :http2] - Performance: Eliminates TCP/TLS handshake overhead for subsequent requests
You configure the pool when adding ReverseIt to your supervisor tree.
Examples
Basic HTTP Proxying
# Proxy all requests to a backend server
forward "/", ReverseIt,
name: MyApp.ReverseProxy,
backend: "http://localhost:4001"Path Stripping
# Strip /api prefix before forwarding
# Request to /api/users becomes /users at backend
forward "/api", ReverseIt,
name: MyApp.ReverseProxy,
backend: "http://api-server:4000",
strip_path: "/api"WebSocket Proxying
# Proxy WebSocket connections
forward "/ws", ReverseIt,
name: MyApp.ReverseProxy,
backend: "ws://websocket-server:4000"Custom Timeouts and Protocols
# Configure timeouts and HTTP protocols
forward "/", ReverseIt,
name: MyApp.ReverseProxy,
backend: "https://backend:443",
upstream_idle_timeout: 60_000,
protocols: [:http1, :http2]Customizing Requests and Responses
You can wrap ReverseIt in your own Plug to modify request/response headers,
add authentication, logging, etc. Use Plug.Conn.register_before_send/2 to
modify responses before they're sent to the client.
defmodule MyApp.APIProxy do
@behaviour Plug
def init(opts), do: opts
def call(conn, _opts) do
# Modify request before proxying
conn
|> Plug.Conn.put_req_header("x-api-key", "...")
# Register callback to modify response after backend responds
|> Plug.Conn.register_before_send(fn conn ->
conn
|> Plug.Conn.put_resp_header("x-proxy-by", "MyApp")
|> Plug.Conn.put_resp_header("x-proxy-version", "1.0")
|> log_request()
end)
# Proxy to backend
|> ReverseIt.call(
ReverseIt.init(
name: MyApp.ReverseProxy,
backend: "http://backend-api:4000",
strip_path: "/api"
)
)
end
defp log_request(conn) do
Logger.info("Proxied #{conn.method} #{conn.request_path} → #{conn.status}")
conn
end
end
# In your router:
scope "/api" do
forward "/", MyApp.APIProxy
end
Summary
Functions
Child spec for starting ReverseIt with a Finch connection pool.
Functions
Child spec for starting ReverseIt with a Finch connection pool.
Add this to your application's supervision tree:
children = [
{ReverseIt, name: MyApp.ReverseProxy, pool_size: 100}
]Options
:name(required) - Name for the Finch pool:pool_size- Max connections per backend (default: 50):pool_count- Number of connection pools (default: 1):connect_timeout- Backend connection timeout in ms (default: 5_000):conn_max_idle_time- Idle timeout for pooled backend HTTP/1 connections (default: 90_000):protocols- Upstream protocols for pooled Finch requests (default: [:http1])