This project is a reverse proxy built using the Echo framework in Golang. It supports modifying request and response headers, removing unwanted headers, and dynamically configuring upstream services via environment variables.
- Proxy multiple upstream services based on the
Hostheader. - Modify request and response headers.
- Remove specific headers from responses.
- Rewrite request paths using regex patterns.
- Conditional proxying based on headers or query parameters.
- Route a single host to multiple upstreams via ordered rules (header, query param, or path prefix).
- Configuration via
jsonfile. - Supports Docker deployment.
You can use this project as a library in your own Go application:
import (
"github.com/roketid/echo-proxy"
)
func main() {
configs := echoproxy.LoadConfig()
port := "8080"
echoproxy.RunProxy(configs, port)
}- Install Go
- Clone this repository:
git clone https://github.com/roketid/echo-proxy.git cd echoproxy - Build and run with config file:
go build -o proxy-server ./cmd/main.go ./proxy-server -config config.json
- Or run with base64-encoded config from environment:
# Encode your config.json as base64 export PROXY_CONFIG=$(base64 -w 0 < config.json) ./proxy-server -config-env PROXY_CONFIG
Create a config.json file with the following example configuration:
{
"example.com": {
"upstream": "https://example.com",
"host_override": "",
"request_headers": {"X-Custom-Header": "MyValue"},
"response_headers": {"X-Response-Header": "ResponseValue"},
"remove_headers": ["Server", "X-Powered-By", "Set-Cookie"],
"condition": {
"header": "X-Api-Key",
"value": "secret-key"
},
"fallback_behavior": "404"
},
"another.com": {
"upstream": "https://another.com",
"host_override": "",
"request_headers": {"X-Another-Header": "AnotherValue"},
"response_headers": {"X-Another-Response": "AnotherResponseValue"},
"remove_headers": ["Server", "Set-Cookie"]
}
}
You can also pass your configuration as a base64-encoded JSON string via an environment variable. This is useful for containerized deployments:
# Encode your config.json as base64
export PROXY_CONFIG=$(base64 -w 0 < config.json)
# Run the proxy with ENV config
./proxy-server -config-env PROXY_CONFIGExample with inline config:
# Create base64 config
CONFIG_JSON='{"example.com": {"upstream": "https://example.com"}}'
export PROXY_CONFIG=$(echo -n "$CONFIG_JSON" | base64 -w 0)
# Run the proxy
./proxy-server -config-env PROXY_CONFIGThis approach is ideal for Kubernetes, Docker, and other containerized environments where you want to pass configuration through environment variables.
You can add a condition object to a host config to only proxy requests that match a specific header or query parameter value. Only equality is supported:
- To match a header:
"condition": { "header": "X-Api-Key", "value": "secret-key" }
- To match a query parameter:
"condition": { "query_param": "token", "value": "mytoken" }
If the condition is not met, the proxy will use the fallback_behavior (e.g., "404", "bad_gateway", or proxy to a fallback upstream if fallback_upstream is set).
A single host key normally maps to one upstream (with an optional condition + fallback_upstream for a two-way choice). If you need to route the same host to more than two upstreams, use routes instead: an ordered list of rules, each with its own condition and upstream. The first matching route wins; a route with no condition is a catch-all and should go last.
{
"api.example.com": {
"routes": [
{ "condition": { "header": "target-app", "value": "b" }, "upstream": "https://service-b.example.com" },
{ "condition": { "query_param": "region", "value": "eu" }, "upstream": "https://eu.example.com" },
{ "condition": { "path_prefix": "/files/" }, "upstream": "https://files.example.com" },
{ "upstream": "https://default.example.com" }
],
"request_headers": {},
"remove_headers": ["Server"]
}
}- Conditions support
header,query_param(matched by equality againstvalue), orpath_prefix(matches when the request path starts with the given prefix). - Each route can set its own
upstream,host_override, andpath_rewrite_regex/path_rewrite_replacement. Everything else (request_headers,response_headers,remove_headers, timeouts) is shared from the host-level config. - If no route matches, the host's
fallback_behavior/fallback_upstreamis used, just like the single-conditioncase (defaulting to502 Bad Gatewayif unset). routestakes precedence over the top-levelupstream/conditionfields when both are present, but existing configs using onlyupstream/conditionkeep working unchanged.
If a request's Host header doesn't match any configured host, the proxy normally responds with 502 Bad Gateway. You can override this by adding a reserved "*" entry to your config — it's used only when nothing else matches:
{
"*": {
"upstream": "https://default-upstream.example.com"
}
}If the "*" entry has an upstream, unmatched requests are reverse-proxied to it just like any other host (headers, timeouts, path rewriting, etc. all apply). If it has no upstream but sets static_index_file, that file is served instead:
{
"*": {
"static_index_file": "./public/index.html"
}
}If neither is set (or no "*" entry exists at all), the proxy falls back to the default 502 Bad Gateway response.
If your config doesn't define a "*" entry, you can set it via environment variables instead — handy when you don't want to edit config.json per environment:
DEFAULT_UPSTREAM: default upstream URL for unmatched hosts.DEFAULT_STATIC_INDEX_FILE: path to a static HTML file to serve for unmatched hosts (used only ifDEFAULT_UPSTREAMis not set).
export DEFAULT_UPSTREAM="https://default-upstream.example.com"
./proxy-server -config config.jsonAn explicit "*" entry in config.json (or PROXY_CONFIG) always takes precedence over these environment variables.
You can rewrite request paths using regex patterns by adding path_rewrite_regex and path_rewrite_replacement to your host config:
{
"api.example.com": {
"upstream": "https://backend.example.com",
"path_rewrite_regex": "^/v1/(.*)",
"path_rewrite_replacement": "/api/$1"
}
}This configuration will rewrite requests from /v1/users to /api/users before forwarding to the upstream server.
Common use cases:
-
Add prefix: Transform
/users→/api/v2/users"path_rewrite_regex": "^/(.*)", "path_rewrite_replacement": "/api/v2/$1"
-
Remove prefix: Transform
/v1/users→/users"path_rewrite_regex": "^/v1/(.*)", "path_rewrite_replacement": "/$1"
-
Replace path segment: Transform
/old/path→/new/path"path_rewrite_regex": "^/old/(.*)", "path_rewrite_replacement": "/new/$1"
You can configure timeout settings for each upstream service (all values in seconds):
{
"api.example.com": {
"upstream": "https://backend.example.com",
"dial_timeout": 30, // Connection establishment timeout
"read_timeout": 30, // Read operation timeout
"write_timeout": 30, // Write operation timeout
"idle_timeout": 90 // Connection idle timeout
}
}Default timeouts are:
dial_timeout: 30 secondsread_timeout: 30 secondswrite_timeout: 30 secondsidle_timeout: 90 seconds
The proxy includes several optimizations for handling high-traffic scenarios:
- Pre-compiled regex patterns: Path rewriting regex patterns are compiled once at startup, not per-request
- Pre-parsed URLs: Upstream URLs are parsed once at initialization
- Connection pooling: Configurable HTTP connection pooling with persistent connections
- Max 100 concurrent connections per host
- Max 100 idle connections
- 30-second keep-alive interval
- Efficient header management: Fast header modification and removal
- Concurrent request handling: Full support for concurrent requests with proper resource management
docker build -f docker/Dockerfile -t roketid/echo-proxy .
docker run -p 8080:8080 -v $(PWD)/config.json:/app/config.json roketid/echo-proxyThe Docker image automatically detects if PROXY_CONFIG environment variable is set and uses it:
# Encode your config to base64
CONFIG_B64=$(base64 -w 0 < config.json)
# Run with base64 config
docker run -p 8080:8080 -e PROXY_CONFIG="$CONFIG_B64" roketid/echo-proxy# With config file
docker run -p 8080:8080 -v $(PWD)/config.json:/app/config.json ghcr.io/roketid/echo-proxy:main
# With base64 config from environment
docker run -p 8080:8080 -e PROXY_CONFIG="$CONFIG_B64" ghcr.io/roketid/echo-proxy:mainThe Dockerfile CMD automatically selects the right approach:
- If
PROXY_CONFIGenvironment variable is set → uses-config-env PROXY_CONFIG - Otherwise → uses config file from
CONFIG_PATH(default:/app/config.json)
This makes it flexible for both traditional volume-mounted configs and containerized deployments using environment variables.
This project includes a GitHub Actions workflow that builds and pushes the Docker image to GitHub Container Registry when changes are pushed to the main branch.
Feel free to submit issues or pull requests for improvements.
MIT License