Description
When building a proxy server using Echo (golang) and enabling the built-in middleware.CORS() on each proxy layer, CORS headers are duplicated in the final response.
This happens in a common microservice / gateway setup where:
- multiple Echo-based services act as proxies,
- each service enables CORS middleware,
- requests pass through several proxy layers before reaching the client.
As a result, the response contains duplicated CORS headers such as:
Access-Control-Allow-Origin: *
Access-Control-Allow-Origin: *
Vary: Origin
Vary: Origin
This behavior breaks some browsers and violates expected HTTP header semantics.
Steps to Reproduce
-
Create Service A (proxy) with Echo:
func main() {
e := echo.New()
e.Use(middleware.Recover())
e.Use(middleware.CORS())
serviceAURL, err := url.Parse("http://localhost:8080")
if err != nil {
e.Logger.Fatal(err)
}
serviceAProxy := httputil.NewSingleHostReverseProxy(serviceAURL)
e.Any("/service-a/*", func(c echo.Context) error {
req := c.Request()
res := c.Response()
req.URL.Path = strings.TrimPrefix(req.URL.Path, "/service-a")
if req.URL.Path == "" {
req.URL.Path = "/"
}
serviceAProxy.ServeHTTP(res, req)
return nil
})
e.Logger.Fatal(e.Start(":8081"))
}
-
Create Service B (proxy) with the same setup:
func main() {
e := echo.New()
e.Use(middleware.Recover())
e.Use(middleware.CORS())
e.GET("/", func(c echo.Context) error {
return c.JSON(http.StatusOK, map[string]string{
"service": "service-a",
"version": "1.0.0",
"status": "running",
})
})
e.GET("/health", func(c echo.Context) error {
return c.JSON(http.StatusOK, map[string]string{
"status": "healthy",
})
})
e.Logger.Fatal(e.Start(":8080"))
}
-
Route traffic:
Client → Service A → Service B
-
Send a request from the browser or postman.
Expected Behavior
CORS headers should not be duplicated in the final response.
Ideally:
- Echo should override existing CORS headers, or
- CORS middleware should check if headers are already present before adding them.
Example:
Access-Control-Allow-Origin: *
Actual Behavior
Each proxy layer appends its own CORS headers, resulting in duplicates.
Why this is a problem
This is a realistic production scenario:
- API Gateway
- Internal proxy services
- BFF layers
Disabling CORS in intermediate services is not always possible or desirable, especially when services are also used independently.
Environment
- Echo version:
v4.14.0
- Go version:
go1.24.0
- OS: macOS
Description
When building a proxy server using Echo (golang) and enabling the built-in
middleware.CORS()on each proxy layer, CORS headers are duplicated in the final response.This happens in a common microservice / gateway setup where:
As a result, the response contains duplicated CORS headers such as:
This behavior breaks some browsers and violates expected HTTP header semantics.
Steps to Reproduce
Create Service A (proxy) with Echo:
Create Service B (proxy) with the same setup:
Route traffic:
Send a request from the browser or postman.
Expected Behavior
CORS headers should not be duplicated in the final response.
Ideally:
Example:
Actual Behavior
Each proxy layer appends its own CORS headers, resulting in duplicates.
Why this is a problem
This is a realistic production scenario:
Disabling CORS in intermediate services is not always possible or desirable, especially when services are also used independently.
Environment
v4.14.0go1.24.0